Files
felhom.eu/documentation/audits/lockouts-2026-10-01/tools/a_calibre_name.py
T

90 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Decision 61 (R-752) on 9202, DRILL catalog: calibre-web with a generated admin LOGIN NAME.
1 the box's template copy carries ADMIN_USER; 2 a stranger polls the public default login from the deploy press until
the install hold opens (R-741's window); 3 the household's name is read the way the app page reads it (the Settings
page's reveal, POST /stacks/<n>/auto-field/reveal); 4 positive controls (name + password: the login form and OPDS),
`admin` + the right password refused; 5 a stranger's 40 wrong tries on `admin` at the 3/minute pace (~14 min); 6 the
household logs in AT ONCE with its own name (form and OPDS); a wrong password on the real name still refused. Removed."""
import json, subprocess, threading, time
import walk as w
import lk
APP, SUB = "calibre-web", "books"
def reveal(env_var):
sess = open(f"{w.SC}/sess{__import__('os').getpid()}.txt").read().strip()
csrf = open(f"{w.SC}/csrf{__import__('os').getpid()}.txt").read().strip()
r = subprocess.run(["curl", "-sk", "-H", w.HOSTHDR, "-H", f"Cookie: {sess}", "-H", f"X-CSRF-Token: {csrf}",
"-H", "Content-Type: application/x-www-form-urlencoded", "--data", f"env_var={env_var}",
f"{w.BASE}/stacks/{APP}/auto-field/reveal"], capture_output=True, text=True)
try:
return json.loads(r.stdout)["data"]["value"]
except Exception:
return ""
w.login()
for _ in range(12):
w.sync_rescan()
cp = w.guest(f"grep -c ADMIN_USER /opt/docker/stacks/{APP}/.felhom.yml").strip()
if cp not in ("", "0"):
break
time.sleep(15)
lk.p("the box's template copy names ADMIN_USER:", cp, "times")
res, stop = [], threading.Event()
def poll():
while not stop.is_set():
res.append((lk.now(), lk.calibre("admin", "admin123")))
time.sleep(2)
t = threading.Thread(target=poll, daemon=True)
t.start()
lk.p("deploy:", w.deploy(APP, SUB))
for _ in range(120):
time.sleep(5)
if "hold OPENED" in w.guest(f"docker logs --since 20m felhom-controller 2>&1 | grep '{APP}: install hold OPENED'"):
break
time.sleep(5)
stop.set()
t.join()
prev = None
for ts, c in res:
if c != prev:
lk.p(" stranger, default login admin/admin123 ->", c)
prev = c
lk.p("stranger got in with the default login:", sum(1 for _, c in res if c == "ok"), "of", len(res))
lk.p(w.guest("docker logs --since 20m felhom-controller 2>&1 | grep -E 'calibre-web.*(install HOLD|hold OPENED|after_install)' | cut -c1-200").strip())
lk.p("app.yaml after_install record:", w.guest(f"grep -A3 '^after_install:' /opt/docker/stacks/{APP}/app.yaml").strip().replace("\n", " "))
name = reveal("ADMIN_USER")
pw = (w.GENERATED.get(APP) or {}).get("ADMIN_PASSWORD", "")
lk.p(f"the household's name (read through the page's reveal): {len(name)} characters, lowercase hex: {name.isalnum() and name == name.lower()}")
lk.p("users in app.db (total, named admin):", w.guest("""cat > /tmp/cwu.py <<'PY'
import sqlite3
c = sqlite3.connect('/config/app.db')
print(c.execute("SELECT count(*), sum(lower(name) = 'admin') FROM user").fetchone())
PY
docker cp /tmp/cwu.py calibre-web:/tmp/cwu.py && docker exec calibre-web python3 /tmp/cwu.py""").strip())
lk.p("positive — form, name + password:", lk.calibre(name, pw), "| OPDS:", lk.calibre_opds(name, pw))
lk.p("admin + the RIGHT password (the old name is gone):", lk.calibre("admin", pw))
lk.p("## a stranger: 40 wrong tries on `admin`, 3 a minute")
n = 0
while n < 40:
for _ in range(3):
if n < 40:
n += 1
r = lk.calibre("admin", f"daily-{n}")
if n % 10 == 0 or r not in ("wrong",):
lk.p(f" wrong try {n}: {r}")
time.sleep(61)
lk.p("one more stranger try on admin:", lk.calibre("admin", "one-more"))
lk.p("HOUSEHOLD at once — form, own name + password:", lk.calibre(name, pw), "| OPDS:", lk.calibre_opds(name, pw))
lk.p("a wrong password on the real name:", lk.calibre(name, "wrong-real"), "| right again:", lk.calibre(name, pw))
w.remove(APP)
lk.p("removed; deployed =", w.stack(APP).get("deployed"))