fix(ssh): per-challenge SSH login + phew buffering/leak/timeout

Passwords failed on 10/16 challenges while state.json looked correct:
- only the 6 native GEMASTIK XVIII images provision 'ctfuser'; every imported
  XVI/XVII image does 'echo root:${PASSWORD} | chpasswd' and logs in as root.
  set_ssh_passwords() hardcoded ctfuser, so chpasswd set a password on an
  account nobody uses -> 'Permission denied' everywhere.
  Registry gains a per-challenge 'ssh_user'; chpasswd now targets the real
  login (and ctfuser/ctf when present) and reports failures loudly.
- phew checker: chall.py block-buffers stdout through the docker exec pipe
  (PYTHONUNBUFFERED now set) and leaks chall.py inside the container on
  timeout (26 orphans, container saturated) -> reaps the whole exec process
  group. Startup does a fresh Pailier keygen (~12 s) so crypto reads need
  _CRYPTO_TIMEOUT, not the 5 s prompt default.

Adds panel/verify_ssh_creds.py (proves the state->container binding from
inside via a real login), audit_ssh_users.sh, reset_runtime.sh.
This commit is contained in:
Cyrene
2026-09-26 14:40:10 +08:00
parent ef385c3397
commit ae50acfe40
33 changed files with 1398 additions and 289 deletions
+67 -38
View File
@@ -26,7 +26,8 @@
"ssh_offset": 22,
"enabled": true,
"service_dir": "blogpost",
"org_port": 10000
"org_port": 10000,
"ssh_user": "ctfuser"
},
{
"name": "carbeat",
@@ -37,7 +38,8 @@
"ssh_offset": 23,
"enabled": true,
"service_dir": "carbeat",
"org_port": 11000
"org_port": 11000,
"ssh_user": "ctfuser"
},
{
"name": "cdn",
@@ -48,7 +50,8 @@
"ssh_offset": 24,
"enabled": true,
"service_dir": "cdn",
"org_port": 12000
"org_port": 12000,
"ssh_user": "ctfuser"
},
{
"name": "phew",
@@ -59,7 +62,8 @@
"ssh_offset": 25,
"enabled": true,
"service_dir": "phew",
"org_port": 13000
"org_port": 13000,
"ssh_user": "ctfuser"
},
{
"name": "sheesh",
@@ -70,7 +74,8 @@
"ssh_offset": 26,
"enabled": true,
"service_dir": "sheesh",
"org_port": 14000
"org_port": 14000,
"ssh_user": "ctfuser"
},
{
"name": "warmup",
@@ -81,7 +86,8 @@
"ssh_offset": 27,
"enabled": true,
"service_dir": "warmup",
"org_port": 15000
"org_port": 15000,
"ssh_user": "ctfuser"
},
{
"name": "art",
@@ -90,9 +96,10 @@
"desc": "Ruby ASCII art renderer",
"chall_offset": 10,
"ssh_offset": 110,
"enabled": false,
"enabled": true,
"service_dir": "art",
"org_port": 10000
"org_port": 10000,
"ssh_user": "root"
},
{
"name": "xl",
@@ -101,9 +108,10 @@
"desc": "Node XL spreadsheets app",
"chall_offset": 11,
"ssh_offset": 111,
"enabled": false,
"enabled": true,
"service_dir": "xl",
"org_port": 11000
"org_port": 11000,
"ssh_user": "root"
},
{
"name": "gemas-notes",
@@ -114,7 +122,8 @@
"ssh_offset": 112,
"enabled": false,
"service_dir": "gemas-notes",
"org_port": 12000
"org_port": 12000,
"ssh_user": "root"
},
{
"name": "pasta",
@@ -125,7 +134,8 @@
"ssh_offset": 113,
"enabled": false,
"service_dir": "pasta",
"org_port": 13000
"org_port": 13000,
"ssh_user": "root"
},
{
"name": "burvesigner",
@@ -136,7 +146,8 @@
"ssh_offset": 114,
"enabled": false,
"service_dir": "burvesigner",
"org_port": 14000
"org_port": 14000,
"ssh_user": "root"
},
{
"name": "hirnfick",
@@ -147,7 +158,8 @@
"ssh_offset": 115,
"enabled": false,
"service_dir": "hirnfick",
"org_port": 15000
"org_port": 15000,
"ssh_user": "root"
},
{
"name": "gemas-fetcher",
@@ -158,7 +170,8 @@
"ssh_offset": 116,
"enabled": false,
"service_dir": "gemas-fetcher",
"org_port": 16000
"org_port": 16000,
"ssh_user": "root"
},
{
"name": "s3",
@@ -167,9 +180,10 @@
"desc": "S3-ish service",
"chall_offset": 20,
"ssh_offset": 120,
"enabled": false,
"enabled": true,
"service_dir": "s3",
"org_port": 20000
"org_port": 20000,
"ssh_user": "root"
},
{
"name": "crawlback",
@@ -180,7 +194,8 @@
"ssh_offset": 121,
"enabled": false,
"service_dir": "crawlback",
"org_port": 21000
"org_port": 21000,
"ssh_user": "root"
},
{
"name": "back-to-basic",
@@ -191,7 +206,8 @@
"ssh_offset": 122,
"enabled": false,
"service_dir": "back-to-basic",
"org_port": 22000
"org_port": 22000,
"ssh_user": "root"
},
{
"name": "anti-alchemy",
@@ -200,9 +216,10 @@
"desc": "Alchemy flask app (web)",
"chall_offset": 30,
"ssh_offset": 130,
"enabled": false,
"enabled": true,
"service_dir": "anti-alchemy",
"org_port": 11000
"org_port": 11000,
"ssh_user": "root"
},
{
"name": "asmr",
@@ -213,7 +230,8 @@
"ssh_offset": 131,
"enabled": false,
"service_dir": "asmr",
"org_port": 15000
"org_port": 15000,
"ssh_user": "root"
},
{
"name": "bit-canvas",
@@ -222,9 +240,10 @@
"desc": "C xinetd pwn",
"chall_offset": 32,
"ssh_offset": 132,
"enabled": false,
"enabled": true,
"service_dir": "bit-canvas",
"org_port": 20000
"org_port": 20000,
"ssh_user": "root"
},
{
"name": "fjb",
@@ -235,7 +254,8 @@
"ssh_offset": 133,
"enabled": false,
"service_dir": "fjb",
"org_port": 17000
"org_port": 17000,
"ssh_user": "root"
},
{
"name": "gift-card",
@@ -244,9 +264,10 @@
"desc": "Crypto gift card",
"chall_offset": 34,
"ssh_offset": 134,
"enabled": false,
"enabled": true,
"service_dir": "gift-card",
"org_port": 21000
"org_port": 21000,
"ssh_user": "root"
},
{
"name": "gift-voucher",
@@ -255,9 +276,10 @@
"desc": "Crypto gift voucher",
"chall_offset": 35,
"ssh_offset": 135,
"enabled": false,
"enabled": true,
"service_dir": "gift-voucher",
"org_port": 16000
"org_port": 16000,
"ssh_user": "root"
},
{
"name": "gleam-drive",
@@ -266,9 +288,11 @@
"desc": "Gleam drive web-crypto",
"chall_offset": 36,
"ssh_offset": 136,
"enabled": false,
"enabled": true,
"service_dir": "gleam-drive",
"org_port": 12000
"org_port": 12000,
"scheme": "https",
"ssh_user": "root"
},
{
"name": "go-green",
@@ -279,7 +303,8 @@
"ssh_offset": 137,
"enabled": false,
"service_dir": "go-green",
"org_port": 20000
"org_port": 20000,
"ssh_user": "root"
},
{
"name": "kode-viewer",
@@ -290,7 +315,8 @@
"ssh_offset": 138,
"enabled": false,
"service_dir": "kode-viewer",
"org_port": 10000
"org_port": 10000,
"ssh_user": "root"
},
{
"name": "more-less",
@@ -299,9 +325,10 @@
"desc": "Python more/less web",
"chall_offset": 39,
"ssh_offset": 139,
"enabled": false,
"enabled": true,
"service_dir": "more-less",
"org_port": 22000
"org_port": 22000,
"ssh_user": "root"
},
{
"name": "tempest-poc",
@@ -312,7 +339,8 @@
"ssh_offset": 140,
"enabled": false,
"service_dir": "tempest-poc",
"org_port": 14080
"org_port": 14080,
"ssh_user": "root"
},
{
"name": "ticketer",
@@ -321,9 +349,10 @@
"desc": "Ticketer crypto oracle (socat)",
"chall_offset": 41,
"ssh_offset": 141,
"enabled": false,
"enabled": true,
"service_dir": "ticketer",
"org_port": 14000
"org_port": 14000,
"ssh_user": "root"
}
]
}