Introduction
Without any doubt, time and again life grants benefits to people who make their purchases earliest. The digital environment provides opportunities for cybercriminals to exploit this short period of time. These time-based system flaws allow experienced users to conquer the system.
The lack of proper safeguards during multiple-step tasks on websites produces a short period when reality becomes unclear. Two people enter the same door at once yet both ignore the other since they neither notice the passing experience.
Online shoppers can use the short period of reality-blurring to get premium leather jackets at unprecedented bargains through the stores’ systems. The basic understanding of how to send various server requests at the perfect times can yield desired results without formal computer science qualifications.
We will immediately start with a practical demonstration based on PortSwigger’s simulated shopping website vulnerability in this lab. Viewers will observe the process of vulnerability identification followed by typical tool usage and comprehend the crucial nature of time in securing online platforms.
Core-Concepts
Race Condition
A race condition happens when two or more threads/processes handle the same data at the same time where the ending results depend on the exact timing of execution.
Programmers fail to manage sequences properly which enables attackers to bypass logical operations that include withdrawing too much from the account and skipping security protocols and record discrepancies.
- Frequently found in:
- Financial apps (e.g., double spending)
- Booking/reservation systems
- Under parallel request situations APIs demonstrate weaknesses.
TOCTOU (Time-Of-Check to Time-Of-Use)
In this subtype of race condition an application checks and then uses a resource state which creates an opportunity for attackers to modify the resource between these moments.
Accessing an empty room and assuming no entry occurred during your absence demonstrates a Real-World Analogy of TOCTOU.
- Example Attack Scenario:
The application performs a write permission check and subsequently the attacker establishes an intentional symbolic link redirect to a critical file which the application prevails to modify by mistake.
Common Attack Vectors
Attack Surface | Scenario | Example |
1. Web Servers | Multiple concurrent HTTP requests to critical endpoints | POST /withdraw , POST /checkout |
| Vulnerable if backend fails to lock operations correctly | - |
2. Databases | Read-modify-write race conditions | App reads balance → parallel withdrawals → all succeed |
3. File Systems | Operations on shared directories without locking | Upload a file → replace with symlink before processing (/tmp , /uploads ) |
Blackbox Testing
Lab 1: Limit Overrun Race Conditions:
Now login with your lab credentials
and now add the lightweight l33t leather jacket
to your cart and proceed to checkout. Now explore the web application functionality and do make sure you apply your coupon.
Now intercept the response, and send this to repeater more than 10 times. Make sure you right-click on tab on repeater and make sure this option to group all the repeater tabs and select send group (parallel)
.
Add all the labs to a single tab group, toggle the send button (the downward button) and make sure you have atleast 30 repeater tabs, just hit ctrl+R
on the intercepted request to replicate the requests.
Hit sent group (parallel)
and notice that you should get discount and only a bill payment of something near to twenty dollars. Since the shop has $50 credit, you should be able to make this purchase. Once the purchase has been completed then you should have solved the lab.
Whitebox Testing
Lab 2: Diogene’s Rage
Field | Details |
Lab URL: | https://app.hackthebox.com/challenges/Diogenes'%2520Rage |
Objective: | Exploit a race condition to win a game unfairly or bypass business logic constraints |
Lab Credentials: | Login usually not required; interact directly with the web challenge |
Note: | Use Burp Suite or a custom script (e.g., Python + threading or ffuf ) to trigger the race condition effectively |
Let’s start by examining the vulnerable code pattern we’re trying to detect. Now the vulnerability exists inside web_diogenes_rage/challenge/routes/index.js
and the following code snippet causes the race condition vulnerability. You could read the other files to understand how web application works, how database works and authentication works, but core vulnerability resides inside routes/index.js
router.post('/api/coupons/apply', AuthMiddleware, async (req, res) => {
return db.getUser(req.data.username)
.then(user => {
if (user.coupons.includes(coupon_code)) {
return res.status(401).send("Coupon already redeemed!");
}
// ... other logic ...
db.setCoupon(user.username, coupon_code); // Race condition here!
});
});
The issue here is straightforward but dangerous, the application checks if a user has already redeemed a coupon, then updates the database in a non-atomic operation. If a user sends multiple concurrent requests, they could potentially redeem the same coupon multiple times before any check reflects the updated state.
Now based on this vulnerability, let’s write semgrep
rule to detect it’s vulnerability. Now if this is your first time hearing about this tool then fear now, this tool is used to detect vulnerability in code, and used yaml
templates to detect vulnerabilities. You can install semgrep
on your machine and get started using this URL
Attempt 1: The Naive Await-Based Rule
My first instinct was to craft a rule looking for the await
pattern:
pattern: |
async (req, res) => {
const user = await $DB.getUser(...);
if (!$CALL.includes(...)) {
await $DB.addBalance(...);
}
}
This rule failed immediately because the target code used promise chains (.then()
) rather than await
. A reminder that we need to match the actual code structure, not just the logical flow.
Attempt 2: Overly Generic Promise Pattern
Next, I tried a more generic approach:
pattern: |
$DB.getUser($USERNAME).then($USER => { ... })
While this matched the structure better, it was far too broad. It would flag any database query followed by a promise chain, regardless of whether it represented a race condition. The signal-to-noise ratio would be unbearable in a real codebase.
Attempt 3: Syntax Errors with Ellipsis
My third attempt tried to be more specific about the vulnerable pattern:
pattern: |
$DB.getUser($USERNAME).then($USER => {
...
if ($USER.coupons.includes($COUPON)) { ... }
...
$DB.setCoupon(...);
})
This rule failed with parsing errors. The ellipsis operator (...
) in Semgrep can be tricky - it needs to be used in a way that preserves valid syntax in the target language.
The Winning Solution: Finding Balance
After several trials and attempts, I landed on a solution that works:
rules:
- id: race-condition-toctou-nodejs
patterns:
- pattern-inside: |
$DB.getUser($USERNAME).then($USER => {
...
if ($USER.coupons.includes($COUPON)) { ... }
...
$DB.setCoupon($USER.username, $COUPON);
})
message: "TOCTOU Race Condition: Non-atomic coupon redemption detected"
languages: [javascript]
severity: ERROR
Testing our semgrep rule:
Let’s run the below command to scan index.js
.
┌──(semgrep)─(kali㉿kali)-[~/Downloads/web_diogenes_rage/challenge]
└─$ semgrep --config race-condition-toctou.yaml routes/index.js
┌──── ○○○ ────┐
│ Semgrep CLI │
└─────────────┘
Why This Rule Works
- The rule searches within particular callback scope through pattern-inside syntax to prevent syntax-related errors.
- This rule recognizes the precise operational series which produces the vulnerability by first fetching user data and next assessing coupon status before executing non-atomic update commands.
- The ellipsis operators are correctly placed to create empty blocks which do not violate JavaScript syntax constraints.
Real-World Considerations and Trade-offs
Our rule might flag legitimate code that has additional protections, such as:
- Implementations using database-level locks
- Code with custom synchronization mechanisms
- Transactions that make the operations atomic
False Negatives
Our rule will miss race conditions if:
- Different method names are used (e.g.,
updateCoupon
instead of setCoupon
)
- The code uses a different pattern to achieve the same functionality
- The race condition involves different resources or patterns
For example, this variant would slip through:
db.updateUser(user.username, { coupons: [...user.coupons, coupon_code] }); // Not detected!
Further-improvements
Now based on this limitations we have created one more rule, that detects the vulnerability in code, now feel free to test this code in real time environment and do let me know if it fails or detects vulnerabilities.
Exploitation:
To keep the blog short and concise, you can refer to this writeup credit goes to the author, and you can read, understand, execute the python script to get the flag.
The fix:
Here’s how to patch the TOCTOU vulnerability in the /api/coupons/apply
endpoint and harden the code against race conditions.
Step 1: Modify routes/index.js
:
router.post('/api/coupons/apply', AuthMiddleware, async (req, res) => {
let transaction;
try {
// Start transaction
transaction = await db.beginTransaction();
// Get user WITH row lock (FOR UPDATE)
const user = await db.getUserForUpdate(req.data.username);
if (!user) {
await db.registerUser(req.data.username);
user = { username: req.data.username, balance: 0.00, coupons: [] };
}
const { coupon_code } = req.body;
if (!coupon_code) {
await db.rollback(transaction);
return res.status(400).json({ error: "Missing coupon code!" });
}
// Atomic check + update
if (user.coupons.includes(coupon_code)) {
await db.rollback(transaction);
return res.status(400).json({ error: "Coupon already redeemed!" });
}
const coupon = await db.getCouponValue(coupon_code);
if (!coupon) {
await db.rollback(transaction);
return res.status(404).json({ error: "Invalid coupon!" });
}
// Atomic operations
await db.addBalance(user.username, coupon.value);
await db.setCoupon(user.username, coupon_code);
// Commit
await db.commit(transaction);
return res.json({ message: "Coupon redeemed successfully!" });
} catch (error) {
if (transaction) await db.rollback(transaction);
console.error("Coupon redemption error:", error);
return res.status(500).json({ error: "Internal server error" });
}
});
Key Changes:
- Uses database transactions to lock the user row (
SELECT ... FOR UPDATE
).
- Performs check (
includes
) and updates (addBalance
, setCoupon
) atomically.
Step 2: Update Database Helper (database.js
)
Add transaction support to your database module:
// database.js
class Database {
async beginTransaction() {
const conn = await this.pool.getConnection();
await conn.query('START TRANSACTION');
return conn;
}
async commit(conn) {
await conn.query('COMMIT');
conn.release();
}
async rollback(conn) {
await conn.query('ROLLBACK');
conn.release();
}
async getUserForUpdate(username) {
const [user] = await this.pool.query(
'SELECT * FROM users WHERE username = ? FOR UPDATE',
[username]
);
return user;
}
// Existing methods: getUser, addBalance, setCoupon...
}
Conclusion
Creating proficient static analysis rules for race conditions proves difficult yet ambitious work. The TOCTOU vulnerability vulnerability detection rule we created works properly as an element within an extensive security framework.
Semgrep
presents itself as a strong security tool although mastering both the target code patterns and patterns syntax requires thorough proficiency. Ongoing practice along with rule refinement will make your code analysis become more efficient at identifying bugs which would otherwise slip to production.
Until next time keep exploring and feel free to try some labs on portswigger
, play some ctfs
from CTFtime, and keep hacking !