Race Condition in Payments
Read-modify-write payment operations without database transactions allow attackers to exploit timing windows and spend the same balance multiple times.
How It Works
A race condition in payments occurs when the application reads a user's balance, checks if it is sufficient, and then deducts the amount in separate non-atomic operations. If an attacker sends multiple simultaneous requests, all of them may read the original balance before any deduction is written. Each request sees sufficient funds and proceeds with the purchase. For example, a user with $100 sends 10 concurrent requests for a $50 item. All 10 read $100, all 10 approve the purchase, resulting in $500 in goods for $100. This is called a TOCTOU (Time Of Check to Time Of Use) vulnerability and is common in e-commerce, in-app currencies, and wallet systems.
app.post('/api/purchase', async (req, res) => {
const user = await db.user.findUnique({ where: { id: req.userId } });
if (user.balance < req.body.amount) {
return res.status(400).json({ error: 'Insufficient funds' });
}
await db.user.update({ where: { id: req.userId },
data: { balance: user.balance - req.body.amount } });
await db.order.create({ data: { userId: req.userId, amount: req.body.amount } });
res.json({ success: true });
});app.post('/api/purchase', async (req, res) => {
const result = await db.$transaction(async (tx) => {
const user = await tx.user.findUnique({ where: { id: req.userId } });
if (user.balance < req.body.amount) throw new Error('Insufficient funds');
const updated = await tx.user.update({ where: { id: req.userId },
data: { balance: { decrement: req.body.amount } } });
if (updated.balance < 0) throw new Error('Insufficient funds');
return tx.order.create({ data: { userId: req.userId, amount: req.body.amount } });
});
res.json({ success: true });
});Real-World Example
In 2018, Starbucks disclosed a race condition in their gift card system that allowed attackers to duplicate balances by making simultaneous transfer requests between cards. The bug was discovered through their bug bounty program and had been exploited in the wild to generate unlimited store credit.
How to Prevent It
- Wrap all balance read-check-update operations in a database transaction
- Use atomic operations like decrement instead of reading and subtracting
- Add optimistic locking with version columns to detect concurrent modifications
- Implement idempotency keys to prevent duplicate processing of the same request
Affected Technologies
Data Hogo detects this vulnerability automatically.
Scan Your Repo FreeRelated Vulnerabilities
Price Manipulation
criticalAccepting prices from the client instead of looking them up server-side allows attackers to modify checkout requests and purchase items at any price they choose.
Feature Flags Exposed
lowFeature flags included in the frontend JavaScript bundle reveal unreleased features, internal testing configurations, and potential attack surfaces to anyone inspecting the code.
Debug Routes in Production
mediumDevelopment and testing routes like /debug, /test, /seed, or /api/dev left active in production expose internal data, bypass authentication, or allow state manipulation.
Privilege Escalation
highProfile update endpoints that accept role or permission fields from the request body allow users to promote themselves to admin by adding role: 'admin' to their update request.