admin管理员组

文章数量:1404923

I recently discovered a serious security issue in Django e-commerce websites where users can modify product prices before adding items to the cart.

Many developers allow users to send price data from the frontend, which can be easily tampered with using Burp Suite or browser developer tools.


Example of the Issue:

Consider a simple Django view that adds items to the cart:

def add_item(request):
    product_id = request.GET.get('product_id')
    price = request.GET.get('price')  #  User-controlled value (security risk)
    qty = int(request.GET.get('qty', 1))

    cart_item = {
        'product_id': product_id,
        'qty': qty,
        'price': price,  #  This price comes from the user, not the database!
    }

    request.session['cart'] = request.session.get('cart', {})
    request.session['cart'][product_id] = cart_item
    request.session.modified = True

    return JsonResponse({'message': 'Added to cart'})

How an Attacker Can Exploit This:

  1. A product costs $500 in the database.
  2. The user clicks "Add to Cart".
  3. Instead of sending the original price, the attacker intercepts the request using Burp Suite.
  4. The price field is changed to $1, and the request is forwarded.
  5. The cart now stores the manipulated price, and the user can proceed to checkout with the wrong amount.

Why Is This a Security Risk?

  • The backend trusts data from the frontend, which can be easily manipulated.
  • The session stores the wrong price, leading to financial loss.
  • Attackers can buy expensive products at extremely low prices by modifying request data.

Discussion Points for the Community:

  • What are the best practices to prevent this?
  • Should e-commerce sites always fetch prices from the database instead of accepting them from the frontend?
  • What other vulnerabilities should developers be aware of when handling cart data in Django?

Would love to hear your thoughts on this!

I recently discovered a serious security issue in Django e-commerce websites where users can modify product prices before adding items to the cart.

Many developers allow users to send price data from the frontend, which can be easily tampered with using Burp Suite or browser developer tools.


Example of the Issue:

Consider a simple Django view that adds items to the cart:

def add_item(request):
    product_id = request.GET.get('product_id')
    price = request.GET.get('price')  #  User-controlled value (security risk)
    qty = int(request.GET.get('qty', 1))

    cart_item = {
        'product_id': product_id,
        'qty': qty,
        'price': price,  #  This price comes from the user, not the database!
    }

    request.session['cart'] = request.session.get('cart', {})
    request.session['cart'][product_id] = cart_item
    request.session.modified = True

    return JsonResponse({'message': 'Added to cart'})

How an Attacker Can Exploit This:

  1. A product costs $500 in the database.
  2. The user clicks "Add to Cart".
  3. Instead of sending the original price, the attacker intercepts the request using Burp Suite.
  4. The price field is changed to $1, and the request is forwarded.
  5. The cart now stores the manipulated price, and the user can proceed to checkout with the wrong amount.

Why Is This a Security Risk?

  • The backend trusts data from the frontend, which can be easily manipulated.
  • The session stores the wrong price, leading to financial loss.
  • Attackers can buy expensive products at extremely low prices by modifying request data.

Discussion Points for the Community:

  • What are the best practices to prevent this?
  • Should e-commerce sites always fetch prices from the database instead of accepting them from the frontend?
  • What other vulnerabilities should developers be aware of when handling cart data in Django?

Would love to hear your thoughts on this!

Share Improve this question edited Mar 31 at 14:14 Google User asked Mar 9 at 6:19 Google UserGoogle User 1751 silver badge8 bronze badges 2
  • 1 Yeah I've seen that "e-commerce project" frequently. I however did not know people really used this, since aside from the security vulnerability, the project had a lot performance issues, bad code design, and so on. – willeM_ Van Onsem Commented Mar 9 at 6:25
  • 1 See for example here and here. Unfortunately there are a lot of youtube videos where you "learn" Django, but the host often themself is not very skilled in Django, or takes shortcuts not meant in a production setting. – willeM_ Van Onsem Commented Mar 9 at 6:30
Add a comment  | 

2 Answers 2

Reset to default 2

What are the best practices to prevent this?

You don't need the price, the view should add the product_id to the cart, and perhaps a quantity, but adding something to the cart has no price involved. It even makes it more complicated to later apply discounts, since the price is determined per product.

Should e-commerce sites always fetch prices from the database instead of accepting them from the frontend?

Not per se, there are some APIs that determine prices on-the-fly. Bots that thus drive up the price if there is more demand, or if a competitor drops their prices.

What other vulnerabilities should developers be aware of when handling cart data in Django?

This has nothing to do with a cart, you always ask the user the absolute minimum you need to know, and by design thus limit the parameters, since this limits what you can tamper with.

I've seen some questions on StackOverflow with a similar approach. Sometimes they define a class Cart that then operates on the session data. But this was indeed bad design, and often not only in terms of this security vulnerability, but performance, referential integrity, etc.: you add an item to a cart with a GET request, which makes no sense; and you can even add a non-existing product as well.

The GET request thus means that if a person making the request hits refresh, it is made a second time. But more importantly, GET requests should be cacheable, which clearly is not the case, and it also puts the product_id (and price) in the URL, which means it is at least visible in the path as well, which is not good practice either.

I always got the impression some person gave a "Django eCommerce tutorial" on Youtube, and people copied some code. But apparently some indeed moved this to production.

To properly handle cart functionality and prevent users from tampering with prices, it's essential to store cart data securely in the database rather than relying on user input.

Cart Table

This table tracks user carts, whether they are associated with an authenticated user or an anonymous session.

  • id (Primary Key)

  • user (ForeignKey to User, nullable for guest users)

  • session (ForeignKey to Session, nullable for authenticated users)

  • created_at (Timestamp for when the cart was created)

  • is_available (Boolean to mark active/inactive carts)

Products Table

This stores product details, including price, which must always be retrieved from the database.

  • id (Primary Key)

  • name (Product name)

  • price (Positive Integer for price storage)

  • category (ForeignKey to a separate Category table for better normalization)

CartItems Table

This table associates products with a cart and ensures price integrity.

  • id (Primary Key)

  • cart (ForeignKey to Cart, linking items to a cart)

  • product (ForeignKey to Product, ensuring price consistency)

  • quantity (To track the number of items)

here is the sample code:

from django.db import models from django.contrib.sessions.models
import Session from django.contrib.auth.models
import User

class Cart(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)  
    session = models.ForeignKey(Session, on_delete=models.CASCADE, null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    is_available = models.BooleanField(default=True)

class Product(models.Model):
    name = models.CharField(max_length=255)  
    price = models.PositiveIntegerField()
    category = models.CharField(max_length=255)  # Ideally, this should be a separate table
class CartItem(models.Model):
    cart = models.ForeignKey(Cart, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)  
    quantity = models.PositiveIntegerField(default=1)

Notes:

  • In the Cart table, both session and user are nullable to support both guest and authenticated carts.

  • It's best to create a separate Category table and reference it with a foreign key for better database normalization.

After designing a robust database structure, the only step required is to send the product ID to the backend. The backend will then add the item to the user's or session's cart, creating a new cart if one doesn't already exist.

本文标签: