Press enter or click to view image in full size
Disclaimer:This write-up describes a vulnerability that has been responsibly disclosed and fixed by the affected organization. All domains, object names, identifiers, and responses have been redacted or modified to prevent abuse while preserving the technical methodology.
GraphQL has become increasingly popular because it allows clients to request exactly the data they need.
Unfortunately, many developers focus on building GraphQL APIs while overlooking one critical aspect:
| Authorization.
During a Vulnerability Disclosure Program (VDP), I came across a government web application that appeared perfectly normal.
At first, everything looked normal. There were no obvious vulnerabilities, exposed admin panels, JavaScript secrets, or verbose error messages. While exploring the application and monitoring requests in Burp Suite, I noticed an API endpoint that looked interesting. Curious to see how it worked, I decided to investigate it further.
That small observation eventually led to an authentication/authorization flaw exposing sensitive government data.
This is the story of how I found it.
Whenever I test a web application, I first spend time understanding how it communicates with the backend.
While reviewing the application’s source code, I noticed that the GraphQL version was disclosed, indicating that the application was using GraphQL.
This prompted me to inspect the network traffic in Burp Suite, where I captured the following request:
GET /api/participants HTTP/1.1
Host: redacted.tn.gov.inits looks normal request .
Instead of relying on automated scanners, I modified the request into a POST request.
POST /api/participants HTTP/1.1
Host: redacted.gov
Content-Type: application/json
{
"query":" query Test{ __typename }"
}The server responded:
{
"data":{
"__typename":"Query"
}
}That confirmed it.
The endpoint was indeed processing GraphQL queries.
One of the first things I check in GraphQL applications is whether introspection is enabled.
Developers often forget to disable it in production.
I sent a simplified introspection query.
POST /api/participants HTTP/1.1
Host: redacted.gov
Content-Type: application/json
{
"query":"query IntrospectionQuery { __schema { types { name } } }"
}Response:
{
"data":{
"__schema":{
"types":[
{
"name":"Query"
},
{
"name":"Mutation"
},
{
"name":"Candidate"
},
{
"name":"Department"
},
...
]
}
}
}Interesting.
The production server happily exposed its GraphQL schema.
Although GraphQL introspection alone is generally considered an information disclosure issue, it gives attackers an excellent roadmap of the backend.
Raw JSON quickly becomes difficult to read.
Instead of manually scrolling through hundreds of objects, I copied the schema into GraphQL Voyager.
Within seconds I had a visual map of the backend.
Instead of guessing endpoints, I could now see relationships between every object.
Some of the objects immediately caught my attention.
├── getCandidate
├── getUser
├── getAdmin
├── getToken
├── getProgrammeReport
├── getDepartments
└── getEmployeesSeeing object names like these immediately raises an important question:
Are these queries properly protected?
Most people stop after finding GraphQL introspection.
I didn’t.
NOTE: GraphQL Voyager is actually a super tool for visualizing relationships. Check it out!
Ex:
Press enter or click to view image in full size
Now came the real test.
Join Medium for free to get updates from this writer.
I removed all authentication, including cookies, authorization headers, and session tokens, before sending the request
POST /api/participants HTTP/1.1
Host: redacted.gov
Content-Type: application/json{
"query":"query{
getCandidate{
id
}
}"
}I expected one of these:
401 Unauthorizedor
403 ForbiddenBut…….
I got
200 OKThat immediately changed the severity of the issue.
This was no longer about GraphQL introspection.
This was beginning to look like missing authorization.
Since the server wasn’t rejecting anonymous users, I wanted to understand what fields were available.
GraphQL makes this incredibly easy.
I expanded the query.
{"query": "query IntrospectionQuery { getProgrammeWiseReport { items { name from_date to_date total_candidate_count male_count female_count candidate { name mobile email address gender is_handicapped designation } department { name } } } }"}The response returned actual records.
{
"data":{
"getCandidate":[
{
"name":"REDACTED",
"email":"REDACTED",
"mobile":"XXXXXXXXXX",
"designation":"REDACTED",
"department":{
"name":"REDACTED"
}
}
]
}
}No login, no session, no token — nothing.
The backend simply returned sensitive information.
At this point the vulnerability was confirmed.
The GraphQL resolver was serving protected data without verifying whether the caller was authorized. This aligns with the report’s description that sensitive GraphQL queries were processed without authentication or authorization and exposed non-public information.
The issue wasn’t just that GraphQL introspection was enabled.
The real problem was that backend authorization checks were missing.
Because of that, an attacker could enumerate sensitive objects and retrieve personally identifiable information (PII), including programme participant details, without authentication. The reported impact included names, email addresses, phone numbers, departments, designations, and related programme information.
This significantly increases the risk of targeted phishing, impersonation, and automated data harvesting.
Honestly…
After enumerating the schema, I noticed several other interesting objects. Some appeared to be related to administrative functionality, while others hinted at token management and user operations. Naturally, the question crossed my mind: could these endpoints also be vulnerable to broken authorization?
may be .
However, this was a Vulnerability Disclosure Program (VDP), and my objective was to identify and responsibly report security issues — not to extract every piece of accessible data or explore beyond what was necessary.
Once I had successfully demonstrated that sensitive information could be accessed without authentication, the impact of the vulnerability was already clear. Continuing to test additional objects would have provided little value while unnecessarily increasing the scope of my testing.
At that point, I stopped my assessment, documented my findings, and submitted a responsible disclosure report.
This vulnerability existed because multiple security issues combined together:
The vulnerability wasn’t caused by GraphQL itself.
It was caused by trusting requests that should never have been trusted.
Approximately one month after I responsibly reported the vulnerability, the issue was fixed by the organization. I later received an official appreciation from CERT-In for the responsible disclosure.
Press enter or click to view image in full size
— Written by
Aruvasaga Chithan A
Ethical Hacker & Offensive Security Researcher.
Thanks for reading — your support keeps me writing.
See you in the next article…