Files
felhom.eu/documentation/audits/permanent-gate-2026-10-01/tools/items12.py
T

60 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Exit items 1 and 2 of EXIT-TEST.md."""
import json, re, subprocess, sys, time
from sp import *
out = open(sys.argv[1], "w", buffering=1)
def say(*a):
s = " ".join(map(str, a)); print(s); out.write(s + "\n")
say(f"# items 1–2, {time.strftime('%FT%TZ', time.gmtime())} — 9202, throwaway familygate (forwardAuth), Grimmory v3.4.1 + MeTube 2026.09.29")
WS = ("Connection: Upgrade", "Upgrade: websocket", "Sec-WebSocket-Version: 13", "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==")
cases = [
(GM, "GET", "/", ("Accept: text/html",), None), (GM, "GET", "/", (), None), (GM, "GET", "/api/v1/books", (), None),
(GM, "GET", "/api/v1/healthcheck", (), None), (GM, "GET", "/api/v1/public-settings", (), None),
(GM, "POST", "/api/v1/setup", ("Content-Type: application/json",), '{"username":"x","password":"Stranger-1234","email":"x@x.hu","name":"x"}'),
(GM, "POST", "/api/v1/auth/login", ("Content-Type: application/json",), '{"username":"admin","password":"guess"}'),
(GM, "GET", "/ws/websocket", WS, None), (GM, "GET", "/assets/index.js", (), None), (GM, "GET", "/no-such-page", ("Accept: text/html",), None),
(MT, "GET", "/", ("Accept: text/html",), None), (MT, "GET", "/", (), None), (MT, "GET", "/history", (), None),
(MT, "POST", "/add", ("Content-Type: application/json",), '{"url":"https://example.com/v","quality":"best"}'),
(MT, "GET", "/socket.io/?EIO=4&transport=polling", (), None), (MT, "GET", "/socket.io/?EIO=4&transport=websocket", WS, None),
(MT, "GET", "/download/x.mp4", (), None), (MT, "GET", "/version", (), None),
]
say("## item 1 — a STRANGER (no cookie), from the LAN and through the simulated tunnel; GATE = the gate answered, APP = the app did")
app_answers = 0
for host, m, p, h, d in cases:
c, head, body = curl(host, p, method=m, data=d, hdrs=h)
hl = list(h)
tc, thead, tbody = tunnel(host, p, "198.51.100.66", None, "-X", m, *sum([["-H", x] for x in hl], []), *(["--data", d] if d else []))
g1, g2 = is_gate(c, head, body), is_gate(tc, thead, tbody)
app_answers += (not g1) + (not g2)
say(f" {host.split('.')[0]:9s} {m:4s} {p:42s} LAN {c} {'GATE' if g1 else 'APP!'} | tunnel {tc} {'GATE' if g2 else 'APP!'}")
say(f"item 1: {app_answers} app answers of {2*len(cases)} stranger requests -> {'PASS' if app_answers == 0 else 'FAIL'}")
say("## item 2 — family members with their OWN logins (not the dashboard password)")
c, ck, head = family_login(GM, "anna")
ma = re.search(r"(?i)max-age=(\d+)", head)
say(f" anna signs in on {GM}: {c}; cookie {'set' if ck else 'NONE'}; Max-Age {ma.group(1) if ma else '?'} s = {int(ma.group(1))/86400 if ma else 0:.0f} days; flags: "
f"{', '.join(f for f in ('HttpOnly','Secure','SameSite=Lax') if f.lower() in head.lower())}; Domain attr: {'YES' if re.search(r'(?i)set-cookie: felhom_family=[^\n]*domain=', head) else 'none (host-only)'}")
for p in ("/", "/api/v1/healthcheck"):
cc, hh, bb = curl(GM, p, cookie=ck, hdrs=("Accept: text/html",))
say(f" anna GET {GM}{p} -> {cc} {'GATE' if is_gate(cc, hh, bb) else 'APP'}")
c2, ck2, head2 = family_login(MT, "bela")
say(f" bela signs in on {MT}: {c2}; cookie {'set' if ck2 else 'NONE'}")
for host, m, p, h, d in [(MT, "GET", "/", ("Accept: text/html",), None), (MT, "GET", "/socket.io/?EIO=4&transport=polling", (), None),
(MT, "GET", "/socket.io/?EIO=4&transport=websocket", WS, None), (MT, "GET", "/history", (), None)]:
cc, hh, bb = curl(host, p, method=m, cookie=ck2, hdrs=h, timeout=5)
say(f" bela {m} {host}{p} -> {cc} {'GATE' if is_gate(cc, hh, bb) else 'APP'}")
cc, hh, bb = curl(GM, "/", cookie=ck2.replace("felhom_family", "felhom_family") if ck2 else None, hdrs=("Accept: text/html",))
say(f" bela's MeTube cookie sent to Grimmory -> {cc} {'GATE' if is_gate(cc, hh, bb) else 'APP!'} (a session is per app host)")
cc, hh, bb = curl(GM, "/", cookie="felhom_family=" + "0"*64, hdrs=("Accept: text/html",))
say(f" a made-up session id -> {cc} {'GATE' if is_gate(cc, hh, bb) else 'APP!'}")
subprocess.run([GSH, "9202", "docker restart spike-gate >/dev/null; sleep 3"], capture_output=True)
cc, hh, bb = curl(GM, "/", cookie=ck, hdrs=("Accept: text/html",))
say(f" after the gate RESTARTED, anna's cookie -> {cc} {'GATE' if is_gate(cc, hh, bb) else 'APP (session survived)'}")
lc, lh, lb = curl(GM, "/__family/logout", cookie=ck)
say(f" anna logs out -> {lc} {re.search(r'(?im)^location: (.*)$', lh).group(1).strip() if re.search(r'(?im)^location:', lh) else ''}")
cc, hh, bb = curl(GM, "/", cookie=ck, hdrs=("Accept: text/html",))
say(f" anna's OLD cookie after logout -> {cc} {'GATE (refused)' if is_gate(cc, hh, bb) else 'APP! (still in)'}")
c3, ck3, _ = family_login(GM, "anna", "wrong-password-x")
say(f" anna with a WRONG password -> {c3}, cookie {'set!' if ck3 else 'none'}")
json.dump({"anna_gm": family_login(GM, "anna")[1], "bela_mt": ck2}, open(f"{SPIKE}/cookies.json", "w")); import os; os.chmod(f"{SPIKE}/cookies.json", 0o600)