Райтап на задачу «Шакализатор картинок»

velvetway

Автор:

velvetway

27 октября 2026
Шакализатор картинок
Категория:

Web

Задача «Шакализатор картинок»

Автор:

LastTeamLead

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

Перейдём на страницу сайта. Нас встретит ряд фотографий с Пикси и разными шакалами.

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

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

По условию таска оригинальные фото Пикси были слишком большого размера и поэтому «на бэкенде был добавлен шакализатор картинок». Разберёмся, как же подгружаются эти фото на сайт. Для этого откроем DevTools.

Запросы страницы в DevTools (вкладка Сеть)

Запросы страницы в DevTools (вкладка Сеть)

Выберем любое из фото и перейдём на его прямой URL.

Прямой URL картинки через роут ресайза

Прямой URL картинки через роут ресайза

URL будет состоять из следующих частей:

1. resize

Сам эндпоинт ресайзера. Это просто имя роута, менять в нём нечего, так что для нас он малоинтересен.

2. 1248x832

Нужный размер. Можем попробовать его поменять и получить разные интересные варианты картинки.

Меняем размер в URL и картинка ресайзится по-другому

Меняем размер в URL и картинка ресайзится по-другому

Касательно ресайза картинки тоже существует интересная уязвимость — CWE-400, но она влияет на доступность сайта и в контексте данной задачи нас интересовать не будет.

3. upload/pixie-1.jpg

Непосредственно путь к исходному файлу. Вот эта часть интересует нас больше всего: путь подставляется в URL напрямую, попробуем загуглить, какая уязвимость актуальна при таком подходе.

Path Traversal (CWE-22)

Path Traversal (CWE-22)

Теперь мы знаем, что она называется Path Traversal (CWE-22), а значит, мы можем попробовать подсунуть свой URL и выйти за пределы папки upload.

Классическая конструкция для Path Traversal — это ../. Чтобы понять, почему она работает, вспомним, как устроена файловая система. Большинство веб-серверов работает на Linux, где файлы и папки организованы в виде дерева каталогов, а переходы между ними записываются через слеш (/).

Специальная последовательность .. говорит серверу, что надо подняться на уровень выше. То есть из каталога upload с помощью ../ можно выйти в корень приложения, а оттуда и добраться до служебных файлов с секретами. Если путь никак не фильтруется, то сервер просто отдаст их.

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

Структура проекта в исходниках (ls -la)

Структура проекта в исходниках (ls -la)

Стоит отметить, что .env — это так называемый dotfile (файл с точкой в начале имени). В Unix-подобных системах такие файлы по умолчанию скрыты при обычном просмотре каталога, поэтому их не видно в списке и нельзя наткнуться случайно. Но это лишь визуальное сокрытие: если знать имя файла, он читается как любой другой. Чтобы увидеть такие файлы, используем флаг -a.

На файл .DS_Store (Desktop Services Store) не обращаем внимания, это скрытый системный файл, который операционная система macOS автоматически создаёт в каждой папке, к которой обращался файловый менеджер Finder.

Загуглим, что такое файл .env.

Что такое файл .env

Что такое файл .env

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

Флаг в .env из исходников (значение-заглушка)

Флаг в .env из исходников (значение-заглушка)

Теперь мы уверены, что флаг лежит в файле .env. Попробуем прочитать флаг на работающем инстансе, используя Path Traversal. Для этого изменим URL до следующего вида /resize/100x100/../.env, где 100x100 — произвольный размер, а вместо имени картинки мы через ../ выходим из папки upload к файлу .env в корне проекта.

Запрос с ../ через роут /resize/ → 400 от nginx

Запрос с ../ через роут /resize/ → 400 от nginx

Но вместо содержимого получим ошибку 400. Загуглим, когда она возникает.

Когда возникает ошибка 400

Когда возникает ошибка 400

Так как ошибок мы не допускали, а тот же роут прекрасно работает для нормального пути к картинке, что мы подтвердили ранее, то можем предположить, что наш запрос не пропустился из-за несоответствия требованиям сервера. Если хотим убедиться в этом наверняка, то можем обратиться к коду resize.php и увидеть, что для ошибки 400 там предусмотрена своя ошибка, до которой мы просто не проходим из-за nginx.

Своя ошибка 400 в resize.php (Bad resize request)

Своя ошибка 400 в resize.php (Bad resize request)

Теперь разберёмся, почему нас не пропускает nginx. Заглянем в сам nginx.conf.

nginx.conf: правила для роута /resize/

nginx.conf: правила для роута /resize/

Теперь мы видим, что для роута /resize/ там прописано сразу несколько ограничений:

if ($request_uri ~* "^/resize/.*(?:%2e|%2f|%5c|%00|%25)") {
    return 400;
}

Правило, возвращающее 400, если в пути есть закодированные спецсимволы (%2e, %2f, %5c, %00, %25), что уже закрывает один из типичных обходов через %2e%2e;

location ~* "^/resize/[0-9]{1,4}x[0-9]{1,4}/upload/[A-Za-z0-9._-]+\\.(png|jpe?g|gif)$" {
    try_files $uri @resize_php;
}

В обработку пропускаются только пути строго вида /resize/WxH/upload/<имя>.(png|jpg|gif);

location ~* ^/resize/ {
    return 400;
}

На всё остальное под /resize/ стоит return 400.

Наш /resize/100x100/../.env под шаблон «картинка из upload» не подходит, поэтому и ловит return 400.

Можем заметить, что все ограничения в nginx навешаны только на путь /resize/…. Но сам ресайз выполняет скрипт resize.php, а в конфиге есть правило, которое отдаёт любые .php-файлы напрямую:

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass 127.0.0.1:9000;
}

Сам путь /resize.php начинается с /resize. (с точкой), а не с /resize/, поэтому под правила WAF он не попадает. Значит, к тому же коду можно обратиться напрямую, минуя все проверки.

Теперь проверим сам resize.php, фильтрует ли он путь.

router.php: разбор пути и заполнение параметров w, h, file

router.php: разбор пути и заполнение параметров w, h, file

Параметр file подставляется в путь как есть, без удаления ../:

$sourcePath = __DIR__ . '/' . $_GET['file'];

А в конце, если файл не распознан как картинка, он просто отдаётся клиенту:

header('Content-Type: image/png');
readfile($sourcePath);

То есть любой файл, который не является изображением (как .env), сервер вернёт в сыром виде.

Соберём всё вместе и обратимся к скрипту напрямую, минуя WAF, и через ../ выходим к .env. Получится URL следующего вида:

/resize.php?w=100&h=100&file=../.env
  • resize.php — обращаемся к скрипту ресайза напрямую;

  • w=100&h=100 — ширина и высота;

  • file=../.env — путь к целевому файлу.

Также в самом пути теперь добавились параметры w, h, file. Это связано с тем, что router.php ранее сам раскладывал части в GET-параметры w, h, file, а при прямом обращении к resize.php роутера нет, поэтому те же параметры передаём напрямую в строке запроса. ? отделяет её от адреса, а & разделяет параметры.

Прямой запрос к resize.php в обход WAF (ответ 200)

Прямой запрос к resize.php в обход WAF (ответ 200)

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

Суть уязвимости — чтение произвольных файлов на сервере через Path Traversal.

Защиту от выхода за пределы папки повесили только на роут /resize/… на уровне nginx, а тот же код оказался доступен напрямую через /resize.php, в обход всех проверок.

Сам же скрипт путь не фильтрует, а берёт параметр file как есть и, если файл не является картинкой, то отдаёт его содержимое в сыром виде.

Как закрыть уязвимость

Если бы путь нормализовался (например, через realpath()) и проверялось, что он действительно внутри upload/, а прямой доступ к resize.php был закрыт, то прочитать .env не вышло бы.

OWASP Top 10

В OWASP Top 10 этой уязвимости соответствует A01:2025 — Broken Access Control: пользователь получает доступ к файлам, к которым не должен.

CWE

По CWE это CWE-22 (Path Traversal). Уязвимость безопасности, которая возникает, когда приложение создаёт путь к файлу из внешних (недоверенных) пользовательских данных, но не нейтрализует специальные элементы вроде ../.

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

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

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

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

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

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

58

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

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

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

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

45