Defending from Vulnerabilities: Exploiting Payment Discounts via Logical Flaws
The attacker manipulates the discount application by applying multiple discount codes, even though the system is supposed to allow only one. This manipulation results in an excessive discount, allowing the attacker to purchase items at a much lower price or even for free.
In this week's series, we explore a scenario where an application handles discount codes for purchases. The discount code logic is correctly implemented, and users can apply a discount code during checkout to reduce the total price of their purchase. However, an attacker discovers that the discount logic is applied in two separate steps: first, the discount is validated and applied to the cart, and second, the cart total is calculated at checkout.
The attacker manipulates the discount application by applying multiple discount codes, even though the system is supposed to allow only one. This manipulation results in an excessive discount, allowing the attacker to purchase items at a much lower price or even for free. This scenario highlights the importance of maintaining logical consistency and ensuring that validation is enforced throughout the entire transaction process.
Understanding the Attack
- Discount Code Application Logic:
- The application allows users to apply a discount code during checkout, which reduces the total price of their purchase.
- The system is designed to allow only one discount code per transaction.
- Attack Vector:
- The attacker discovers a logical flaw where they can apply multiple discount codes in separate requests, even though the system is supposed to limit the usage to one.
- This manipulation results in an excessive discount, allowing the attacker to significantly reduce the total price or complete the transaction for free.
Defensive Strategy
To defend against this specific discount manipulation scenario, follow these steps:
Enforce Single Discount Code Usage: Ensure that only one discount code can be applied per transaction by maintaining consistent validation across all stages of the checkout process.
Example Code:
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
app.post('/apply-discount', (req, res) => {
const { discountCode } = req.body;
// Check if a discount code has already been applied
if (req.session.appliedDiscount) {
return res.status(400).send('A discount code has already been applied');
}
// Validate and apply the discount
const discount = validateDiscountCode(discountCode);
if (!discount) {
return res.status(400).send('Invalid discount code');
}
// Store the applied discount in the session
req.session.appliedDiscount = discount;
res.send('Discount applied successfully');
});
function validateDiscountCode(code) {
// Mock function to validate discount code
const validCodes = { 'SAVE10': 10, 'SAVE20': 20 };
return validCodes[code] || null;
}
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Implement Server-Side Validation and Recalculation: Recalculate the cart total on the server side based on validated discount codes and original item prices, rejecting any excess or unauthorized discounts.
Example Code:
app.post('/checkout', (req, res) => {
const cart = req.session.cart || [];
const discount = req.session.appliedDiscount || 0;
// Recalculate cart total
let cartTotal = cart.reduce((total, item) => total + item.price, 0);
// Apply the validated discount
cartTotal -= discount;
// Ensure cart total is non-negative
if (cartTotal < 0) {
return res.status(400).send('Invalid cart total');
}
// Proceed with order processing
res.send(`Order processed successfully with total: ${cartTotal}`);
});
Limit Discount Code Combinations: Apply business rules that restrict the types of discounts that can be combined and enforce these rules on both the client and server sides.
Example Code:
app.post('/apply-discount', (req, res) => {
const { discountCode } = req.body;
// Check if multiple discounts are being applied
if (req.session.appliedDiscounts && req.session.appliedDiscounts.length >= 1) {
return res.status(400).send('Multiple discounts are not allowed');
}
// Validate and apply the discount
const discount = validateDiscountCode(discountCode);
if (!discount) {
return res.status(400).send('Invalid discount code');
}
// Store the applied discount in the session
req.session.appliedDiscounts = [...(req.session.appliedDiscounts || []), discount];
res.send('Discount applied successfully');
});
Enable Logging and Monitoring for Unusual Discount Patterns: Log and monitor transactions that involve large or unexpected discounts, flagging them for review.
Example Code:
app.post('/apply-discount', (req, res) => {
const { discountCode } = req.body;
// Check if multiple discounts are being applied
if (req.session.appliedDiscounts && req.session.appliedDiscounts.length >= 1) {
return res.status(400).send('Multiple discounts are not allowed');
}
// Validate and apply the discount
const discount = validateDiscountCode(discountCode);
if (!discount) {
return res.status(400).send('Invalid discount code');
}
// Store the applied discount in the session
req.session.appliedDiscounts = [...(req.session.appliedDiscounts || []), discount];
res.send('Discount applied successfully');
});
Conclusion
By enforcing single discount code usage, recalculating the total price on the server side, limiting discount combinations, and enabling logging and monitoring, you can effectively mitigate the risk of discount code abuse and financial 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.