Skip to content

Commit 9f30d3f

Browse files
authored
Merge pull request #252 from HackTricks-wiki/update_nodes_proxy_GET___Kubelet__exec_RCE_via_WebSocket__20260127_014641
nodes/proxy GET → Kubelet /exec RCE via WebSocket handshake ...
2 parents d4b7c71 + e19da8e commit 9f30d3f

2 files changed

Lines changed: 27 additions & 1 deletion

File tree

src/pentesting-cloud/kubernetes-security/abusing-roles-clusterroles-in-kubernetes/README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,26 @@ Principals with access to the **`nodes/proxy`** subresource can **execute code o
677677
../pentesting-kubernetes-services/kubelet-authentication-and-authorization.md
678678
{{#endref}}
679679

680-
You have an example of how to get [**RCE talking authorized to a Kubelet API here**](../pentesting-kubernetes-services/index.html#kubelet-rce).
680+
#### nodes/proxy GET -> Kubelet /exec via WebSocket verb confusion
681+
682+
- Kubelet maps HTTP methods to RBAC verbs **before** protocol upgrade. WebSocket handshakes must start with **HTTP GET** (`Connection: Upgrade`), so `/exec` over WebSocket is checked as **verb `get`** instead of the expected `create`.
683+
- `/exec`, `/run`, `/attach`, and `/portforward` are not explicitly mapped and fall into the default **`proxy`** subresource, so the authorization question becomes **`can <user> get nodes/proxy?`**
684+
- If a token only has **`nodes/proxy` + `get`**, direct WebSocket access to the kubelet on `https://<node_ip>:10250` allows arbitrary command execution in any pod on that node. The same request via the API server proxy path (`/api/v1/nodes/<node>/proxy/exec/...`) is denied because it is a normal HTTP POST and maps to `create`.
685+
- The kubelet performs no second authorization after the WebSocket upgrade; only the initial GET is evaluated.
686+
687+
**Direct exploit (requires network reachability to the kubelet and a token with `nodes/proxy` GET):**
688+
689+
```bash
690+
kubectl auth can-i --list | grep "nodes/proxy"
691+
websocat --insecure \
692+
--header "Authorization: Bearer $TOKEN" \
693+
--protocol "v4.channel.k8s.io" \
694+
"wss://$NODE_IP:10250/exec/$NAMESPACE/$POD/$CONTAINER?output=1&error=1&command=id"
695+
```
696+
697+
- Use the **Node IP**, not the node name. The same request with `curl -X POST` will be **Forbidden** because it maps to `create`.
698+
- Direct kubelet access bypasses the API server, so AuditPolicy only shows `subjectaccessreviews` from the kubelet user agent and **does not log `pods/exec`** commands.
699+
- Enumerate affected service accounts with the [detection script](https://gist.github.com/grahamhelton/f5c8ce265161990b0847ac05a74e466a) to find tokens limited to `nodes/proxy` GET.
681700

682701
### Delete pods + unschedulable nodes
683702

@@ -844,6 +863,9 @@ https://github.com/aquasecurity/kube-bench
844863
- [**https://blog.rewanthtammana.com/creating-malicious-admission-controllers**](https://blog.rewanthtammana.com/creating-malicious-admission-controllers)
845864
- [**https://kubenomicon.com/Lateral_movement/CoreDNS_poisoning.html**](https://kubenomicon.com/Lateral_movement/CoreDNS_poisoning.html)
846865
- [**https://kubenomicon.com/**](https://kubenomicon.com/)
866+
- [nodes/proxy GET -> kubelet exec WebSocket bypass](https://grahamhelton.com/blog/nodes-proxy-rce)
867+
- [nodes/proxy GET detection script](https://gist.github.com/grahamhelton/f5c8ce265161990b0847ac05a74e466a)
868+
- [websocat](https://github.com/vi/websocat)
847869

848870
{{#include ../../../banners/hacktricks-training.md}}
849871

src/pentesting-cloud/kubernetes-security/pentesting-kubernetes-services/kubelet-authentication-and-authorization.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ The kubelet authorizes API requests using the same [request attributes](https://
9191
| /spec/\* | nodes | spec |
9292
| _all others_ | nodes | proxy |
9393

94+
> [!NOTE]
95+
> WebSocket-based `/exec`, `/run`, `/attach`, and `/portforward` fall into the default **proxy** subresource and are authorized using the initial HTTP **GET** handshake. A principal with only `nodes/proxy` **GET** can still exec containers if it connects directly to `https://<node_ip>:10250` over WebSockets. See the [nodes/proxy GET -> Kubelet /exec verb confusion abuse](../abusing-roles-clusterroles-in-kubernetes/README.md#nodesproxy-get---kubelet-exec-via-websocket-verb-confusion) for details.
96+
9497
For example, the following request tried to access the pods info of kubelet without permission:
9598

9699
```bash
@@ -105,6 +108,7 @@ Forbidden (user=system:node:ip-172-31-28-172.ec2.internal, verb=get, resource=no
105108
## References
106109

107110
- [https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/](https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/)
111+
- [nodes/proxy GET -> kubelet exec via WebSocket bypass](https://grahamhelton.com/blog/nodes-proxy-rce)
108112

109113
{{#include ../../../banners/hacktricks-training.md}}
110114

0 commit comments

Comments
 (0)