Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,26 @@ Principals with access to the **`nodes/proxy`** subresource can **execute code o
../pentesting-kubernetes-services/kubelet-authentication-and-authorization.md
{{#endref}}

You have an example of how to get [**RCE talking authorized to a Kubelet API here**](../pentesting-kubernetes-services/index.html#kubelet-rce).
#### nodes/proxy GET -> Kubelet /exec via WebSocket verb confusion

- 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`.
- `/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?`**
- 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`.
- The kubelet performs no second authorization after the WebSocket upgrade; only the initial GET is evaluated.

**Direct exploit (requires network reachability to the kubelet and a token with `nodes/proxy` GET):**

```bash
kubectl auth can-i --list | grep "nodes/proxy"
websocat --insecure \
--header "Authorization: Bearer $TOKEN" \
--protocol "v4.channel.k8s.io" \
"wss://$NODE_IP:10250/exec/$NAMESPACE/$POD/$CONTAINER?output=1&error=1&command=id"
```

- Use the **Node IP**, not the node name. The same request with `curl -X POST` will be **Forbidden** because it maps to `create`.
- Direct kubelet access bypasses the API server, so AuditPolicy only shows `subjectaccessreviews` from the kubelet user agent and **does not log `pods/exec`** commands.
- Enumerate affected service accounts with the [detection script](https://gist.github.com/grahamhelton/f5c8ce265161990b0847ac05a74e466a) to find tokens limited to `nodes/proxy` GET.

### Delete pods + unschedulable nodes

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ The kubelet authorizes API requests using the same [request attributes](https://
| /spec/\* | nodes | spec |
| _all others_ | nodes | proxy |

> [!NOTE]
> 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.

For example, the following request tried to access the pods info of kubelet without permission:

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

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

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

Expand Down