Райтап на задачу «Безвыигрышная лотерея»

velvetway

Автор:

velvetway

25 октября 2026

30

25.10.2026
Безвыигрышная лотерея
Категория:

Web

Задача «Безвыигрышная лотерея»

Автор:

LastTeamLead

Первая кровь:
skydaemon
Сложность:
easy
Дата публикации:
17.01.2026
Решений:
71
Рейтинг:
4.8/5
Ссылка:
Решать

Запускаем персональный инстанс. Нас встречает Пикси и просит представиться для участия в лотерее.

Стартовая страница лотереи

Стартовая страница лотереи

Сделаем это и сразу получим результаты лотереи.

Результат лотереи

Результат лотереи

Мы проиграли. Но не расстраиваемся и попробуем обновить страницу ещё несколько раз. Каждый раз мы получим уже новое число, но победы так и не дождёмся :(.

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

Логика проверки выигрыша в game.php

Логика проверки выигрыша в game.php

Здесь и видно, как определяется победа:

$rnd = mt_rand(1, 100) + 1; // число от 2 до 101
$isLose = (!empty($user) && $user['is_win'] !== $rnd);
$isWin = !$isLose;

Во-первых, выпавшее число всегда попадает в диапазон от 2 до 101, а наш is_win всегда равен 1. Совпасть они не могут в принципе.

Во-вторых, проигрыш засчитывается только при условии !empty($user). То есть если $user окажется пустым, первое условие рушится, $isLose становится false, и мы выигрываем. А $user — это результат json_decode(user_data). Значит, наша цель — сделать так, чтобы json_decode вернул null. Для этого изучим код index.php — той самой страницы, где мы вначале вводили свой юзернейм.

Страница ввода юзернейма

Страница ввода юзернейма

В index.php при регистрации наше имя попадает в JSON-строку и пишется в базу:

$userData = json_encode([ 'user' => $username, 'is_win' => 1 ], JSON_UNESCAPED_UNICODE);

Остаётся понять, как испортить сохранённый JSON. Смотрим на структуру таблицы в init.sql:

Структура таблицы users (init.sql)

Структура таблицы users (init.sql)

Колонка user_data объявлена как VARCHAR(255). То есть строка не должна быть длиннее 255 символов, но что, если мы захотим ввести её больше этого лимита? Чтобы это узнать, изучим также файл настройки самой БД — custom.cnf:

Настройка sql_mode (custom.cnf)

Настройка sql_mode (custom.cnf)

В sql_mode перечислены:

  • ERROR_FOR_DIVISION_BY_ZERO (ошибка при делении на ноль)

  • NO_AUTO_CREATE_USER (не создавать пользователей через GRANT)

  • NO_ENGINE_SUBSTITUTION (не подменять недоступный движок таблиц)

Но ни один из них не включает строгую проверку длины: для этого нужен STRICT_TRANS_TABLES, а его здесь нет. Загуглим, что случится, если его нет.

Поведение без strict-режима

Поведение без strict-режима

Получается, слишком длинное значение не вызывает ошибку, а молча обрезается до 255 символов. Значит, если наше имя сделать достаточно длинным, JSON {"user":"...","is_win":1} перевалит за лимит и сохранится обрезанным посередине — без закрывающей кавычки и скобки. Такой JSON будет невалиден, и функция json_decode вернёт null, а $user окажется пустым, и лотерея засчитает выигрыш.

Возьмём любое большое имя, чтобы перейти через эту границу.

Клиентская валидация ограничивает длину имени

Клиентская валидация ограничивает длину имени

Клиентская валидация не даст сделать это так просто — ограничит примерно 50 символами. Но проверим, аналогична ли ей серверная. Для этого отправим запрос напрямую через Burp Repeater.

Отправка длинного имени через Burp Repeater

Отправка длинного имени через Burp Repeater

В ответ получим код 302, который говорит нам о перенаправлении. Нажмём на кнопку Follow Redirection, и нас перенаправит на страницу лотереи, на которой мы успешно победили.

Лотерея засчитала выигрыш

Лотерея засчитала выигрыш

В чём уязвимость?

Суть уязвимости — это доверие к клиентской валидации и отсутствие серверной проверки длины имени. Из-за нестрогого режима БД переполненная строка обрезается и ломает сохранённый JSON, а приложение на битых данных ошибочно засчитывает выигрыш. Если бы валидация сервера была аналогична клиентской, то выиграть в такой лотерее не получилось бы.

В OWASP Top 10 этой уязвимости соответствует A06:2025 — Insecure Design: доверие к клиентской валидации и логика, которая на повреждённых данных засчитывает выигрыш. По CWE это CWE-602 (перенос серверной проверки на клиент).

Если вы нашли опечатку или ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter

Также по теме:

Райтап на задачу «Просмотр файлов»

/Разборы задач

Райтап на задачу «Просмотр файлов»

Райтап feadal на задачу «Просмотр файлов»: проверка пути через startswith, поведение os.path.normpath и обход запрета на чтение файла.
Прочитать

10

Райтап на задачу «Десериализатор»

/Разборы задач

Райтап на задачу «Десериализатор»

Райтап feadal на задачу «Десериализатор»: небезопасная десериализация в PHP, приватные свойства и получение флага через PHP Object Injection.
Прочитать

9