August 26, 2026
Race Condition That Created an Undeletable Group Member
Overview

By ShivKumar
4 min read
Overview
These Vulnerability describes a Time-of-Check to Time-of-Use (TOCTOU) race condition in the original disclosure's group/member functionality.
The core idea is simple:
Two requests that modify the same group-member state can arrive at nearly the same time. The application checks the state before the previous operation has fully completed, leaving the member in an inconsistent state.
1. Vulnerability
Type: Race Condition / TOCTOU Target: the original disclosure group/member functionality Impact: A group member could become undeletable through normal application functionality.
Real-world example
Suppose an admin wants to remove Alice from a group.
Normally:
Alice is a member
↓
Admin clicks Remove
↓
Server removes Alice
↓
Alice is no longer a memberAlice is a member
↓
Admin clicks Remove
↓
Server removes Alice
↓
Alice is no longer a memberNow imagine two related requests are processed at nearly the same time.
The server can temporarily see an old state in one request while another request is already changing it.
That timing gap can leave the application in an unexpected state — for example, the member remains present but cannot be removed through the normal workflow.
2. Root Cause
The root cause is unsafe handling of shared state during concurrent requests.
A simplified vulnerable design might look like:
Request A → check member state
Request B → check member state
Request A → change member state
Request B → change member stateRequest A → check member state
Request B → check member state
Request A → change member state
Request B → change member stateBoth requests made their decisions using state that could become outdated.
The important point is:
Checking something and changing it must be synchronized when the operation depends on the check.
This is why simply validating each request individually may not be enough.
3. Attack Flow
A simplified attack flow is:
Identify group-member operation
↓
Capture the normal HTTP request
↓
Find an operation that changes member state
↓
Send the same/state-related requests concurrently
↓
Application processes overlapping operations
↓
Database/application state becomes inconsistent
↓
Member can no longer be removed normallyIdentify group-member operation
↓
Capture the normal HTTP request
↓
Find an operation that changes member state
↓
Send the same/state-related requests concurrently
↓
Application processes overlapping operations
↓
Database/application state becomes inconsistent
↓
Member can no longer be removed normallyThe attacker does not necessarily need to bypass authentication.
The bug comes from making the server process valid requests at the wrong time.
4. How to Find It
When testing a web application, look for operations that modify the same object:
- Add/remove group members
- Join/leave groups
- Accept/revoke invitations
- Add/remove permission
- Create/delete resources
- Redeem one-time benefits
- Vote/unvote
- Apply/remove limits
- Transfer or redeem value
Then ask:
"What happens if two valid requests for the same object arrive at almost exactly the same time?"
Good hunting targets
Look for endpoints containing concepts such as:
/member
/group
/invite
/remove
/delete
/join
/leave
/accept
/revoke/member
/group
/invite
/remove
/delete
/join
/leave
/accept
/revoke5. Reproduction Methodology
Step 1 — Test normally
First understand the intended behavior.
For example:
Add member
↓
Member appears
↓
Remove member
↓
Member disappearsAdd member
↓
Member appears
↓
Remove member
↓
Member disappearsDo not start with concurrency before understanding the normal workflow.
Step 2 — Capture the request
Use a proxy such as Burp Suite.
Record:
HTTP method
Endpoint
Authentication
Session information
Object IDs
Request body
CSRF protectionHTTP method
Endpoint
Authentication
Session information
Object IDs
Request body
CSRF protectionThe goal is to understand which request changes the state.
Step 3 — Create concurrent requests
Duplicate the relevant request.
Then send multiple copies at the same time, rather than one after another.
Conceptually:
Request 1 ──┐
Request 2 ──┤
Request 3 ──┼──> Server
Request 4 ──┤
Request 5 ──┘Request 1 ──┐
Request 2 ──┤
Request 3 ──┼──> Server
Request 4 ──┤
Request 5 ──┘Tools that support parallel HTTP requests can help make the timing more reliable.
Step 4 — Check the result
After the requests finish, inspect the actual application state.
Do not stop at:
HTTP 200 OKHTTP 200 OKInstead ask:
What changed?
Was the object created?
Was it deleted?
Did the permission change?
Can the action still be performed?
Does the UI agree with the backend?What changed?
Was the object created?
Was it deleted?
Did the permission change?
Can the action still be performed?
Does the UI agree with the backend?The final state is the important evidence.
6. Payload Logic
A race-condition payload is usually not about a special malicious string.
The "payload" is often the same valid request sent concurrently.
Conceptually:
Request 1 ───────┐
Request 2 ───────┼──> Server
Request 3 ───────┤
Request 4 ───────┘
↓
Shared application state
↓
Unexpected final stateRequest 1 ───────┐
Request 2 ───────┼──> Server
Request 3 ───────┤
Request 4 ───────┘
↓
Shared application state
↓
Unexpected final stateThis is why race conditions are different from injection bugs.
You are exploiting timing and state, not necessarily malformed input.
7. Security Impact
The confirmed impact of this bugs was limited compared with account takeover or remote code execution.
The practical effect was an undeletable group member, creating an integrity/business-logic problem in the group-management functionality.
Possible consequences of similar race conditions can be much more serious when the affected operation involves:
- Money
- Account permissions
- Invitations
- Access control
- One-time credits
- Password/MFA state
- Resource limits
So the same bug class can range from Low to Critical, depending on what state is being raced.
8. Why Was It Possible?
The key architectural mistake is treating a multi-step state change as if requests will always arrive one after another.
A safe design conceptually needs:
Check state
+
Change state
+
Commit stateCheck state
+
Change state
+
Commit stateto behave as one atomic operation.
An unsafe implementation can behave like:
Request A: check
Request B: check
Request A: update
Request B: updateRequest A: check
Request B: check
Request A: update
Request B: updateBoth requests can make decisions using state that is no longer valid.
9. Lessons for Penetration Testers
Think about timing
Do not only ask:
"Can I send a malicious request?"
Also ask:
"Can I send two legitimate requests before the application finishes processing the first?"
Test state-changing functions
Prioritize:
- Add/remove
- Create/delete
- Claim/redeem
- Join/leave
- Accept/reject
- Grant/revoke
- Transfer/refund
Look for "one-time" rules
A strong race-condition candidate usually has a rule such as:
Only once
Maximum 1
Maximum 5
Cannot be repeated
Already exists
Already removed
Already claimedOnly once
Maximum 1
Maximum 5
Cannot be repeated
Already exists
Already removed
Already claimedThese rules deserve concurrency testing.
Always verify the final state
A 200 OK is not proof of success.
The real question is:
"What state did the server end up in after all concurrent operations finished?"
10. Defensive Fix
Developers should protect sensitive state transitions with mechanisms such as:
- Atomic database transactions
- Row-level locking where appropriate
- Unique database constraints
- Idempotency controls
- Server-side state validation
- Correct transaction isolation
- Atomic check-and-update operations
For example, instead of:
SELECT member
→ check member exists
→ DELETE memberSELECT member
→ check member exists
→ DELETE memberthe operation should be designed so the relevant state cannot change unexpectedly between the check and the update.
11. Key Takeaway
Race conditions are bugs in time, state, and synchronization.
This case is a good beginner example because the attack does not require a complicated payload.
The important skill is recognizing:
"This operation changes shared state. What happens if two valid requests execute at the same time?"