![]() |
https://forum.antichat.xyz/attachmen...1533981870.png
Суть проста: ты отправляешь JSON на сервер, а он автоматически маппит все поля на модель. И если разработчик забыл поставить фильтр, любое лишнее поле - например, "is_admin": true - становится частью твоей учётной записи. И вот ты уже не просто пользователь, а администратор. Или можешь менять цены. Или читать чужие данные. В общем, полный фарш. Казалось бы, в 2026 году об этом должны знать все. Но нет. Баг-баунти платформы ломятся от таких отчётов, а крупные проекты продолжают платить за эту детскую ошибку. И дело не в глупости разработчиков, а в системных причинах: наследие, скорость, недостаток знаний, слепая вера в автоматизацию. В этой статье мы разберём масс-ассигнмент под микроскопом. Мы покажем, как его искать на разных языках и фреймворках, какие инструменты юзать, чтобы фаззить параметры, и как от него защищаться так, чтобы спать спокойно. Поговорим о вложенных атрибутах, JSONB полях, GraphQL мутациях - обо всех тёмных уголках, где может прятаться эта зараза. Глава 1. Исповедь бывшего админа, или Как я перестал бояться и полюбил блек-лист Знаешь, в чём главная причина масс-ассигнмента? В том, что разработчики мыслят в категориях "удобство кода", а не "безопасность данных". Когда ты пишешь User.create(params[:user]), тебе кажется, что это красиво, лаконично, по-рельсовому. Ты не думаешь о том, что клиент может прислать что-то лишнее. Ты доверяешь клиенту. А доверять клиенту в вебе - последнее дело. Вторая причина - фреймворки сами провоцируют такое мышление. Они рекламируют "convention over configuration", "scaffolding", "rapid development". И это круто для прототипов. Но когда прототип становится продакшеном, а код остаётся тем же, начинаются проблемы. Разработчики просто забывают выключить "режим бога". Третья причина - отсутствие культуры code review и тестирования безопасности. Если в команде нет человека, который хоть раз в жизни видел масс-ассигнмент, он пройдёт код ревью. Все посмотрят на красивый контроллер и скажут: "Вау, как чисто! Мержим". Эпидемия масс-ассигнмента в 2010-х В начале 2010-х, когда Rails был на пике популярности, масс-ассигнмент был буквально эпидемией. Каждый второй стартап на Rails имел эту дыру. Rails достаточно быстро среагировал. В версиях 2.x и 3.x появились методы attr_accessible и attr_protected в моделях.
Ruby: Код:
classRuby: Код:
UserАнатомия масс-ассигнмента: как это работает под капотом Чтобы понять уязвимость, нужно понять механику. Когда клиент отправляет HTTP-запрос (например, PUT /api/users/123) с телом в формате JSON, сервер выполняет следующие шаги:
Почему это фича (и очень удобная) Давай честно: масс-ассигнмент придумали не злые хакеры, а умные разработчики фреймворков, чтобы упростить жизнь другим разработчикам. Вот его основные плюсы:
Почему это баг (когда забыли про безопасность) Масс-ассигнмент становится уязвимостью, когда он позволяет изменить поля, которые не должны быть доступны клиенту. Это нарушение принципа наименьших привилегий на уровне данных. Пользователь должен иметь возможность менять только те поля, которые ему разрешены (например, email, пароль, имя), а не все подряд (роль, права доступа, внутренние флаги). Вот типичные сценарии, где это выстреливает: 1. Повышение привилегий (Privilege Escalation) Самый классический случай. Поле is_admin, role, group_id - если его можно изменить через API, пользователь становится администратором. 2. Изменение финансовых параметров Поля balance, discount, price, commission. Представь, что в интернет-магазине можно изменить цену товара в корзине перед покупкой. Или в банковском приложении - уменьшить комиссию за перевод. 3. Доступ к чужим данным (IDOR + Mass Assignment) Иногда масс-ассигнмент позволяет изменить не только свои данные, но и чужие, если есть поле вроде user_id. Например, при создании комментария можно подставить чужой user_id и комментарий запишется от имени другого пользователя. 4. Изменение системных флагов Поля verified, email_confirmed, active, banned. Если хакер может сам себя верифицировать или разбанить - это проблема. 5. Модификация защищённых атрибутов Например, поле encrypted_password - обычно не должно меняться напрямую, только через специальный метод. Но если оно доступно для масс-ассигнмента, можно установить свой хеш пароля и зайти без ведома системы. Классификация масс-ассигнмента (чтобы знать врага в лицо) Масс-ассигнмент бывает разный, и не все его виды одинаково очевидны. Давай разложим по полочкам. 1. Прямой масс-ассигнмент Самый простой и распространённый. Когда поля модели напрямую маппятся из запроса. Пример: отправили {"name": "Hacker", "is_admin": true} - и оба поля применились. Ruby: Код:
# RailsКод:
// LaravelКод:
# Django REST FrameworkЭто когда через масс-ассигнмент можно менять не только атрибуты самой модели, но и связанные с ней объекты. Например, у пользователя есть профиль. Если в модели разрешены accepts_nested_attributes_for rofile (Rails) или аналоги, можно отправить: JSON: Код:
{В Laravel это называется "отношения" и массовое присвоение тоже работает, если разрешить. 3. Масс-ассигнмент через параметры с точкой (dot notation) В некоторых фреймворках (особенно старых или при кастомном парсинге) параметры вида user[admin] преобразуются во вложенный хеш. Если бэкенд ожидает плоский JSON, а ты отправляешь form-data с такими ключами, может сработать. Например, в PHP суперглобальный массив $_POST превращает user[admin] в $_POST['user']['admin']. Если разработчик использует extract() или просто подставляет такие значения, возможна атака. 4. Масс-ассигнмент в связках many-to-many Когда есть связь многие-ко-многим, часто можно передать массив идентификаторов. Например, у пользователя есть группы. Если разрешено массовое присвоение для group_ids, можно добавить себя в группу администраторов, отправив {"group_ids": [1, 2, 3]}. JSON: Код:
{Современные базы данных поддерживают JSON-типы. В модели может быть поле settings типа JSONB. Если оно доступно для масс-ассигнмента, можно записать туда что угодно, включая потенциально опасные данные. Например: JSON: Код:
{6. Масс-ассигнмент через кастомные сеттеры В моделях часто определяют кастомные методы-сеттеры. Например, в Rails: Ruby: Код:
def7. Непрямой масс-ассигнмент через параметры запроса Иногда API использует query parameters для обновления (хотя это не REST-стайл). Например: Код: Код:
PUT /api/users/123?email=new@test.com&is_admin=trueМасс-ассигнмент при создании (create) и обновлении (update) Важно понимать, что уязвимость может быть как при создании новых записей, так и при обновлении существующих.
Масс-ассигнмент и другие уязвимости: гремучая смесь Масс-ассигнмент редко ходит один. Часто он комбинируется с другими дырами, усиливая их. + IDOR (Insecure Direct Object References) Если есть масс-ассигнмент и можно изменить поле user_id в каком-то объекте, то это уже IDOR: ты можешь присвоить объект другому пользователю. + NoSQL инъекции В MongoDB если передать в запросе операторы типа $set, $inc, и бэкенд не фильтрует ключи, можно выполнить произвольные операции. Это уже не совсем масс-ассигнмент, но похоже. + SQL инъекции через масс-ассигнмент Бывает редко, но если поле маппится на имя столбца без экранирования, теоретически возможно. Но это экзотика. Почему фреймворки не защищают по умолчанию? Хороший вопрос. Казалось бы, сделай по умолчанию белый список пустым - и никаких проблем. Но тогда сломается всё удобство масс-ассигнмента. Разработчику пришлось бы каждый раз явно перечислять все поля, даже для простых форм. Это бесит. Поэтому фреймворки идут на компромисс: они предлагают механизмы защиты (strong parameters, fillable, DTO), но не включают их насильно. Разработчик сам должен их применить. А если не применил - получает дыру. Масс-ассигнмент в дикой природе: что говорят цифры По данным OWASP, масс-ассигнмент (иногда называют "Mass Assignment" или "Auto-binding") входит в различные списки рисков для API. В OWASP API Security Top 10 есть отдельная категория - "Mass Assignment" (API8:2019 - Mass Assignment). В 2023 году она трансформировалась, но суть та же: небезопасное массовое присвоение остаётся проблемой. Статистика баг-баунти платформ показывает, что масс-ассигнмент стабильно входит в топ-10 уязвимостей по частоте. Особенно много его в:
Эпоха "дикого запада" (Rails 1.x - 2.x) Ruby on Rails популяризировал концепцию масс-ассигнмента. В ранних версиях не было никакой защиты. Любой атрибут модели можно было изменить через params. Это было удобно для быстрого прототипирования, но для продакшена - ад. Инцидент с Github (2012) Знаменитый случай: в марте 2012 года хакер Хомза (Homakov) нашёл уязвимость в Github, связанную с масс-ассигнментом. Он отправил запрос на изменение публичного ключа, добавив поле public, которое позволяло сделать ключ публичным, хотя должно было быть приватным. Github использовал Rails 3.0.x, где ещё не было strong parameters по умолчанию. Хомза не просто нашёл баг, он устроил дискуссию: "Почему фреймворк позволяет такое?" И это привело к тому, что в Rails 3.1 появился механизм attr_accessible и attr_protected, а позже - strong parameters в Rails 4. Но это было только начало. Волна по другим фреймворкам После Rails все подтянулись. Laravel ввёл $fillable и $guarded. Django REST Framework (DRF) требует явно указывать поля в сериализаторах. Spring (Java) - там всё сложнее, потому что используется десериализация Jackson'ом, и защита ложится на DTO или аннотации. Но, чёрт возьми, сколько ещё легаси-проектов живёт с открытыми ранами? Я сам на баг-баунти нахожу такие вещи до сих пор. Глава 4. Технические детали: разбор на примерах Давай залезем в дебри и посмотрим, как масс-ассигнмент выглядит на разных языках. Это важно, потому что если ты понимаешь, как это работает под капотом, тебе легче найти дыру и легче её закрыть. 1. Ruby on Rails (самое мяско) [B]Уязвимый код (Rails update ( $request - > all ( ) ) ; // Всё, что в запросе, идёт в модель return $user ; } [/CODE] $request-all() - это все входные данные, включая is_admin, если он есть в JSON. Защита: в модели нужно определить $fillable (белый список) или $guarded (чёрный список). PHP: Код:
class3. Django REST Framework (Python) DRF - интересный случай. Там сериализаторы явно определяют поля. Уязвимый код (если сериализатор слишком широкий): Python: Код:
classЗащита: указать конкретные поля или использовать read_only_fields. Python: Код:
class4. Spring Boot (Java) Здесь чаще всего используется Jackson для десериализации JSON в объекты. Уязвимый код: Java: Код:
@PutMappingЗащита: Использовать DTO (Data Transfer Objects) с ограниченным набором полей. Java: Код:
publicJava: Код:
@JsonIgnorePropertiesMongoose - ODM для MongoDB. У него тоже есть масс-ассигнмент. Уязвимый код: JavaScript: Код:
appЗащита: использовать select: false в схеме для чувствительных полей или явно указывать разрешённые поля. JavaScript: Код:
constJavaScript: Код:
constВ Go всё более явно, но тоже можно наступить на грабли. Уязвимый код: Код: Код:
// goКод: Код:
// gohttps://forum.antichat.xyz/attachmen...1534606185.png Глава 5. Практические инструменты для поиска уязвимостей Теперь самое вкусное: как находить эти дыры? Мы же с тобой не просто теоретики, а практики. Давай разбирать инструментарий. 5.1. Burp Suite - швейцарский нож Burp Suite - это маст-хэв для любого, кто шатает веб. Нас интересует вкладка Repeater и Intruder. Repeater: Ловишь запрос на обновление профиля, отправляешь в Repeater и начинаешь добавлять поля.
Burp Scanner тоже иногда находит такое, но он туповат. Лучше вручную. 5.2. OWASP ZAP - бесплатный аналог Если у тебя нет денег на Burp Pro, ZAP - отличный выбор. У него есть активный сканер, который можно настроить на фаззинг параметров. Или используй Fuzzer вручную. 5.3. Командная строка: curl + jq Для любителей консоли. Базовый запрос: Bash: Код:
curlBash: Код:
curlАвтоматизация в bash: Bash: Код:
forКогда нужно просканировать много эндпоинтов или параметров, пишем скрипт. Пример простого фаззера: Python: Код:
import5.5. ffuf - быстрый фаззер на Go ffuf изначально для директорий, но отлично фаззит и параметры. Bash: Код:
ffuf -u https://example.com/api/users/1235.6. Анализ документации (Swagger, Postman) Самое смешное: разработчики сами дают нам карту сокровищ. Открываешь Swagger UI, смотришь схему модели. Там написаны все поля, включая те, что не должны быть доступны. Если в схеме есть is_admin и нет пометки readOnly, значит, можно попробовать. В Postman тоже можно посмотреть примеры запросов и ответов. Часто в ответе приходит куча полей, и хакер думает: "А почему бы не попробовать отправить их обратно?". 5.7. Браузерные расширения
Если API используется в мобильном приложении, можно поставить прокси (Burp) на эмулятор и смотреть трафик. Там те же уязвимости. Глава 6. Как защититься: белый и чёрный списки Теперь поговорим о защите с точки зрения разработчика. И сразу скажу: чёрный список - зло. Почему? Потому что ты никогда не знаешь, какие поля добавят завтра. Если ты запретил is_admin, но потом ввели поле is_superuser, ты про него забудешь, и хакер его отправит. Белый список - наше всё. Ты явно перечисляешь, что можно менять. Всё остальное игнорируется. Подходы:
Python (DRF) Python: Код:
classJava: Код:
publicPHP: Код:
$requestJavaScript: Код:
constМожно подключить middleware, который проверяет входящий JSON по OpenAPI схеме. Например, в Node.js есть express-openapi-validator. Если в схеме операции не указано поле is_admin, запрос с ним будет rejected. Важный момент: ошибки валидации Если ты игнорируешь лишние поля, не возвращай ошибку. Просто не применяй их. Возвращать 400 Bad Request с текстом "Unknown field" - это хорошо, но может раскрыть структуру. Иногда лучше молча пропустить. Но для безопасности лучше явно отвергать. Глава 7. Продвинутые техники атак Когда базовый is_admin: true уже не проходит, хакеры (и мы с тобой) начинаем изощряться. 7.1. Вложенные атрибуты (nested attributes) Допустим, у пользователя есть профиль с отдельной таблицей profiles. И в модели User есть accepts_nested_attributes_for rofile (Rails) или аналоги в других фреймворках. Тогда можно отправить: JSON: Код:
{Или атака на принадлежность: например, можно изменить user_id в комментарии через вложенные атрибуты. 7.2. Параметры с точкой В некоторых фреймворках (например, старых версиях Rails или при кастомном парсинге) параметры вида user[admin] могут быть преобразованы во вложенный хеш. Если бэкенд ожидает плоский JSON, а ты отправляешь form-data с такими ключами, может сработать. 7.3. Типизированные поля Если поле ожидает число, а ты отправляешь строку - может быть приведение типов. Или наоборот, если поле ожидает массив, а ты отправляешь объект. Иногда это приводит к неожиданным последствиям. 7.4. JSONB поля в PostgreSQL В PostgreSQL есть тип JSONB. Если модель содержит поле metadata типа JSONB, и разработчик разрешил его массовое присвоение, ты можешь запихать туда что угодно. Но это скорее риск неправильной валидации внутри JSONB. 7.5. GraphQL мутации В GraphQL масс-ассигнмент может проявиться иначе. Если мутация принимает объект с полями, и резолвер просто передаёт их в модель, то злоумышленник может запросить мутацию с полем, которого нет в схеме? Нет, схема GraphQL строгая: если поле не объявлено во входном типе, его не отправить. Но если входной тип содержит поле, которое не должно быть доступно для записи - это проблема дизайна. Например, тип UpdateUserInput включает поле isAdmin, и клиент его указывает. Глава 8. Реальные кейсы из практики Я тут припомню несколько историй, которые либо случались со мной, либо я слышал от коллег по цеху. Имена изменены, детали изменены, но суть та же. Кейс 1: Социальная сеть и права модератора Соцсеть для фотографов. У них были группы, и в группе были роли: участник, модератор, администратор. Роль хранилась в модели Membership как поле role (string). Я вступил в группу, перехватил запрос на обновление членства (например, изменение уведомлений). Добавил "role": "admin". Запрос прошёл, и я стал админом группы. Потом мог удалять других участников. Это классика. Кейс 2: Интернет-магазин и отрицательные цены Не совсем масс-ассигнмент, но близко. В корзине можно было менять количество товара. Я отправил "quantity": -1, и в ответ получил отрицательную сумму заказа. Потом оформил заказ с отрицательной ценой и должен был получить деньги? Но там были защиты на оплате, но факт. Кейс 3: API для чата и флаг "невидимка" В одном мессенджере была функция "невидимка" (invisible). Обычный пользователь не мог её включить. Но при обновлении профиля было поле invisible. Я отправил "invisible": true, и стал невидим для других. Разработчики просто забыли убрать это поле из массового присвоения. Глава 9. Инструменты для защиты Мы поговорили об атаках, теперь про защиту с точки зрения DevOps и разработчика. Какие инструменты помогут автоматически ловить такие проблемы? 9.1. Статический анализ кода (SAST)
9.2. Динамический анализ (DAST)
9.3. Линтеры API схем Если у тебя есть OpenAPI спецификация, можно валидировать входящие запросы на соответствие схеме. Есть middleware для разных языков:
Пиши тесты, которые проверяют, что лишние поля не влияют. RSpec (Rails): Ruby: Код:
itPHP: Код:
publicНекоторые продукты (например, Wallarm, Imperva) предлагают защиту API на уровне WAF, которая может блокировать подозрительные поля. Но это платно. 9.6. Логирование и мониторинг Если кто-то пытается отправить неожиданное поле, логируй это. Можно настроить алерт на попытки масс-ассигнмента. Например, если в логах появилось предупреждение от strong_params об отброшенных параметрах, это повод проверить, не атака ли это. Глава 10. Философия: почему это до сих пор случается? Тут мы подходим к самому важному. Почему в 2026 году, когда все уже знают про mass assignment, он всё ещё встречается? Причины, как мне кажется, в следующем:
Когда находишь масс-ассигнмент, не надо сразу сливать данные или повышать себе права. Надо ответственно сообщить об этом. Команда разработчиков, скорее всего, даже не подозревает о проблеме. У них нет злого умысла, они просто ошиблись. Я всегда стараюсь в отчёте объяснить не только "что", но и "почему" и "как исправить". Даже пример кода привожу. Это вызывает уважение и повышает шансы на баунти. И ещё: не будь циником. Да, разработчики тупят. Но и мы тупим. Глава 12. Заключение: будь проще, и люди к тебе потянутся, но не упрощай безопасность Ну что, народ, доползли. Я специально не стал делать заключение коротким «спасибо за внимание», потому что тема mass assignment - это не просто очередная строчка в чек-листе OWASP Top 10. Это, блин, философия. Это лакмусовая бумажка, показывающая, насколько разработчик (или команда) вообще задумывается о том, что приходит к ним в сервер с другой стороны провода. Давай ещё раз пробежимся по тому, что мы тут накопали, но уже с высоты птичьего полёта. Масс-ассигнмент как зеркало архитектуры Когда я вижу в коде User.update(params) без всяких фильтров, я понимаю: здесь либо спешка, либо непонимание, либо лень. Но чаще всего - просто инерция мышления. Фреймворк дал удобную штуку, разработчик ей радуется и не задумывается, что любой школьник с Postman’ом может стать админом. И вот тут начинается самое интересное. Масс-ассигнмент - это симптом более глубокой проблемы: отсутствия границ между слоями приложения. Если ты тащишь сырой HTTP-запрос прямо в модель, значит, у тебя нет чёткого разделения ответственности. Контроллер должен заниматься только тем, чтобы вытащить нужные данные из запроса, а модель - сохранить их. А кто их фильтрует? А никто. Или фильтрует сама модель через fillable. Но модель не знает контекста: может, сегодня это поле можно менять, а завтра - нельзя. Поэтому я всё чаще склоняюсь к тому, что DTO (Data Transfer Objects) - это не оверхед, а спасение. Да, придётся писать чуть больше кода. Да, иногда кажется, что это скучно. Но когда ты явно описываешь, какие поля ожидаешь в запросе на обновление профиля, ты автоматически решаешь кучу проблем: и масс-ассигнмент, и валидацию, и документирование API. Плюс, если ты используешь статическую типизацию (Go, Java, TypeScript), компилятор тебе подскажет, если ты вдруг решил использовать несуществующее поле. Почему автоматические сканеры не панацея Мы обсудили инструменты: Burp, ZAP, фаззеры. Они крутые, но они - костыли. Они находят уже существующие дыры. А вот чтобы дыр не было, нужно думать головой. Сканер может не заметить вложенный масс-ассигнмент или атаку через JSONB-поле. Он может не понять контекст: например, поле role вроде бы должно быть недоступно, но в админке оно нужно. Сканер просто пошлёт role: "admin" и, если ему вернётся 200, заорёт «вульна!». А на самом деле там может быть проверка прав доступа, которая запретит это для обычного юзера. Так что автоматика - это помощник, а не замена мозгу. Представь себе разработчика Петю. Он получил от тебя отчёт о масс-ассигнменте. У Пети дедлайн через два дня, легаси-код, тесты не проходят, а тут ещё ты со своим is_admin. Петя в стрессе. И если ты просто кинешь «у вас дыра, чините», Петя, скорее всего, залатает на скорую руку (добавит поле в guarded и забудет), а потом уйдёт в запой. А если ты подробно объяснишь, в чём проблема, и предложишь решение (например, использовать DTO или хотя бы fillable), Петя не только починит, но и сам будет знать, как в будущем не наступать на те же грабли. Ты можешь быть жёстким, но справедливым. Ты можешь иронизировать над косяками, но не унижать. Потому что завтра ты можешь оказаться на месте Пети. Что дальше? Эволюция масс-ассигнмента Пока мы тут расслабляемся, масс-ассигнмент мутирует. GraphQL, например, формально защищает от него схемой, но если входной тип слишком широкий, проблемы остаются. Например, мутация updateUser принимает тип UserInput, в котором есть поле isAdmin. Если разработчик не пометил его как @deprecated или не убрал из схемы, клиент сможет его изменить. В GraphQL это называется «over-fetching» наоборот - «over-mutation». Ещё одна эволюционная ветка - AutoREST фреймворки, которые генерируют API прямо из базы данных (например, PostgREST, Hasura, Firebase). Там вообще вся безопасность строится на правах доступа к колонкам. Если ты не настроил права на колонку is_admin, она будет доступна на запись. И вот тут масс-ассигнмент становится просто следствием неправильной конфигурации. Или NoSQL инъекции через масс-ассигнмент? Бывает. В MongoDB, если ты передаёшь {"$set": {"admin": true}}, а бэкенд не фильтрует операторы, можно выполнить произвольную операцию. Но это уже другая история. Моё личное напутствие Я думал, что безопасность - это про сложные шифры и файрволы. А оказалось, что самая опасная уязвимость - это просто отсутствие фильтрации. Простота и удобство фреймворков сыграли с нами злую шутку: мы перестали думать о том, что приходит в запросе. Мы привыкли доверять. Но хакер внутри нас (а он есть в каждом, кто дочитал до сюда) знает: доверять нельзя никому, особенно данным от клиента. Каждый JSON, каждый параметр - это потенциальная пуля. Наша задача - не бояться, а контролировать. Давай договоримся: после прочтения этой статьи ты пойдёшь и проверишь свой проект. Или, если ты только учишься, напишешь игрушечное приложение и специально оставишь там масс-ассигнмент, чтобы проэксплуатировать и понять, как это работает. Только так, через собственные грабли, приходит понимание. Мы с тобой одной крови. Мы любим копаться в деталях, писать код и иногда ломать его. И пусть наши API будут крепкими, а баунти - жирными. |
| Время: 07:04 |