Files
felhom.eu/documentation/audits/security-2026-11-02/part5/walk_crash_t1_customer.py
T

76 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""walk_crash_t1_customer.py — the household's leg of a crash mail (fail.app-crash, fault.customer-email), Tester 1.
Tester 1's notification page has no address (found empty 2026-11-02), so the first walk mailed only the operator. A household
types its address on the page and saves it (the page's own form), then an app crash-loops. Want: the Hungarian mail in the
household's mailbox (the catch-all, read through the mailbox). Then the address is cleared through the same page (its own
„confirm_clear"), the app removed. What stays: the saved list of events = the page's defaults."""
import os, re, sys, time
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
os.environ.setdefault("TARGET", "tester-1")
import box_walk as w
APP, SUB = "radicale", "radicale"
EV, ADDRESS = os.environ["EV"], "tester1@felhom.eu"
def form(fields):
sess = open(f"{w.SC}/sess{os.getpid()}.txt").read().strip()
csrf = open(f"{w.SC}/csrf{os.getpid()}.txt").read().strip()
args = ["curl", "-sk", "-D", "-", "-o", "/dev/null", "-H", w.HOSTHDR, "-H", f"Cookie: {sess}", "-X", "POST", "--data-urlencode", f"_csrf={csrf}"]
for k, v in fields:
args += ["--data-urlencode", f"{k}={v}"]
r = w.sh(args + [f"{w.BASE}/settings/notifications"], timeout=60)
return (r.stdout or "").split("\n")[0].strip()
w.login()
page = w.page("/settings/notifications")
page = page if isinstance(page, str) else str(page)
fields = [("notification_email", ADDRESS), ("cooldown_hours", "6")]
for m in re.finditer(r'<input type="hidden" name="(group_[a-z]+_shown)" value="([^"]*)"', page):
fields.append((m.group(1), m.group(2)))
checked = []
for tag in re.findall(r"<input[^>]*type=\"checkbox\"[^>]*>", page):
n = re.search(r'name="([a-z_]+)"', tag)
if n and " checked" in tag:
fields.append((n.group(1), "on"))
checked.append(n.group(1))
w.say(f" checked as found: {checked}")
if not any(c in ("group_apps", "event_app_stopped_unhealthy") for c in checked):
fields.append(("group_apps", "on"))
w.say(" the group „my apps” was off: switched on, as a household that wants this mail would")
w.say(f" save with the address -> {form(fields)}")
time.sleep(20) # the page's save syncs the preferences to the hub
assert not w.stack(APP).get("deployed"), "radicale is on the box — not a throwaway"
assert w.deploy(APP, SUB), "install failed"
w.wait_app(SUB, "/", tries=40)
start = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
w.say(f" installed; kills start {start}")
for i in range(12):
pid = w.guest(f"docker inspect -f '{{{{.State.Pid}}}}' {APP} 2>/dev/null").strip()
if pid and pid != "0":
w.guest(f"kill -9 {pid}")
st = w.stack(APP)
w.say(f" kill -9 #{i + 1} pid {pid}: state {st.get('state')} hold {str(st.get('hold_reason'))[:40]!r}")
if st.get("hold_reason"):
break
time.sleep(15)
time.sleep(60)
w.say(" controller log:\n" + w.guest(f"docker logs --since {start} felhom-controller 2>&1 | grep -E 'STOPPING|Event pushed' | cut -c1-200 | tail -6"))
c, d = w.ctl("POST", f"/api/stacks/{APP}/start")
w.say(f" Start -> {c}")
for _ in range(24):
time.sleep(5)
st = w.stack(APP)
if st.get("state") == "running" and not st.get("hold_reason"):
break
w.remove(APP)
w.say(f" removed: deployed={w.stack(APP).get('deployed')}")
w.say(f" the address cleared -> {form([('notification_email', ''), ('confirm_clear', '1'), ('cooldown_hours', '6')] + [f for f in fields if f[0] not in ('notification_email', 'cooldown_hours')])}")
time.sleep(5)
p2 = w.page("/settings/notifications")
p2 = p2 if isinstance(p2, str) else str(p2)
w.say(" the page's address now: " + (re.search(r'name="notification_email"\s*value="([^"]*)"', p2) or [None, "?"])[1])
open(os.path.join(EV, "crash-walk-customer.log"), "w").write("\n".join(w.LOG) + "\n")