# Part B: a stranger's sign-up through the public route (traefik), each sign-up route of the app in 7 shapes: # as written, trailing slash, UPPER, Mixed, one letter percent-encoded, a double slash, a query string. # Each answer: BLOCK (the box's "sign-up is closed"), APP-REFUSED (the app's own refusal), or GOT-IN (an account was # made / the form was served). A GOT-IN is a hole. import json, re, secrets, sys, urllib.parse sys.path.insert(0, '.') import ro from ro import w, host from browser import Browser def pw(): return "Xx" + secrets.token_hex(8) + "9!" def jn(n): return {"username": n, "email": n + "@x.hu", "password": pw()} FORM = {"Content-Type": "application/x-www-form-urlencoded"} # app -> list of (label, method, path, body-maker, headers, got-in test) ROUTES = { "gitea": [("web form", "GET", "/user/sign_up", None, {}, lambda st, t: st == 200 and 'name="user_name"' in t)], "calcom": [("API", "POST", "/api/auth/signup", lambda n: dict(jn(n)), {}, lambda st, t: st in (200, 201)), ("web page", "GET", "/signup", None, {}, lambda st, t: st == 200 and "Signup is disabled" not in t and "sign-up is closed" not in t and "nem lehet regisztr" not in t and 'name="password"' in t)], "gramps-web": [("API", "POST", "/api/users/{n}/register/", lambda n: {"email": n + "@x.hu", "password": pw(), "full_name": "S"}, {}, lambda st, t: st in (200, 201))], "homebox": [("API", "POST", "/api/v1/users/register", lambda n: {"name": n, "email": n + "@x.hu", "password": pw()}, {}, lambda st, t: st in (200, 201, 204))], "adventurelog": [("web form action", "POST", "/signup", lambda n: urllib.parse.urlencode({"username": n, "email": n + "@x.hu", "password1": "Xx12345678z9!", "password2": "Xx12345678z9!", "first_name": "S", "last_name": "S"}), dict(FORM, **{"x-sveltekit-action": "true"}), lambda st, t: '"type":"redirect"' in t and '"location":"/"' in t), ("allauth API", "POST", "/auth/browser/v1/auth/signup", lambda n: jn(n), {}, lambda st, t: st in (200, 201)), ("allauth classic", "POST", "/accounts/signup/", lambda n: urllib.parse.urlencode({"username": n, "email": n + "@x.hu", "password1": "Xx12345678z9!", "password2": "Xx12345678z9!"}), FORM, lambda st, t: st in (200, 302) and "sign-up is closed" not in t and "nem lehet" not in t and "CSRF" not in t and "Forbidden" not in t)], "papra": [("API", "POST", "/api/auth/sign-up/email", lambda n: {"email": n + "@x.hu", "password": pw(), "name": n}, {}, lambda st, t: st in (200, 201) and "token" in t)], "sparkyfitness": [("API", "POST", "/api/auth/sign-up/email", lambda n: {"email": n + "@x.hu", "password": pw(), "name": n}, {}, lambda st, t: st in (200, 201) and "token" in t)], "termix": [("API", "POST", "/users/create", lambda n: {"username": n, "password": pw()}, {}, lambda st, t: st in (200, 201) and "User created" in t)], "vikunja": [("API", "POST", "/api/v1/register", lambda n: jn(n), {}, lambda st, t: st in (200, 201) and '"id"' in t), ("web page", "GET", "/register", None, {}, lambda st, t: False)], "opengist": [("web form", "POST", "/-/register", "FORM-CSRF", {}, None)], "wishlist": [("web form action", "POST", "/signup", lambda n: urllib.parse.urlencode({"name": "S", "username": n, "email": n + "@x.hu", "password": "Xx12345678z9!", "confirmPassword": "Xx12345678z9!"}), dict(FORM, **{"x-sveltekit-action": "true"}), lambda st, t: '"type":"success"' in t)], } def variants(path): base = path.rstrip("/") segs = base.split("/") last = segs[-1] or segs[-2] enc = last[0] + "%" + format(ord(last[1]), "02X") + last[2:] if len(last) > 2 else last mixed = "/".join(s[:1].upper() + s[1:] for s in segs) lastup = "/".join(segs[:-1] + [segs[-1].upper()]) out = [("as written", path), ("trailing slash", base + "/"), ("UPPER", base.upper()), ("Mixed", mixed), ("last part UPPER", lastup), ("percent-encoded", "/".join(segs[:-1] + [enc])), ("double slash", "/" + base), ("query string", base + "?x=1")] seen, res = set(), [] for k, p in out: if p not in seen: seen.add(p); res.append((k, p)) return res def classify(st, t, got, m="POST"): if "sign-up is closed" in t or "nem lehet regisztr" in t or "Sign-up closed" in t: return "BLOCK" # A POST answered by the app's HTML shell (its fallback for an unknown address) made no account: the app's own # router is case-sensitive, so the tricked address never reached its sign-up code. Measured 2026-09-29: the # homebox login of such a name answers "unauthorized". if m == "POST" and t.lstrip()[:15].lower().startswith((" {st0} {verdict} (no form served)") continue b = urllib.parse.urlencode({"username": n, "password": "Xx12345678z9!", "_csrf": mm.group(1)}) st, t, hops = s.req(f"https://{host(app)}{p2}", "POST", body=b, headers=dict(FORM, Origin=h["Origin"])) gotin = st == 200 and hops[-1][2] in ("/", "/-/all") and len(hops) > 1 verdict = "BLOCK" if ("nem lehet regisztr" in t or "sign-up is closed" in t) else ("GOT-IN" if gotin else "APP-REFUSED") print(f"{app:14} {label:16} {kind:16} {p2:42} -> {st} {verdict}") continue b = body(n) if callable(body) else None accept = "text/html" if m == "GET" else "application/json" st, t, _ = s.req(f"https://{host(app)}{p2}", m, body=b, accept=accept, headers=h, follow=(m == "GET")) print(f"{app:14} {label:16} {kind:16} {p2:42} -> {st} {classify(st, t, got, m)}")