Back to Learning Center
highOWASP Business Logic Abuse Top 10CWE-840CWE-841

Business Logic Vulnerabilities

Business logic vulnerabilities occur when application design flaws allow attackers to abuse legitimate functionality in unintended ways. Unlike technical vulnerabilities that exploit code weaknesses, these attacks manipulate how applications are supposed to work - making them difficult to detect with automated scanners and invisible to traditional security controls.

What Makes Business Logic Flaws Different

Business logic vulnerabilities arise from flawed assumptions about how users will interact with an application. They don't involve malformed input or exploitation of technical weaknesses - instead, attackers use the application exactly as designed, just not as intended.

Key characteristics:

  • Automated scanners cannot detect them
  • Require understanding of business context
  • WAFs and security tools don't block them
  • Often have severe financial or operational impact

Common Attack Patterns

Price Manipulation

javascript
// Vulnerable checkout flow
app.post('/api/checkout', async (req, res) => {
  const { items, total } = req.body;
  
  // VULNERABLE: Trusting client-provided total
  await processPayment(req.user.id, total);
  await createOrder(req.user.id, items);
  
  res.json({ success: true });
});

// Attack: Frontend sends cart items with a modified total
// POST /api/checkout
// { "items": [{"id": "expensive-item", "qty": 1}], "total": 0.01 }
// Attacker pays $0.01 for a $999 item

Secure: Server-Side Price Calculation

javascript
// Secure: Calculate total on server
app.post('/api/checkout', async (req, res) => {
  const { items } = req.body;
  
  // Calculate total from database prices
  let total = 0;
  for (const item of items) {
    const product = await db.products.findById(item.id);
    if (!product) {
      return res.status(400).json({ error: `Invalid product: ${item.id}` });
    }
    total += product.price * item.qty;
  }
  
  // Process with server-calculated total
  await processPayment(req.user.id, total);
  await createOrder(req.user.id, items, total);
  
  res.json({ success: true, total });
});

Workflow Bypass

javascript
// Vulnerable: Multi-step process without state validation

// Step 1: Add items to cart
app.post('/api/cart/add', (req, res) => {
  // ... add to cart
});

// Step 2: Enter shipping address
app.post('/api/checkout/shipping', (req, res) => {
  // ... save shipping
});

// Step 3: Process payment
app.post('/api/checkout/payment', (req, res) => {
  // ... process payment
});

// Step 4: Complete order (VULNERABLE)
app.post('/api/checkout/complete', async (req, res) => {
  // No validation that previous steps were completed!
  await createOrder(req.session.cartId);
  res.json({ success: true });
});

// Attack: Skip directly to /api/checkout/complete
// Bypasses payment step entirely

Secure: State Machine Validation

javascript
// Secure: Validate workflow state
const CHECKOUT_STATES = {
  CART: 'cart',
  SHIPPING: 'shipping',
  PAYMENT: 'payment',
  COMPLETED: 'completed'
};

const VALID_TRANSITIONS = {
  [CHECKOUT_STATES.CART]: [CHECKOUT_STATES.SHIPPING],
  [CHECKOUT_STATES.SHIPPING]: [CHECKOUT_STATES.PAYMENT],
  [CHECKOUT_STATES.PAYMENT]: [CHECKOUT_STATES.COMPLETED],
};

async function validateTransition(checkoutId, targetState) {
  const checkout = await db.checkouts.findById(checkoutId);
  const validNextStates = VALID_TRANSITIONS[checkout.state] || [];
  
  if (!validNextStates.includes(targetState)) {
    throw new Error(`Invalid transition: ${checkout.state} -> ${targetState}`);
  }
  
  return checkout;
}

app.post('/api/checkout/complete', async (req, res) => {
  try {
    // Verify payment step was completed
    const checkout = await validateTransition(
      req.session.checkoutId, 
      CHECKOUT_STATES.COMPLETED
    );
    
    // Also verify payment was actually processed
    if (!checkout.paymentId) {
      return res.status(400).json({ error: 'Payment not processed' });
    }
    
    await createOrder(checkout);
    res.json({ success: true });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

Coupon and Discount Abuse

javascript
// Vulnerable: Multiple discount stacking
app.post('/api/cart/apply-coupon', async (req, res) => {
  const { couponCode } = req.body;
  const cart = await getCart(req.user.id);
  
  const coupon = await db.coupons.findByCode(couponCode);
  if (!coupon || coupon.expired) {
    return res.status(400).json({ error: 'Invalid coupon' });
  }
  
  // VULNERABLE: No check for existing discounts
  cart.appliedCoupons.push(coupon);
  
  // Apply all discounts
  let discount = 0;
  for (const c of cart.appliedCoupons) {
    discount += c.discountPercent;
  }
  
  // Attack: Stack multiple 50% coupons for free items
  cart.discount = Math.min(discount, 100);  // Caps at 100% but still exploitable
  
  await cart.save();
  res.json(cart);
});

// Attack flow:
// 1. Apply SUMMER50 (50% off)
// 2. Apply WELCOME50 (50% off)
// 3. Get 100% off on everything

Secure: Discount Rules Engine

javascript
// Secure: Explicit discount rules
const DISCOUNT_RULES = {
  maxCouponsPerOrder: 1,
  maxDiscountPercent: 50,
  incompatibleCoupons: [
    ['SUMMER50', 'WELCOME50'],  // Can't use together
  ],
};

app.post('/api/cart/apply-coupon', async (req, res) => {
  const { couponCode } = req.body;
  const cart = await getCart(req.user.id);
  
  // Check coupon limit
  if (cart.appliedCoupons.length >= DISCOUNT_RULES.maxCouponsPerOrder) {
    return res.status(400).json({ 
      error: 'Maximum coupon limit reached' 
    });
  }
  
  const coupon = await validateCoupon(couponCode, cart);
  
  // Check incompatibility
  for (const existing of cart.appliedCoupons) {
    if (areIncompatible(existing.code, couponCode)) {
      return res.status(400).json({ 
        error: 'This coupon cannot be combined with existing discounts' 
      });
    }
  }
  
  // Calculate with cap
  const totalDiscount = calculateTotalDiscount(cart, coupon);
  if (totalDiscount > DISCOUNT_RULES.maxDiscountPercent) {
    return res.status(400).json({ 
      error: 'Maximum discount exceeded' 
    });
  }
  
  await applyDiscount(cart, coupon);
  res.json(cart);
});

Inventory Manipulation

javascript
// Vulnerable: Reservation without time limit
app.post('/api/cart/reserve', async (req, res) => {
  const { productId, quantity } = req.body;
  
  const product = await db.products.findById(productId);
  if (product.stock < quantity) {
    return res.status(400).json({ error: 'Out of stock' });
  }
  
  // VULNERABLE: Reserve indefinitely without checkout
  product.stock -= quantity;
  await product.save();
  
  req.session.reserved.push({ productId, quantity });
  res.json({ success: true });
});

// Attack: Reserve all inventory across multiple sessions
// Other customers see "out of stock"
// Attacker never completes purchase

Secure: Time-Limited Reservations

javascript
// Secure: Expire reservations automatically
const RESERVATION_TTL = 15 * 60 * 1000; // 15 minutes

app.post('/api/cart/reserve', async (req, res) => {
  const { productId, quantity } = req.body;
  
  // Atomic reservation with expiry
  const result = await db.reservations.create({
    userId: req.user.id,
    productId,
    quantity,
    expiresAt: new Date(Date.now() + RESERVATION_TTL),
  });
  
  if (!result.success) {
    return res.status(400).json({ error: 'Unable to reserve' });
  }
  
  res.json({ 
    success: true, 
    expiresAt: result.expiresAt 
  });
});

// Background job: Release expired reservations
cron.schedule('*/5 * * * *', async () => {
  await db.reservations.deleteMany({
    expiresAt: { $lt: new Date() }
  });
});

// Real stock = physical_stock - active_reservations

Negative Value Exploitation

javascript
// Vulnerable: No validation of quantity sign
app.post('/api/cart/update', async (req, res) => {
  const { productId, quantity } = req.body;
  const cart = await getCart(req.user.id);
  
  // VULNERABLE: Accepts negative quantities
  const item = cart.items.find(i => i.productId === productId);
  item.quantity = quantity;
  
  // Total calculation
  cart.total = cart.items.reduce(
    (sum, item) => sum + (item.price * item.quantity), 
    0
  );
  
  await cart.save();
  res.json(cart);
});

// Attack: Set quantity to -1
// POST /api/cart/update { "productId": "item1", "quantity": -1 }
// Item costs $100, so cart total becomes -$100
// Store owes the attacker money!

Secure: Input Validation with Business Rules

javascript
import { z } from 'zod';

const updateCartSchema = z.object({
  productId: z.string().uuid(),
  quantity: z.number().int().min(0).max(100),
});

app.post('/api/cart/update', async (req, res) => {
  const result = updateCartSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ error: result.error.issues });
  }
  
  const { productId, quantity } = result.data;
  
  // Additional business rule validation
  const product = await db.products.findById(productId);
  if (quantity > product.maxPerOrder) {
    return res.status(400).json({ 
      error: `Maximum ${product.maxPerOrder} per order` 
    });
  }
  
  // Update cart safely
  await updateCartItem(req.user.id, productId, quantity);
  res.json(await getCart(req.user.id));
});

Security Checklist

  • Never trust client-provided prices, totals, or calculations
  • Implement state machine validation for multi-step workflows
  • Define explicit rules for discounts, limits, and combinations
  • Add time limits to reservations and pending actions
  • Validate numeric inputs for range, sign, and reasonability
  • Log business logic operations for anomaly detection
  • Conduct threat modeling focused on business flows
  • Perform manual testing by security engineers who understand the business

Practice Challenges

View all