GraphQL Security

GraphQL Security

๐Ÿ” Introduction

GraphQL์€ ์›น์—์„œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์„œ๋ฒ„๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์ด๊ฒŒ ๊ฐ€์ ธ๋Š” ๊ฒƒ์„ ๋ชฉ์ ์œผ๋กœ ํ•˜๋Š” Query Lanauge๋กœ ๊ธฐ์กด REST API์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ ์ž ๋“ฑ์žฅํ•œ ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค. REST API๊ฐ€ ์„œ๋ฒ„์—์„œ ์ •์˜ํ•œ ์ŠคํŽ™์— ๋”ฐ๋ผ ํ˜ธ์ถœํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์–ป๋Š” ํ˜•ํƒœ๋ผ๋ฉด GraphQL์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ •์˜ํ•œ ํฌ๋งท์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ณ  ์‹ถ์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค.

REST API, SQL์„ ํ†ตํ•œ ์„œ๋น„์Šค๋“ค์ด ๋ฐฑ์—”๋“œ์—์„œ ์ฟผ๋ฆฌํ•˜๊ณ  ์ฒ˜๋ฆฌํ•œ๋‹ค๋ฉด, GraphQL ์„œ๋น„์Šค๋Š” ํ”„๋ก ํŠธ์—์„œ ์ฟผ๋ฆฌํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ๋น„์ค‘์ด ๋†’์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ GraphQL์˜ HTTP ์š”์ฒญ์€ ๊ตฌ์กฐ๋ฅผ ์ •์˜ํ•˜๋Š” Schema์™€ ์‹ค์ œ ์ฟผ๋ฆฌ์ธ Query์™€ Mutation ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋‰˜์–ด์ง‘๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ GQL์ด๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค.

Schema

type  Query {
   polygon (sides: Int, regular: Boolean): Polygon
}

type  Polygon {
   perimeter: Float
   area: Float
}

Query & Mutation

query {
  polygon(sides: 1, regular: true) {
	perimeter
	area
  }
}

mutation {
  createPerson(input: { ... }) {
    person {
      id
      name
    }
  }
  updatePerson(input: { ... }) {
    person {
      id
      name
    }
  }
}

๐Ÿ—ก Offensive techniques

How to Testing

GraphQL์„ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋น„์Šค๋Š” ๋ณดํ†ต ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” Form, JSON ๊ธฐ๋ฐ˜์ด ์•„๋‹Œ ๋ณ„๋„์˜ Body๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ณดํ†ต /graphql ๋“ฑ์˜ ๊ฒฝ๋กœ๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋ฉฐ ํ•ด๋‹น ๊ฒฝ๋กœ๊ฐ€ ์•„๋‹ˆ๋”๋ผ๋„ query๋ฅผ ํฌํ•จํ•œ ๊ฒฝ์šฐ GrphQL ์„œ๋น„์Šค๋กœ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

Testing point

์œ„์—์„œ ์„ค๋ช…ํ–ˆ๋“ฏ์ด GraphQL์—์„œ์˜ ๋ฐฑ์—”๋“œ๋Š” ์ „๋‹ฌ๋ฐ›์€ GQL์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌ๋งŒ ํ•ฉ๋‹ˆ๋‹ค. ์ฟผ๋ฆฌ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ถ€๋ถ„์€ ํ”„๋ก ํŠธ์—์„œ ๋‹ด๋‹นํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ ์†Œ๋ฆฌ๋Š” ๊ฒฐ๊ตญ ๊ธฐ์กด ๋ฐฑ์—”๋“œ์— ๊ฐ์ถฐ์ง„ ๋กœ์ง๋“ค์ด ํ”„๋ก ํŠธ์—”๋“œ๋กœ ๋งŽ์ด ๋„˜์–ด์˜จ๋‹ค๋Š” ์˜๋ฏธ์ด๊ณ , ๊ณง ์šฐ๋ฆฌ๊ฐ€ ํ…Œ์ŠคํŒ…ํ•  ํฌ์ธํŠธ๊ฐ€ ๋Š˜์–ด๋‚ฌ๋‹ค๋Š” ์˜๋ฏธ๊ฐ€ ๋˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

ZAP

ZAP์€ GQL์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. Request์—์„œ Schema์™€ Query ์ •๋ณด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ณ , Site tree์—์„œ๋„ GQL ๋‚ด์šฉ์— ๋”ฐ๋ผ ์ผ๋ถ€ ํ‘œ๊ธฐ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ GQL์˜ ๊ฐ๊ฐ ํ•„๋“œ๋“ค์€ ๋ชจ๋‘ Input Vectors์— ํฌํ•จ๋˜๊ธฐ ๋•Œ๋ฌธ์— ActiveScan ์‹œ GQL๋„ ํ…Œ์ŠคํŒ… ๋ฒกํ„ฐ๊ฐ€ ์ถ”๊ฐ€๋˜์–ด ์Šค์บ”๋ฉ๋‹ˆ๋‹ค.

GraphQL ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ์„ค์ •์€ Options > GraphQL์— ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ƒ๋‹จ Import > Import a GraphQL ~~~ ๋ฉ”๋‰ด๋ฅผ ํ†ตํ•ด File์ด๋‚˜ URL ๊ธฐ๋ฐ˜์œผ๋กœ GraphQL Scheme์„ ๋กœ๋“œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

BurpSuite

BurpSuite ๋˜ํ•œ GQL์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. BurpSuite๋Š” Request ํƒญ์—์„œ ๋ณ„๋„๋กœ GraphQL ํƒญ์„ ์ง€์›ํ•˜๋ฉฐ, Schema์™€ Query๋ฅผ ๋‚˜๋ˆ ์„œ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค๋งŒ GQL ์ง€์›์„ ์œ„ํ•ด์„  GraphQL Raider๋ž€ ํ™•์žฅ ๊ธฐ๋Šฅ ์„ค์น˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Insomnia

Insomnia๋Š” API ํ…Œ์ŠคํŒ… ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋ฉฐ, Postman๊ณผ ๊ฐ™์ด API ์ŠคํŽ™์„ ์ •์˜ํ•˜๊ณ  ๋งž์ถฐ์„œ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋„ GraphQL์„ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— GQL ์ •๋ณด๋ฅผ ๋กœ๋“œํ•˜์—ฌ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐœ์ธ์ ์œผ๋กœ๋„ ๊ฐœ๋ฐœ/๋ณด์•ˆํ…Œ์ŠคํŒ… ์‹œ ์ •๋ง ์ž˜ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ณ  ์ข‹์•„ํ•˜๋Š” ๋„๊ตฌ์—์š”. Insomnia๊ฐ€ ๊ถ๊ธˆํ•˜์‹œ๋‹ค๋ฉด โ€œInsomnia๋กœ REST API๋ฅผ ์‰ฝ๊ฒŒ ํ…Œ์ŠคํŠธํ•˜์ž ๐Ÿ˜Žโ€ ๊ธ€์„ ์ฝ์–ด์ฃผ์„ธ์š”!

Attacks

GraphQL Injection

โ€œCullinan > GraphQL Injectionโ€œ์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

IDOR

API, REST API ์„œ๋น„์Šค์™€ ๋™์ผํ•˜๊ฒŒ GraphQL ๋˜ํ•œ IDOR์˜ ์˜ํ–ฅ์„ ๋งŽ์ด ๋ฐ›์Šต๋‹ˆ๋‹ค.

Original

query {
  getMyInfo(userid: "31114") {
	key
	secret
  }
}

Attack

query {
  getMyInfo(userid: "0") { # 0 is admin user id
	key
	secret
  }
}

IDOR์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ Cullinan > IDOR ํ•ญ๋ชฉ์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”!

Improper Access Control

GrphQL์€ GraphQL์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ API๋ฅผ ํ•ธ๋“ค๋งํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์น˜ API G/W์™€ ์œ ์‚ฌํ•œ ํ˜•ํƒœ์ธ๋ฐ์š”, ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ ๊ฐ API์˜ ๊ถŒํ•œ ๋ฒ”์œ„๋‚˜ ์ธ์ฆ ๋“ฑ์„ ๋†“์น˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. GraphQL์— ์—ฐ๋™๋˜๋Š” ์„œ๋น„์Šค๋Š” GQL์— ๋”ฐ๋ผ ์ถฉ๋ถ„ํžˆ API๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ณ  ์™ธ๋ถ€๋กœ ์ŠคํŽ™์„ ์ง์ ‘์ ์œผ๋กœ ๋…ธ์ถœ์‹œํ‚ค์ง€ ์•Š์•„๋„ GQL์„ ํ†ตํ•ด ๊ตฌ์กฐ๋ฅผ ํŒŒ์•…ํ•˜๊ณ  ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

query {
  getAdminData() {
	key
	secret
  }
}

Race condition

API ๊ณ„ํ†ต์˜ ์„œ๋น„์Šค๋“ค์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ ํ˜•ํƒœ์— ๋”ฐ๋ผ์„œ Race condition์˜ ์˜ํ–ฅ์„ ๋ฐ›๋Š” ๊ฒƒ ์ฒ˜๋Ÿผ GraphQL ๋˜ํ•œ ๋‹จ์‹œ๊ฐ„์— ๋งŽ์€ ์ˆ˜์˜ ์š”์ฒญ์ด ์™”์„ ๋•Œ ๋ฐฑ์—”๋“œ์˜ ์ฒ˜๋ฆฌ ๋กœ์ง์— ๋”ฐ๋ผ Race condition์˜ ์˜ํ–ฅ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ Query๋ฅผ ๊ณต๊ฒฉ์ž๊ฐ€ ์ž์œ ๋กญ๊ฒŒ ๋˜์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ฟผ๋ฆฌ๋ฅผ ์ž˜ ๊ตฌ์„ฑํ•˜์—ฌ Race condition์„ ์„ฑ๊ณตํ•  ํ™•๋ฅ ์„ ๋†’์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

Etc

์ด์™ธ์—๋„ ์—ฌ๋Ÿฌ Injection ์ด๋‚˜ ์›น ์ทจ์•ฝ์  ๋“ฑ์ด ๋ชจ๋‘ ์œ ํšจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ „๋ฐ˜์ ์œผ๋กœ Schema๋‚˜ Query ๋Œ€ํ•ด์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ›ก Defensive techniques

Basic

๊ธฐ๋ณธ์ ์œผ๋กœ ์œ„์— ์ด์•ผ๊ธฐํ•œ ๊ณต๊ฒฉ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๋Œ€์‘๋“ค์€ ๋ชจ๋‘ ์ด๋ฃจ์–ด์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. GraphQL์ด ์—ฐ๊ฒฐ๋˜๋Š” API ๋“ค์€ ๋ชจ๋‘ ํŠน์ˆ˜๋ฌธ์ž ๊ฒ€์ฆ ๋“ฑ์œผ๋กœ Injection ๊ณต๊ฒฉ์„ ์˜ˆ๋ฐฉํ•˜๊ณ  ๋ถˆํ•„์š”ํ•œ ์ •๋ณด๋Š” Client๋กœ ๋„˜๊ฒจ์ฃผ์ง€ ์•Š๋„๋ก ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋“ฑ์ด ์ž˜ ๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Access Control

GraphQL์˜ ํŽธ์˜์„ฑ์œผ๋กœ ์žƒ๋Š” ๊ฒƒ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ ์ธ์ฆ/์ธ๊ฐ€๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ฐ๊ฐ ๊ฐœ๋ณ„ ๊ธฐ๋Šฅ๋“ค์ด ํ•˜๋‚˜์˜ GQL๋กœ ๋ฌถ์ด๊ธฐ ๋•Œ๋ฌธ์— ์‹ ๊ฒฝ์จ์„œ ๊ถŒํ•œ์„ ์ž˜ ๋ถ„๋ฆฌํ•˜๊ณ  ๊ถŒํ•œ์— ๋งž๋Š” ์š”์ฒญ์ธ์ง€ ๊ตฌ๋ณ„ํ•˜๋Š” ๋กœ์ง๋“ค์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Large Query ๋Œ€์‘

GQL์€ ํด๋ผ์ด์–ธํŠธ์—์„œ ์š”์ฒญํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋•Œ๋•Œ๋กœ ์•…์˜์ ์ธ ์ฟผ๋ฆฌ๊ฐ€ ์ „์†ก๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด์„œ Timeout์ด๋‚˜ Maximum Query Depth ๋“ฑ์œผ๋กœ ํฐ ์š”์ฒญ์— ๋Œ€ํ•ด ์ œํ•œํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์•„๋ž˜ ๊ธ€์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

https://www.howtographql.com/advanced/4-security/

๐Ÿ•น Tools

๐Ÿ“š Articles

๐Ÿ“Œ References