Гоняю по вечерам OWASP Juice Shop - магазинчик, специально напичканный дырами для тренировки. Взялся за челлендж «Register Admin» - зарегистрировать юзера с правами администратора.
Точка входа 🔍
Регистрация идёт через авто-сгенерённый CRUD - POST /api/Users. Рука сама тянется подкинуть лишнее поле в JSON:
JSON
{ "email": "x@y.z", "password": "123", "role": "admin" }
Сервер честно кладёт всё тело запроса в модель. Вайтлиста нет. Клиент попросил роль - клиент получил роль. Классика mass assignment: доверять инпуту от юзера - как оставлять шелл с root на проде.
Бам - админка за один запрос. 💥
Бонус-раунд: выбери правильный фикс 🎯
К задачке есть задачка посложнее - найти строку с багом и выбрать, как её починить. Виноват хук resource.create.send.before(...): последнее место, где можно перетереть роль до сохранения, - пропущено.
Фиксы:
❌ Добавить 'role' в exclude - обманка. В finale это работает на сериализацию ответа, а не на запись. Баг живее всех живых.
✅ Форсить роль на сервере: context.instance.role = 'customer' - критичные поля выставляет только бэк. Правильный фикс.
❌ «Если роль пришла - оставь как есть» - это не фикс, это официальное сохранение уязвимости 🍬
❌ Удалить несвязанный код - remediation by deletion, мимо кассы.
Выводы 💻
Увидел авто-CRUD (finale, loopback) - шли всё, что модель не должна принимать: role, isAdmin, balance.
«Не возвращается в ответе» ≠ «нельзя записать».
Исправление одно: критичные поля задаёт только сервер. Период.
Админы, которые думают, что их код безопасен, - мой любимый вид дичи 🦌