July 14, 2026
How I Demoted a Project Owner to Viewer Using Just Burp Suite โ A Broken Access Bug ๐ชฒ
Medium โ CVSS 6.8 Accepted > Resolved > Rewarded

By Rudr03
4 min read
Medium โ CVSS 6.8 Accepted > Resolved > Rewarded
A Quick Intro
If you've been following along, you already know I'm rudr03 โ a self-taught bug bounty hunter documenting every report I find. This is my second writeup, and this one's a little different from the JS secrets finding. This time it's about logic โ specifically, what happens when a backend forgets to enforce the same rules the frontend is so carefully hiding.
Let's get into it.
The Target
redacted.com โ a platform with project-based access control. Users can be assigned different roles within a project: Owner, Admin, and Viewer, each with different levels of permission.
The UI had a clear rule baked in: you cannot change the Project Owner's role. No dropdown, no option, nothing. The frontend made it look like an iron wall.
Spoiler: it wasn't.
The Idea
Most Broken Access Control bugs don't live in the UI. They live in the API that the UI talks to.
When I started poking at the role management feature, I noticed something interesting. Changing a user's role wasn't just a button click โ it was a PATCH request to a backend API, with the target user's ID sitting right in the URL path. That's a classic setup for an IDOR-style exploit.
The question was: does the backend actually verify whether the target user is the Project Owner before allowing the role change?
Time to find out.
Step 1 โ Setting Up the Scenario
To test this properly, I set up a two-account scenario:
- Account A โ the victim. Creates a project, becomes the Project Owner.
- Account B โ the attacker. Gets invited by Account A and assigned the Project Admin role.
First thing I did was confirm what the UI shows for Account B (the attacker). And sure enough โ the Project Owner's role had no edit option. The UI completely hid any way to modify the owner's role.
This is important because it confirms the restriction is intentional โ the developers knew this shouldn't be allowed. The frontend enforces it. The question is whether the backend does too.
Step 2 โ Finding the Project Owner's User ID
Before I could do anything useful, I needed the victim's user ID. This part was almost too easy.
On the project access page, there's an option to "View User" on any member's profile. Clicking it redirects you to a URL that contains the user's ID (UUID)right in the path.
So I clicked on the Project Owner's profile โ grabbed their ID from the URL. No fuzzing, no brute forcing. Just clicking around.
For the purpose of this writeup, let's use placeholder IDs:
- Victim (Project Owner):
aaaa-1111-bbbb-2222 - Attacker (Project Admin):
cccc-3333-dddd-4444 - Other member:
eeee-5555-ffff-6666
Step 3 โ Capturing a Legitimate Role Change Request
Next, I used Account B (the attacker/admin) to do something totally normal: change the role of a different project member โ not the owner. Just a regular role change to see what the API request looks like.
I had Burp Suite running with the proxy intercepting traffic. Here's what the request looked like:
PATCH /users/eeee-5555-ffff-6666/project-access/project-xyz
Host: api.redacted.com
Authorization: Bearer <attacker_token>
Content-Type: application/json
{
"role": "viewer"
}PATCH /users/eeee-5555-ffff-6666/project-access/project-xyz
Host: api.redacted.com
Authorization: Bearer <attacker_token>
Content-Type: application/json
{
"role": "viewer"
}Clean, simple. The user ID is in the URL path. The role is in the body. The attacker's auth token is in the header.
Now the fun part.
Step 4 โ Swapping the User ID
I intercepted the same request again โ and before forwarding it, I replaced the target user ID in the URL with the Project Owner's ID:
PATCH /users/aaaa-1111-bbbb-2222/project-access/project-xyz
Host: api.redacted.com
Authorization: Bearer <attacker_token>
Content-Type: application/json
{
"role": "viewer"
}PATCH /users/aaaa-1111-bbbb-2222/project-access/project-xyz
Host: api.redacted.com
Authorization: Bearer <attacker_token>
Content-Type: application/json
{
"role": "viewer"
}Everything else stayed the same. Same auth token (the attacker's), same project, same role. Only the user ID in the path changed.
I forwarded the request and waited.
Step 5 โ The Response
HTTP/1.1 204 No ContentHTTP/1.1 204 No ContentNo error. No "403 Forbidden". No "you can't do that". Just a clean, silent 204.
I switched over to Account A (the victim) and refreshed the project access page.
The Project Owner's role now showed as Viewer.
The original project creator โ the person who made the project, invited everyone, and held all the keys โ was now sitting in a read-only role on their own project, with no way to promote themselves back. Because viewers can't manage roles.
What This Actually Means
This isn't just a "you changed a number in a URL" kind of bug. The real-world impact here is pretty serious:
- A Project Admin (lower privilege) can permanently strip the Project Owner (higher privilege) of all access
- The owner loses control of project configuration, deployments, and resources
- The owner cannot recover their own privileges without external help
- An attacker could do this silently, in the background, before taking any destructive action
- In a production environment, this could mean complete loss of control over live infrastructure
The UI explicitly prevented this. The backend simply didn't.
Why This Happened
The root cause is something I see come up a lot in BAC findings: the backend trusted user-supplied input without validating it against authorization rules.
The API received a user ID in the URL, looked up that user, and changed their role โ without ever checking:
- Is the target user the Project Owner?
- Does the requesting user have the authority to modify the target user's role specifically?
The frontend said "no" to this operation. The backend never got that memo.
Fix Suggestions
If you're a developer reading this, here's what should change:
- Never trust user-controlled identifiers blindly โ derive the target identity from validated session context, not raw URL parameters
- Enforce role hierarchy at the backend โ if the UI says "owners can't be demoted," the API should enforce that too, always
- Add an ownership invariant โ every project should always have at least one immutable owner; any request that would break that should be rejected outright
- Server-side authorization checks โ before any role modification, validate that the requester actually has permission to modify that specific user's role based on role hierarchy, not just "is this person an admin?"
That's it for this one. If this helped you think differently about role management features or access control testing, drop 50 claps ๐ and follow along.
More reports coming !
โ rudr03