Как пользователи могут изменять цены в корзинах с помощью Burp Suite и почему это представляет угрозу безопасности в Django?

Недавно я обнаружил серьезную проблему с безопасностью на веб-сайтах электронной коммерции Django, где пользователи могут изменять цены на товары перед добавлением товаров в корзину.

Многие разработчики позволяют пользователям отправлять данные о ценах из интерфейса, которые можно легко подделать с помощью Burp Suite или инструментов разработчика браузера.

<время работы/>

Пример проблемы:

Рассмотрим простое представление Django, которое добавляет товары в корзину:

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'})
<время работы/>

Как Злоумышленник может воспользоваться Этим:

  1. Стоимость продукта $500 в базе данных.
  2. Пользователь нажимает "Добавить в корзину".
  3. Вместо отправки первоначальной цены злоумышленник перехватывает запрос с помощью Burp Suite.
  4. Поле price будет изменено на $1, и запрос будет перенаправлен.
  5. В корзине теперь хранится измененная цена, и пользователь может перейти к оформлению заказа, указав неправильную сумму.
<время работы/>

Почему это представляет угрозу безопасности?

  • Серверная часть доверяет данным из внешнего интерфейса, которыми можно легко манипулировать.
  • В сеансе сохраняется неверная цена, что приводит к финансовым потерям.
  • Злоумышленники могут покупать дорогие продукты по чрезвычайно низким ценам, изменив данные запроса.
<время работы/>

Вопросы для обсуждения сообществом:

  • Каковы наилучшие методы предотвращения этого?
  • Должны ли сайты электронной коммерции всегда получать цены из базы данных вместо того, чтобы принимать их из внешнего интерфейса?
  • О каких еще уязвимостях следует знать разработчикам при обработке данных корзины в Django?

Хотелось бы услышать ваши мысли по этому поводу!

Каковы наилучшие методы предотвращения этого?

Цена вам не нужна, в представлении должно быть добавлено product_id в корзину и, возможно, quantity, но добавление чего-либо в корзину не связано с ценой. Это даже усложняет последующее применение скидок, поскольку цена определяется за товара.

Должны ли сайты электронной коммерции всегда получать цены из базы данных вместо того, чтобы принимать их из внешнего интерфейса?

Как таковой, нет, есть несколько API, которые определяют цены на лету. Боты, которые таким образом повышают цену, если есть больший спрос или если конкурент снижает свои цены.

О каких еще уязвимостях следует знать разработчикам при обработке данных корзины в Django?

Это не имеет никакого отношения к корзине, вы всегда спрашиваете пользователя об абсолютном минимуме, который вам нужно знать, и намеренно ограничиваете параметры, поскольку это ограничивает то, что вы можете изменить.

Я видел несколько вопросов по StackOverflow с похожим подходом. Иногда они определяют класс Cart, который затем обрабатывает данные сеанса. Но это действительно был плохой дизайн, и часто не только с точки зрения уязвимости в системе безопасности, но и с точки зрения производительности, целостности ссылок и т.д.: вы добавляете товар в корзину с помощью запроса GET, что не имеет смысла; и вы даже можете добавить несуществующий товар.

Запрос GET, таким образом, означает, что если пользователь, отправляющий запрос, нажимает кнопку обновить, он выполняется во второй раз. Но что еще более важно, запросы GET должны быть кэшируемыми, что явно не так, и это также помещает product_idprice) в URL, что означает, что они, по крайней мере, также видны в path, что не очень хорошо практикуйтесь в любом случае.

У меня всегда было впечатление, что кто-то выложил на Youtube "руководство по электронной коммерции на Django", и люди скопировали какой-то код. Но, по-видимому, кто-то действительно перенес это в производство.

Чтобы должным образом использовать функциональность корзины и предотвратить изменение цен пользователями, важно надежно хранить данные о корзине в базе данных, а не полагаться на вводимые пользователем данные.

Тележечный стол

В этой таблице отслеживаются пользовательские корзины, независимо от того, связаны ли они с аутентифицированным пользователем или анонимным сеансом.

  • id ( Первичный ключ)

  • user ( Внешний ключ для User, может быть обнулен для гостевых пользователей)

  • session ( Внешний ключ для Session, значение которого может быть обнулено для прошедших проверку подлинности пользователей)

  • created_at ( Отметка времени создания корзины)

  • is_available ( Логическое значение для обозначения активных/неактивных тележек)

Таблица продуктов

Здесь хранятся сведения о продукте, включая цену, которые всегда необходимо извлекать из базы данных.

  • id ( Первичный ключ)

  • name ( Название продукта)

  • price ( Положительное целое число для хранения цены)

  • category ( Внешний ключ в отдельную таблицу категорий для лучшей нормализации)

Таблица карточек

В этой таблице товары сопоставляются с корзиной и обеспечивается целостность цен.

  • id ( Первичный ключ)

  • cart ( Внешний ключ для Cart, связывающий товары с корзиной)

  • product ( Внешний ключ для Product, обеспечивающий согласованность цен)

  • quantity ( Для отслеживания количества товаров)

вот пример кода:

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)

Примечания:

  • В таблице корзин как сеанс, так и пользователь могут быть обнулены для поддержки как гостевых, так и аутентифицированных корзин.

  • Лучше всего создать отдельную таблицу категорий и ссылаться на нее с помощью внешнего ключа для лучшей нормализации базы данных.

После разработки надежной структуры базы данных требуется только отправить идентификатор продукта серверной части. Затем серверная часть добавит товар в корзину пользователя или сеанса, создав новую корзину, если таковой еще не существует.

Вернуться на верх