Defending from Vulnerabilities: Bypassing Payment Validation with Mixed Price Manipulation
Price manipulation is not always about setting negative amounts or reduced amount which you can prevent by adding a validation. However, often the validation can be bypassed or abused by mixed price manipulations.
Price manipulation is not always about setting negative amounts or reduced amounts, which you can prevent by adding a validation. However, the validation can often be bypassed or abused by mixed price manipulations.
In this week's series, we explore a vulnerability in financial transaction handling on a movie ticketing website. The application correctly validates that ticket prices must be zero or positive during checkout, preventing direct tampering with negative values. However, an attacker manipulates the total cart value by adding three tickets with negative prices and three tickets with correct prices, resulting in an overall cart value of zero. Upon processing the order, the attacker successfully purchases three tickets without paying. This scenario highlights the need for robust validation and consistency checks across all transaction stages.
Understanding the Attack
- Price Validation During Checkout:
- The application validates that the total price during checkout must be zero or positive.
- Negative prices are detected and rejected during checkout.
- Attack Vector:
- The attacker adds three tickets with negative prices to the cart.
- The attacker then adds three tickets with the correct prices, making the total cart value zero.
- The checkout process accepts the cart with a zero value, allowing the attacker to purchase three tickets without paying.
Defensive Strategy
To defend against this specific price manipulation scenario, follow these steps:
Implement Item-Level Price Validation: Validate each item's price before adding it to the cart to ensure no negative or manipulated prices are accepted.
Example:
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
app.post('/add-to-cart', (req, res) => {
const { ticketId, ticketPrice } = req.body;
// Validate ticket price before adding to cart
if (ticketPrice < 0) {
return res.status(400).send('Invalid ticket price');
}
// Add ticket to cart (assuming cart is stored in session or database)
req.session.cart = req.session.cart || [];
req.session.cart.push({ ticketId, ticketPrice });
res.send('Ticket added to cart');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Perform Consistency Checks on the Cart Total: Ensure that the cart total is calculated based on validated item prices rather than relying on potentially manipulated input.
Example:
app.get('/cart-total', (req, res) => {
const cart = req.session.cart || [];
const cartTotal = cart.reduce((total, item) => total + item.ticketPrice, 0);
// Ensure total is non-negative
if (cartTotal < 0) {
return res.status(400).send('Invalid cart total');
}
res.send({ cartTotal });
});
Implement Server-Side Validation and Recalculation: Perform server-side recalculation of the total price during checkout based on original prices stored in the database, not user-supplied data.
Example:
app.post('/checkout', async (req, res) => {
const cart = req.session.cart || [];
// Recalculate total based on original prices in the database
let cartTotal = 0;
for (let item of cart) {
const originalTicket = await db.getTicketById(item.ticketId);
if (!originalTicket) {
return res.status(400).send('Invalid ticket in cart');
}
cartTotal += originalTicket.price;
}
// Validate final cart total
if (cartTotal < 0) {
return res.status(400).send('Invalid cart total');
}
// Process the order
// Save order, deduct inventory, etc.
res.send('Order processed successfully');
});
Enable Logging and Monitoring for Unusual Transactions: Log and monitor for unusual transactions, such as carts with a zero or negative total, and flag them for review.
Example:
const express = require('express');
const winston = require('winston');
const app = express();
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'transactions.log' }),
],
});
app.post('/checkout', async (req, res) => {
const cart = req.session.cart || [];
let cartTotal = 0;
for (let item of cart) {
const originalTicket = await db.getTicketById(item.ticketId);
if (!originalTicket) {
return res.status(400).send('Invalid ticket in cart');
}
cartTotal += originalTicket.price;
}
if (cartTotal < 0) {
logger.warn(`Negative cart total detected: ${cartTotal}`, { cart });
return res.status(400).send('Invalid cart total');
}
logger.info(`Checkout processed with cart total: ${cartTotal}`, { cart });
res.send('Order processed successfully');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Conclusion
By implementing item-level price validation, performing consistency checks on the cart total, recalculating prices during checkout, and enabling logging and monitoring, you can effectively mitigate the risk of payment validation bypass through mixed price manipulation in your application. This detailed approach helps developers and security engineers understand and apply these protections in their specific scenarios. Stay tuned for more specific attack scenarios and defensive strategies in our weekly series.