October 1, 2026
Broken Function Level Authorization in Linktree GraphQL API Allows Lower-Privileged Admins toβ¦
Introduction
By Ahemd ashraf
3 min read
Introduction
While testing the authorization model of Linktree's GraphQL API, I discovered a Broken Function Level Authorization vulnerability that allowed a lower-privileged Admin account to perform an administrative operation that should have been restricted to the account Owner.
The application correctly distinguished between different administrative roles in the UI, but the backend GraphQL API did not enforce the same privilege boundary for the affected mutation.
Understanding the Authorization Model
The account had multiple privilege levels.
The Owner had permission to manage administrators and invite additional users.
A lower-privileged Admin account, however, did not have permission to perform the same operation.
This created an interesting authorization boundary to test:
Can an Admin directly call the GraphQL mutation used by the Owner to invite another administrator?
Instead of relying on the UI, I tested the authorization directly at the API layer.
Finding the Mutation
While inspecting the application's GraphQL requests, I identified a mutation used by the Owner to add another user as an administrator.
The important part was that the operation itself was accessible through the GraphQL API and accepted user/account-related parameters.
At this point, the expected behavior was:
- Owner session β mutation should succeed.
- Lower-privileged Admin session β mutation should be rejected.
I then tested the exact same operation using the lower-privileged Admin's authenticated session.
Proof of Concept
First, I authenticated as the account Owner and captured the request responsible for adding another administrator.
I then replaced the authentication cookie with the cookie belonging to the lower-privileged Admin account.
No Owner credentials were used for the second request.
The same GraphQL mutation was sent using the Admin session.
Conceptually, the request looked like:
mutation {
<owner_admin_invitation_mutation>(
identifier: "<target-user>"
accountId: "<account-id>"
) {
...
}
}mutation {
<owner_admin_invitation_mutation>(
identifier: "<target-user>"
accountId: "<account-id>"
) {
...
}
}The important change was not the mutation itself.
The only relevant change was the authenticated session.
Owner Session
Using the Owner's cookie:
Request β Owner session β Mutation β SuccessRequest β Owner session β Mutation β SuccessThis was expected behavior.
Lower-Privileged Admin Session
I then replaced the Owner's cookie with the cookie belonging to the Admin account that did not have permission to add administrators.
The request was still accepted.
The API successfully performed the operation and added the target user as an administrator.
The response indicated that the user had been successfully added, for example:
<target-user> has been added as an admin<target-user> has been added as an adminThis demonstrated that the backend was authorizing the mutation based on authentication rather than verifying whether the authenticated user had the required privilege to execute that function.
Why This Is a Security Issue
The important point is that this was not simply an API endpoint being exposed.
The application already had a privilege distinction:
Owner β lower-privileged Admin
The lower-privileged Admin was intentionally not supposed to have the ability to invite/add administrators.
However, the backend accepted the same privileged operation when it was called directly through GraphQL.
This is a classic example of Broken Function Level Authorization (BFLA).
The authorization check should have been performed against the function being requested, not merely against whether the requester was authenticated or belonged to the account.
Impact
An attacker who compromises or controls a lower-privileged Admin account could potentially use this authorization flaw to perform administrative actions reserved for the Owner.
In this case, the attacker could add another user as an administrator despite not having the required permission.
Depending on the application's administrative capabilities, this could allow:
- Unauthorized administrator creation
- Privilege escalation
- Expansion of attacker-controlled access
- Persistence through additional administrator accounts
- Potential compromise of the entire workspace/account
The demonstrated impact was the unauthorized creation of an administrator using a session that did not have permission to perform that operation.
Root Cause
The GraphQL mutation did not correctly enforce function-level authorization.
The backend appeared to verify that the request was associated with an authenticated account, but failed to verify that the authenticated user's role was authorized to execute the specific administrative mutation.
Authentication answers:
"Who is this user?"
Authorization must additionally answer:
"Is this user allowed to perform this specific operation?"
The second check was missing or insufficient.
How It Should Be Fixed
The server should enforce authorization inside the resolver responsible for the mutation.
Before performing the operation, the backend should verify:
- The requester is authenticated.
- The requester belongs to the relevant account/workspace.
- The requester's role has permission to invite/add administrators.
- The requested target account/resource belongs to the same authorized scope.
- The requested operation is permitted for that role.
For example, conceptually:
Authenticated user
β
Resolve account/workspace
β
Resolve user's role
β
Check permission for "invite administrator"
β
Allow or reject mutationAuthenticated user
β
Resolve account/workspace
β
Resolve user's role
β
Check permission for "invite administrator"
β
Allow or reject mutationThe authorization decision must happen server-side and must not depend on whether the operation is hidden from the frontend UI.
Conclusion
This vulnerability highlights an important lesson when testing GraphQL APIs:
Never assume that hiding a privileged function from a lower-privileged user's UI is sufficient authorization.
GraphQL makes this particularly interesting because mutations that are not exposed through the normal UI can often still be called directly if the schema and endpoint are accessible.
In this case, a lower-privileged Admin could directly invoke a mutation intended for the Owner and successfully add another administrator.
The proper security boundary must therefore be enforced inside the backend resolver itself, regardless of how the request was generated.
Key Takeaway for Pentesters
When testing role-based authorization, don't only test:
"Can this user access this page?"
Also test:
"Can this user directly invoke functions that belong to a higher privilege level?"
For GraphQL applications, enumerate mutations and compare their behavior across different roles.
A useful methodology is to capture the same privileged operation using an Owner account and then replay it using a lower-privileged account.
If the lower-privileged session can successfully execute the operation, you may have a Broken Function Level Authorization vulnerability.