Until recently I had not solved any hacker101 CTF challenges. Last week I was working through them and one of them was BugDB v2. Here's the flow of how I got the flag and hope it helps.
Starting Point
Whenever I see a GraphQL API, the first thing I do is run an introspection query. Basically asking the API to describe itself. It came back with a handful of queries. There was also a mutation called modifyBug.
{
__schema {
types {
name
fields {
name
args {
name
type {
name
kind
}
}
type {
name
kind
ofType {
name
kind
}
}
}
}
}
}
The Bugs type had a private field on it, which immediately got my attention. There's probably something being hidden right?
So I queried allBugs and got one result back. A single public bug, posted by admin, nothing interesting in the text. But the ID stood out QnVnczox. I threw it into a base64 decoder and got Bugs:1.

So there's probably a `Bugs:2` somewhere. That would be QnVnczoy in base64.
I tried findBug with that ID. Nothing. I tried several IDs. All null. I also tried the modifyBug mutation to flip a bug from private to public, but that didn't get me anywhere either.
Real Progress
The schema also had this node query. It's a pretty standard GraphQL thing, you give it any ID and it returns whatever object that ID points to. I hadn't tried it yet, so I gave it a shot:
{
node(id: "QnVnczoy") {
id
... on Bugs {
text
private
reporter {
username
}
}
}
}
And finally it returned with what I've been looking for. The flag. The private bug, owned by a user called victim, with the flag in the text field.
What Actually Went Wrong for me here was:
The allBugs and findBug queries both checked whether a bug was private before returning it. But that was not the same case with the node query. It's a generic utility that fetches any object by ID, and was not gatekeeping anything.
So even though the flag was private, you could just ask for it directly by ID. And since the IDs were just sequential numbers wrapped in base64, guessing Bugs:2 took about five seconds.
Authorization in GraphQL needs to live at the resolver level, on every single query that touches sensitive data. It's not enough to protect the obvious endpoints if there's a back door hiding in a utility query that nobody thought to check.
Thanks for reading. Happy hacking.