Files
attack-defense-platform/panel/verify_ssh_creds.py
T

70 lines
2.8 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Verify each team's SSH passwords actually work in the live containers.
state.json can look perfect while the container holds a different password —
set_ssh_passwords() races container boot and its failures are easy to miss.
This proves the binding from the INSIDE (per the skill rule: never trust
config, prove it with a real login).
python3 panel/verify_ssh_creds.py [teamIdx ...]
"""
import json
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import teams as orch
def probe(user, pw, port, host="127.0.0.1"):
r = subprocess.run(
["sshpass", "-p", pw, "ssh",
"-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null",
"-o", "ConnectTimeout=8", "-o", "LogLevel=ERROR",
"-p", str(port), f"{user}@{host}", "whoami; hostname"],
capture_output=True, text=True, timeout=30)
out = (r.stdout or "").strip().splitlines()
return (r.returncode == 0 and len(out) >= 2, out, (r.stderr or "").strip()[:80])
def main():
want = [int(a) for a in sys.argv[1:]]
teams = [t for t in orch.list_teams() if not want or t["index"] in want]
for t in teams:
idx = t["index"]
names = [c["name"] for c in orch.enabled_challenges()]
jobs = []
for n in names:
if n not in (t.get("ports") or {}):
continue
jobs.append((n, t["ports"][n]["ssh"], t.get("chall_passwords", {}).get(n)))
ok = bad = 0
details = []
users = orch.challenge_ssh_users()
with ThreadPoolExecutor(max_workers=6) as ex:
futs = {ex.submit(probe, users.get(n, "root"), pw, port): n
for n, port, pw in jobs if pw}
for fut, n in futs.items():
good, out, err = fut.result()
if good:
ok += 1
# hostname must be <challenge>_teamN — proves the binding
details.append((n, out[1] if len(out) > 1 else "?"))
else:
bad += 1
details.append((n, f"FAIL {err}"))
print(f"team{idx} ({t.get('label')}): ssh {ok} ok / {bad} fail")
for n, info in details:
if info == "FAIL" or info.startswith("FAIL"):
print(f" {n}: {info}")
hosts = [i for n, i in details if not i.startswith("FAIL")]
mism = [(n, i) for n, i in details
if not i.startswith("FAIL") and not i.endswith(f"_team{idx}")]
if mism:
print(f" !! hostname mismatch (not _team{idx}): {mism}")
else:
print(f" all hostnames correct (e.g. {hosts[0] if hosts else '-'})")
if __name__ == "__main__":
main()