June 21, 2026
From Revoked Privileges to Resource Creation: A Privilege Persistence Vulnerability in a VDP…
My name is Montaser Mohsen, a security researcher and bug bounty hunter focused on web application security, broken access control…
montaser mohsen
3 min read
My name is Montaser Mohsen, a security researcher and bug bounty hunter focused on web application security, broken access control, business logic flaws, and authorization issues.
I spend much of my time analyzing how modern applications handle permissions, role changes, and sensitive operations. During a recent Vulnerability Disclosure Program engagement, I discovered an interesting authorization issue involving privilege revocation and client-side state handling.
To protect the affected organization, all identifiers, domains, organization IDs, and sensitive information have been modified.
Introduction
Access control vulnerabilities are among the most impactful security issues in modern applications. While many researchers focus on classic privilege escalation, some vulnerabilities appear only after permissions change.
This article describes a Privilege Persistence vulnerability discovered during a private VDP engagement.
The issue occurred after a user's privileges were revoked. Although the application correctly returned an authorization error, a previously captured successful response could still influence the application's workflow, ultimately allowing unauthorized resource creation.
Vulnerability Type
- Broken Access Control
- Privilege Persistence
- Authorization Bypass
- Business Logic Vulnerability
Initial Access
The researcher was initially added to an organization with elevated privileges such as Administrator or Modeler.
At that point, creating models inside the organization was allowed.
Example request:
POST /api/models HTTP/2
Host: app.example.com
Content-Type: application/json
{
"data": {
"name": "example-model",
"description": "test",
"organization": "ORG_ID"
}
}POST /api/models HTTP/2
Host: app.example.com
Content-Type: application/json
{
"data": {
"name": "example-model",
"description": "test",
"organization": "ORG_ID"
}
}The server returned:
HTTP/2 201 Created
Content-Type: application/json
{
"name": "example-model",
"organization": "ORG_ID",
"status": "active"
}HTTP/2 201 Created
Content-Type: application/json
{
"name": "example-model",
"organization": "ORG_ID",
"status": "active"
}The model was successfully created.
Permission Revocation
After some time, the user's role inside the organization was changed to a restricted role with limited permissions.
Interestingly, the application still displayed the Create Model button in the user interface.
When attempting to create a model normally, the server correctly responded:
HTTP/2 401 Unauthorized
You are not authorized to create models for this organization.HTTP/2 401 Unauthorized
You are not authorized to create models for this organization.At first glance, the authorization mechanism appeared to function properly.
Previous Authorized State
During earlier testing, while elevated permissions still existed, the researcher had already captured a successful creation response.
Example:
HTTP/2 201 Created
Content-Type: application/json
{
"name": "example-model",
"organization": "ORG_ID",
"status": "active"
}HTTP/2 201 Created
Content-Type: application/json
{
"name": "example-model",
"organization": "ORG_ID",
"status": "active"
}This response represented a valid model creation operation performed before the role downgrade.
Response Replay and Client State Manipulation
Further testing revealed that the application relied heavily on the server response to determine whether model creation had succeeded.
When the restricted user attempted to create a new model, the server returned:
HTTP/2 401 Unauthorized
You are not authorized to create models for this organization.HTTP/2 401 Unauthorized
You are not authorized to create models for this organization.The response was intercepted using an intercepting proxy.
Instead of forwarding the new authorization failure to the application, the previously captured successful response was replayed.
Conceptually:
Server Response:
401 Unauthorized
↓
Intercepted by Proxy
↓
Replaced With:
201 Created (previously captured)
↓
Client processes operation as successful
↓
New model appears inside the organizationServer Response:
401 Unauthorized
↓
Intercepted by Proxy
↓
Replaced With:
201 Created (previously captured)
↓
Client processes operation as successful
↓
New model appears inside the organizationThe replayed response looked similar to:
HTTP/2 201 Created
Content-Type: application/json
{
"name": "example-model",
"organization": "ORG_ID",
"status": "active"
}HTTP/2 201 Created
Content-Type: application/json
{
"name": "example-model",
"organization": "ORG_ID",
"status": "active"
}Surprisingly, the application accepted the old success response and completed the workflow.
A new model became available inside the organization despite the user's restricted role.
Why Is This Important?
The issue was not simply a user interface problem.
The server correctly returned a 401 Unauthorized response.
However, the application trusted the client-side success state sufficiently for a previously authorized operation to influence a new unauthorized request.
This created a privilege persistence condition where revoked permissions did not fully invalidate previous authorization context.
Root Cause
Several factors contributed to the issue:
- Permission changes were not fully synchronized with application state.
- Previously trusted responses remained usable after privilege revocation.
- The client relied heavily on response data to determine operation success.
- Authorization revocation did not completely invalidate earlier privileged state.
- The application workflow accepted stale success information.
Attack Scenario
Step 1: User receives elevated permissions.
Role = AdministratorRole = AdministratorRequest:
POST /api/modelsPOST /api/modelsResponse:
201 Created201 CreatedStep 2: Permissions are revoked.
Role = Restricted UserRole = Restricted UserRequest:
POST /api/modelsPOST /api/modelsResponse:
401 Unauthorized401 UnauthorizedStep 3: Previous success response is replayed.
Old 201 Created response
↓
Client trusts success state
↓
Model creation workflow completesOld 201 Created response
↓
Client trusts success state
↓
Model creation workflow completesImpact
Depending on the application's architecture, this issue could lead to:
- Unauthorized resource creation.
- Abuse of organizational quotas.
- Continued access after permission revocation.
- Business logic bypass.
- Weak privilege revocation mechanisms.
- Inconsistent authorization enforcement.
Recommendations
To prevent similar issues, applications should:
- Revalidate permissions on every sensitive operation.
- Immediately invalidate authorization state after role changes.
- Refresh permissions before executing privileged actions.
- Avoid relying on client-side state to determine operation success.
- Ensure that previously authorized responses cannot influence future requests.
- Invalidate cached permission information after role modifications.
Lessons Learned
Authorization testing should not stop after receiving a 401 Unauthorized response.
Important questions include:
- What happens after permissions change?
- Does the client retain previous authorization state?
- Can old responses influence new actions?
- Does the application trust the client too much?
- Are revoked privileges fully invalidated?
Sometimes the vulnerability is not the authorization check itself, but how the application handles state after permissions change.
Disclosure
This issue was responsibly disclosed through the affected organization's Vulnerability Disclosure Program.
All company names, domains, identifiers, organization IDs, and technical details have been modified to protect the affected platform.
Thanks for reading 🙏 I'll keep sharing more from my bug bounty journey.
Feel free to connect or share your thoughts
Facebook: https://facebook.com/montasermohsen98 Twitter (X): https://x.com/Montaser_M98 LinkedIn: https://linkedin.com/in/montasermohsen98