From 14b5855b86b20d22731ad68375ee87736ab2852b Mon Sep 17 00:00:00 2001 From: dmeetrogon Date: Wed, 26 Aug 2026 11:27:58 +0300 Subject: [PATCH] =?UTF-8?q?rusgram:=20=D0=BF=D0=B0=D1=82=D1=87=20RCE,=20?= =?UTF-8?q?=D0=B7=D0=B0=D1=85=D0=B2=D0=B0=D1=82=D0=B0=20=D0=B0=D0=BA=D0=BA?= =?UTF-8?q?=D0=B0=D1=83=D0=BD=D1=82=D0=B0,=20SSRF=20=D0=B8=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=B4=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B2=20500?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Разбор сервиса и все PoC — в services/rusgram/WRITEUP.md и VULNS.md. Каждая находка воспроизведена на живом стенде до патча и перепроверена после. content.py — V1 (RCE, крит) и V5: template_folder='files' совпадает с каталогом БД, поэтому render_template(path) с путём из URL рендерил файл пользователя как исходник Jinja-шаблона. Имя юзера выбирает атакующий: регистрируем `pwn.html`, кладём payload себе в first_name, дёргаем /db/users/pwn.html через публичный :38000 — RCE от root в контейнере, где смонтирован весь website/. Теперь рендерим только 4 реальные страницы. logic.py — V2 (захват аккаунта) и V3/V6 (SSRF/открытый редирект): register не проверял существование юзера, а database делал read-modify-write, так что повторная регистрация ПЕРЕЗАПИСЫВАЛА пароль, сохраняя msg и профиль: чужой аккаунт вместе с флагом и сломанный логин у чекера. Теперь 409. Location строился из заголовка Origin, а proxy ходил по нему серверсайд — чтение внутренней сети. Редирект стал фиксированным /login.html. database.py — снижает ущерб от V4: валидация имени юзера как имени файла, белый список полей, пароль можно только создать, но не перезаписать (смены пароля в сервисе нет), 400/404 вместо 500. proxy.py — defense in depth к V3: allow_redirects=False на ветке api/. V8: единая безопасная проверка сессии вместо 4 копий — кривая кука, отсутствие куки, несуществующий юзер и нечисловой img больше не роняют воркер в 500. Формат куки login||password и плейнтекстовые пароли осознанно НЕ трогали: чекер почти наверняка на них завязан. Тесты: services/rusgram/test_rusgram.py — SLA 16/16, SEC 21/21 на патче; на services_vulnerable та же секция SEC падает 12 раз. Сплойт собирает 0 флагов. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01NN8sHQuGbTBLGGkJyfxzXr --- services/rusgram/PATCH.md | 122 +++++++++ services/rusgram/VULNS.md | 347 ++++++++++++++++++++++++++ services/rusgram/WRITEUP.md | 152 +++++++++++ services/rusgram/content/content.py | 87 +++++-- services/rusgram/database/database.py | 122 +++++++-- services/rusgram/docker-compose.yml | 11 +- services/rusgram/logic/logic.py | 177 ++++++++++--- services/rusgram/proxy/proxy.py | 9 +- services/rusgram/test_rusgram.py | 228 +++++++++++++++++ 9 files changed, 1169 insertions(+), 86 deletions(-) create mode 100644 services/rusgram/PATCH.md create mode 100644 services/rusgram/VULNS.md create mode 100644 services/rusgram/WRITEUP.md create mode 100644 services/rusgram/test_rusgram.py diff --git a/services/rusgram/PATCH.md b/services/rusgram/PATCH.md new file mode 100644 index 0000000..925523b --- /dev/null +++ b/services/rusgram/PATCH.md @@ -0,0 +1,122 @@ +# Rusgram — что запатчено + +Оригинал (для диффа): `services_vulnerable/rusgram` +Пропатчено: `services/rusgram` + +```bash +diff -ru services_vulnerable/rusgram services/rusgram +``` + +Проверено на живом стенде: легальный флоу чекера проходит полностью, +`sploits/rusgram_sploit.py` против пропатченного сервиса собирает **0 флагов** +и не может даже перечислить пользователей. + +--- + +## Изменения по файлам + +### `content/content.py` — закрывает V1 (RCE) и V5 + +* **Белый список шаблонов.** `render_template(path)` теперь вызывается только для + `index.html`, `login.html`, `register.html`, `specific.html`. Всё остальное — 404. + Это корень SSTI: `template_folder='files'` совпадает с каталогом БД, поэтому + файл пользователя рендерился как исходник Jinja-шаблона. +* `get_login()` вынесен из тела вьюхи и больше не падает: нет куки, кука кривого + формата, юзера не существует, database недоступен — всё возвращает `None`. +* `specific.html`: `img` приводится к `int` и проверяется на диапазон 1–16. + Побочно чинит незакавыченную подстановку `{{img['id']}}` внутрь `