보안 취약점을 찾기 위해 CodeQL을 사용하여 CORS 프레임 워크를 모델링합니다
웹 애플리케이션을 위해 CORS를 설정할 때 발생할 수있는 다양한 유형의 취약점이 있으며, 수제 CORS 구현에서 CORS 프레임 워크 및 논리 오류를 불안하게 사용하면 공격자가 인증을 우회 할 수있는 심각한 보안 취약점으로 이어질 수 있습니다. 또한 공격자는 CORS의 오해를 활용하여 웹 애플리케이션에서 기존의 다른 취약점의 심각성을 확대하여 인트라넷의 서비스에 액세스 할 수 있습니다.

이 블로그 게시물에서는 개발자와 보안 연구원이 CodeQL을 사용하여 자신의 라이브러리를 모델링하여 GO에서 GO에서 수행 한 작업을 예로 들어 어떻게 수행 할 수 있는지 보여줍니다. 내가 사용한 기술은 다른 프레임 워크를 모델링하는 데 유용하기 때문에이 블로그 게시물은 자신의 프로젝트에서 취약점을 모델링하고 찾는 데 도움이 될 수 있습니다. CodeQL과 같은 정적 분석기는 구조, 기능 및 가져온 라이브러리에 대한 자세한 정보를 얻을 수 있으므로 GREP와 같은 간단한 도구보다 다재다능합니다. 또한 CORS 프레임 워크는 종종 특정 구조 및 함수를 통한 설정 구성을 사용하기 때문에 CodeQL을 사용하는 것이 코드베이스에서 오해를 찾는 가장 쉬운 방법입니다.
CodeQL에 Code를 추가 할 때는 휠을 재창조하지 않도록 이미 사용 가능한 관련 쿼리 및 프레임 워크를 항상 확인하는 것이 가장 좋습니다. 대부분의 언어의 경우 CodeQL에는 이미 많은 기본 케이스를 다루는 CORS 쿼리가 있습니다. CORS를 구현하는 가장 쉽고 간단한 방법은 수동으로 설정하는 것입니다. Access-Control-Allow-Origin
그리고 Access-Control-Allow-Credentials
응답 헤더. CodeQL은 언어 (예 : Django, Fastapi 및 Flask)의 프레임 워크를 모델링하여 해당 헤더가 설정된 코드의 위치를 식별 할 수 있습니다. CodeQL은 특정 헤더 값을 찾아 해당 모델을 바탕으로 CORS의 간단한 예를 찾아 취약한 값과 일치하는지 확인할 수 있습니다.
다음과 같은 예에서, 서버의 무단 리소스는 임의의 웹 사이트에서 액세스 할 수 있습니다.
func saveHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
}
이는 공격자가 위험한 종말점에 액세스하고 악용 할 수 있기 때문에 로컬로 호스팅하려는 도구와 같이 인증이없는 웹 애플리케이션에 문제가 될 수 있습니다.
이것은 CodeQL이 Set
방법 보안 관련 헤더를 찾는 방법이 프레임 워크에 대한 쓰기. 헤더 쓰기는 다음에 의해 모델링됩니다 HeaderWrite
수업 HTTP.qll
모든 헤더 쓰기를 찾기 위해 다른 모듈과 클래스에 의해 확장됩니다.
/** Provides a class for modeling new HTTP header-write APIs. */
module HeaderWrite {
/**
* A data-flow node that represents a write to an HTTP header.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HTTP::HeaderWrite` instead.
*/
abstract class Range extends DataFlow::ExprNode {
/** Gets the (lower-case) name of a header set by this definition. */
string getHeaderName() { result = this.getName().getStringValue().toLowerCase() }
다음과 같은 유용한 방법 getHeaderName
그리고 getHeaderValue
또한 CORS의 오해와 같은 헤더와 관련된 보안 쿼리를 개발하는 데 도움이 될 수 있습니다. 이전 코드 예제와 달리, 아래 패턴은 그 효과가 훨씬 더 큰 영향을 미치는 CORS의 오해의 예입니다.
func saveHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin",
r.Header.Get("Origin"))
w.Header().Set("Access-Control-Allow-Credentials",
"true")
}
요청 원산지 헤더를 반영하고 자격 증명을 허용하면 공격 웹 사이트가 현재 로그인 한 사용자로서 요청을 만들 수 있으며 전체 웹 응용 프로그램이 손상 될 수 있습니다.
CodeQL을 사용하면 헤더를 모델링하여 CodeQL이 관련 보안 코드 구조를 식별하여 CORS 취약점을 찾을 수 있도록 특정 헤더 및 메소드를 찾을 수 있습니다.
/**
* An `Access-Control-Allow-Credentials` header write.
*/
class AllowCredentialsHeaderWrite extends Http::HeaderWrite {
AllowCredentialsHeaderWrite() {
this.getHeaderName() = headerAllowCredentials()
}
}
/**
* predicate for CORS query.
*/
predicate allowCredentialsIsSetToTrue(DataFlow::ExprNode allowOriginHW) {
exists(AllowCredentialsHeaderWrite allowCredentialsHW |
allowCredentialsHW.getHeaderValue().toLowerCase() = "true"
여기, HTTP::HeaderWrite
이전에 논의 된 바와 같이 클래스는 슈퍼 클래스로 사용됩니다. AllowCredentialsHeaderWrite
모든 헤더가 값에 대한 쓰기를 찾습니다 Access-Control-Allow-Credentials
. 그런 다음 CORS Misconfiguration 쿼리가 자격 증명이 활성화되어 있는지 확인하면 allowcredentialsheaderwrite를 확인할 수있는 소스 중 하나로 사용합니다.
개발자가 CORS 정책을 설정하는 가장 간단한 방법은 서버에서 HTTP 응답에 대한 헤더를 설정하는 것입니다. 헤더가 설정된 모든 인스턴스를 모델링하면 CORS 쿼리에서 이러한 CORS 케이스를 확인할 수 있습니다.
CodeQL을 사용하여 웹 프레임 워크를 모델링 할 때 HTTP::HeaderWrite
모델의 영향을 필요로하는 모든 CodeQL 보안 쿼리에 사용할 수 있습니다. 웹 응용 프로그램의 헤더는 매우 중요 할 수 있으므로 프레임 워크에서 작성할 수있는 모든 방법을 모델링하는 것은 해당 웹 프레임 워크를 CodeQL에 추가하는 첫 번째 단계가 될 수 있습니다.
CodeQL의 모델링 프레임 워크

CORS 헤더를 수동으로 설정하는 대신 많은 개발자가 대신 CORS 프레임 워크를 사용합니다. 일반적으로 CORS 프레임 워크는 모든 응답에 대한 헤더를 추가하기 위해 웹 프레임 워크 라우터에 미들웨어를 사용합니다. 일부 웹 프레임 워크에는 자체 Cors Middleware가 있거나 타사 패키지를 포함해야 할 수도 있습니다. CodeQL에서 CORS 프레임 워크를 모델링 할 때 일반적으로 CORS 정책을 나타내는 관련 구조 및 방법을 모델링합니다. 모델링 된 구조 또는 메소드에 올바른 값이 있으면 쿼리는 구조가 실제로 코드베이스에 사용되는지 확인해야합니다.
프레임 워크의 경우, 우리는 CORS를 크게 지원하기 때문에 선택의 언어로 Go를 살펴볼 것입니다. Go는 몇 가지 CORS 프레임 워크를 제공하지만 대부분 Gin 웹 프레임 워크를위한 CORS 미들웨어 프레임 워크 인 Gin Cors의 구조를 따릅니다. 다음은 CORS에 대한 GIN 구성의 예입니다.
package main
import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"
AllowMethods: []string{"PUT", "PATCH"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool {
return origin == "
}
}))
router.Run()
}
이제 우리는 모델을 모델링했습니다 router.Use
방법 및 cors.New
– 그것을 확인하십시오 cors.Config
구조는 어느 시점에 a router.Use
실제 사용을위한 기능 – 그런 다음 모든 것을 확인해야합니다. cors.Config
적절한 헤더를위한 구조.
다음으로, 우리는 모델링하려는 적절한 헤더 필드를 찾습니다. 기본 CORS의 오해 쿼리의 경우 모델을 모델링 할 것입니다 AllowOrigins
,,, AllowCredentials
,,, AllowOriginFunc
. CodeQL에 프레임 워크를 추가하는 데 관심이있는 모든 것을 보는 데 관심이있는 경우 CodeQL에 Gincors 및 RSCOR를 추가하라는 요청은 CodeQL에 참조로 사용할 수 있습니다. 아래에서 가장 중요한 세부 사항에 대해 논의하겠습니다.
/**
* A variable of type Config that holds the headers to be set.
*/
class GinConfig extends Variable {
SsaWithFields v;
GinConfig() {
this = v.getBaseVariable().getSourceVariable() and
v.getType().hasQualifiedName(packagePath(), "Config")
}
/**
* Get variable declaration of GinConfig
*/
SsaWithFields getV() { result = v }
}
필드가있는 단일 정적 할당 인 SsawithFields를 사용하여 구성 유형을 모델링했습니다. 사용하여 getSourceVariable()
구조가 할당 된 변수를 얻을 수 있으며, 이는 구성이 사용되는 위치를 확인하는 데 도움이 될 수 있습니다. 이를 통해 코드베이스의 CORS 구성 구조가 포함 된 트랙 변수를 찾을 수 있습니다.
func main() {
...
// We can now track the corsConfig variable for further updates,such as when one of the fields is updated.
corsConfig:= cors.New(cors.Config{
...
})}
관련 구조가 포함 된 변수가 있으므로 변수가 기록 된 모든 인스턴스를 찾고자합니다. 이렇게하면 할당 된 관련 속성 값을 이해하여 CORS 구성이 잘못 구성되어 있는지 여부를 결정할 수 있습니다.
/**
* A write to the value of Access-Control-Allow-Origins header
*/
class AllowOriginsWrite extends UniversalOriginWrite {
DataFlow::Node base;
// This models all writes to the AllowOrigins field of the Config type
AllowOriginsWrite() {
exists(Field f, Write w |
f.hasQualifiedName(packagePath(), "Config", "AllowOrigins") and
w.writesField(base, f, this) and
// To ensure we are finding the correct field, we look for a write of type string (SliceLit)
this.asExpr() instanceof SliceLit
)
}
/**
* Get config variable holding header values
*/
override GinConfig getConfig() {
exists(GinConfig gc |
(
gc.getV().getBaseVariable().getDefinition().(SsaExplicitDefinition).getRhs() =
base.asInstruction() or
gc.getV().getAUse() = base
) and
result = gc
)
}
}
추가함으로써 getConfig
함수, 우리는 이전에 생성 된 것을 반환합니다 GinConfig
이를 통해 관련 헤더에 대한 쓰기가 동일한 구성 구조에 영향을 미치는지 확인할 수 있습니다. 예를 들어, 개발자는 취약한 원점이있는 구성과 자격 증명을 허용하는 다른 구성을 만들 수 있습니다. 취약한 원점을 가진 구성 만 보안 문제를 생성하기 때문에 자격 증명을 허용하는 구성은 강조 표시되지 않습니다. CORS 관련 헤더가 다른 프레임 워크에서 모든 확장을하도록 허용함으로써 UniversalOriginWrite
그리고 UniversalCredentialsWrite
우리는 Cors Misconfiguration 쿼리의 것들을 사용할 수 있습니다.
CODEQL에 CORS 오해 쿼리를 작성합니다
CORS 문제는 자격 증명이없는 두 가지 유형으로 분리되어 있습니다 (우리가 찾고있는 곳 또는 null)과 자격 증명이있는 Cors (Origin Reflection 또는 Null을 찾고있는 곳). CodeQL 쿼리를 간단하게 유지하려면 각 유형의 CORS 취약점에 대해 하나의 쿼리를 만들고 그에 따라 심각도를 할당 할 수 있습니다. GO 언어의 경우 CodeQL에는 모든 응용 프로그램에 적용 할 수 있으므로 “자격 증명이있는 COR”유형의 쿼리 만 있습니다.
Go Cors의 오해 쿼리 자체에서 어떻게 사용되는지 확인하기 위해 위에서 만든 모델에 묶어 봅시다.
from DataFlow::ExprNode allowOriginHW, string message
where
allowCredentialsIsSetToTrue(allowOriginHW) and
(
flowsFromUntrustedToAllowOrigin(allowOriginHW, message)
or
allowOriginIsNull(allowOriginHW, message)
) and
not flowsToGuardedByCheckOnUntrusted(allowOriginHW)
...
select allowOriginHW, message
이 쿼리는 중요한 취약점에만 관심이 있으므로 자격 증명이 허용되는지 여부와 허용 된 기원이 원격 소스에서 나오는지 또는 하드 코딩되어 있는지 확인합니다. 오 탐지를 방지하기 위해 원격 소스가 원점에 도달하기 전에 스트링 비교와 같은 특정 경비원이 있는지 확인합니다. 술어를 자세히 살펴 보겠습니다 allowCredentialsIsSetToTrue
.
/**
* Holds if the provided `allowOriginHW` HeaderWrite's parent ResponseWriter
* also has another HeaderWrite that sets a `Access-Control-Allow-Credentials`
* header to `true`.
*/
predicate allowCredentialsIsSetToTrue(DataFlow::ExprNode allowOriginHW) {
exists(AllowCredentialsHeaderWrite allowCredentialsHW |
allowCredentialsHW.getHeaderValue().toLowerCase() = "true"
|
allowOriginHW.(AllowOriginHeaderWrite).getResponseWriter() =
allowCredentialsHW.getResponseWriter()
)
or
...
술어의 첫 번째 부분에서는 헤더를 비교하기 위해 이전에 모델링 한 헤더 중 하나를 사용할 수 있습니다. 이를 통해 자격 증명이없는 모든 헤더 쓰기를 필터링하는 데 도움이됩니다.
exists(UniversalAllowCredentialsWrite allowCredentialsGin |
allowCredentialsGin.getExpr().getBoolValue() = true
|
allowCredentialsGin.getConfig() = allowOriginHW.(UniversalOriginWrite).getConfig() and
not exists(UniversalAllowAllOriginsWrite allowAllOrigins |
allowAllOrigins.getExpr().getBoolValue() = true and
allowCredentialsGin.getConfig() = allowAllOrigins.getConfig()
)
or
allowCredentialsGin.getBase() = allowOriginHW.(UniversalOriginWrite).getBase() and
not exists(UniversalAllowAllOriginsWrite allowAllOrigins |
allowAllOrigins.getExpr().getBoolValue() = true and
allowCredentialsGin.getBase() = allowAllOrigins.getBase()
)
)
}
CORS가 헤더를 통해 설정되지 않으면 CORS 프레임 워크를 사용하는지 확인합니다. UniversalAllowCredentialsWrite
. 해당 원점 값이 “*”로 설정된 모든 인스턴스를 필터링하기 위해 not
CodeQL 키워드 켜짐 UniversalAllowAllOriginsWrite
이들은이 취약점에 적용 할 수 없기 때문에. flowsFromUntrustedToAllowOrigin
그리고 allowOriginIsNull
유사한 논리를 따라 결과 헤더 권한이 취약한 지 확인하십시오.
CODEQL 쿼리를 모델링하여 CORS와 관련된 취약점을 감지하면 한 가지 크기의 접근 방식을 사용할 수 없습니다. 대신 두 가지 이유로 각 웹 프레임 워크에 쿼리를 조정해야합니다.
- 각 프레임 워크는 고유 한 방식으로 Cors 정책을 구현합니다
- 취약성 패턴은 프레임 워크의 동작에 달려 있습니다
예를 들어, 우리는 Gin Cors에서 전에 AllowOriginFunc
. 문서를 살펴 보거나 코드를 실험 한 후, 우리는 그것이 무시할 수 있음을 알 수 있습니다. AllowOrigins
. 쿼리를 개선하기 위해 찾는 CodeQL 쿼리를 작성할 수 있습니다. AllowOriginFunc
s는 항상 true를 반환하여 자격 증명과 쌍을 이루면 심각도 취약성이 높아집니다.
이것을 당신과 함께 가져 가십시오
CodeQL을 사용하여 웹 프레임 워크 및 헤더의 동작을 이해하면 코드에서 보안 문제를 찾고 취약점이 작업에 들어갈 가능성을 줄이는 것은 간단합니다. CORS의 오해 쿼리를 지원하는 CodeQL 언어의 수는 여전히 증가하고 있으며 항상 커뮤니티의 개선의 여지가 있습니다.
이 블로그가 CodeQL 쿼리 작성에 도움이된다면 CodeQL 커뮤니티 팩에서 커뮤니티와 공유하고 싶은 것을 자유롭게 열어주십시오.
마지막으로, Github Code Security는 CORS Misconfiguration과 같은 버그에 대한 수정을 감지하고 제안함으로써 프로젝트를 확보하는 데 도움이 될 수 있습니다!
더 많이 탐색하십시오 Github 보안 실험실 블로그 게시물>
작성자가 작성했습니다
Post Comment