Detectar kubectl exec, attach y port-forward en audit logs
Cómo aparecen kubectl exec, attach, cp, port-forward y nodes/proxy en los audit logs de Kubernetes, qué revela el comando y cómo distinguir admin de abuso.
TL;DR. kubectl exec y kubectl attach son solicitudes a los subrecursos pods/exec y pods/attach; kubectl port-forward llega a pods/portforward. Filtra por objectRef.subresource, acepta tanto create como get como verbos, espera el código de respuesta 101 y lee el comando en los parámetros command= de requestURI. Un exec hecho por una cuenta de servicio es raro y debe tratarse como severidad alta. El acceso a nodes/proxy llega directamente al kubelet y es peor. Nada de lo que se teclea dentro de la sesión se registra.
Ejecutar un comando en un contenedor es la técnica ATT&CK T1609, Container Administration Command. También es lo que más hace un administrador legítimo durante una caída. El objetivo no es marcar cada exec, sino que cada exec tenga explicación.
Cómo se ve un exec en el audit log
kubectl exec -it api-6c9f7d8b5-q2w8x -n shop -- sh produce un evento como este (recortado, nombres ficticios):
{
"verb": "create",
"stage": "ResponseStarted",
"requestURI": "/api/v1/namespaces/shop/pods/api-6c9f7d8b5-q2w8x/exec?command=sh&container=api&stdin=true&stdout=true&tty=true",
"user": { "username": "alice@example.com" },
"objectRef": { "resource": "pods", "namespace": "shop", "name": "api-6c9f7d8b5-q2w8x", "subresource": "exec" },
"responseStatus": { "code": 101 }
}
Lo que conviene saber:
- Verbo. El endpoint exec se ha autorizado y auditado tradicionalmente como
create. Desde Kubernetes 1.31, kubectl usa WebSockets por defecto para el streaming, y un upgrade de WebSocket empieza como unGETHTTP, así que según las versiones puedes verget. Busca ambos. - Stage. El exec es una solicitud larga. El evento
ResponseStartedse escribe al abrirse la sesión;ResponseComplete, solo al cerrarse. Si la política no omite nada, tendrás los dos. - Código de respuesta.
101 Switching Protocolssignifica que la sesión se estableció. Un403significa que alguien lo intentó y RBAC dijo que no, lo cual ya es interesante. - Comando. Cada argumento es un parámetro
command=distinto, codificado en URL:command=chroot&command=%2Fhost&command=sheschroot /host sh. Como está en la URI, se registra incluso en nivelMetadata.
Consulta la anatomía de un evento de auditoría para el resto de campos.
Attach, cp, debug y port-forward
| Acción del cliente | Rastro de auditoría | Qué mirar |
|---|---|---|
kubectl attach | pods/attach | Sin comando: se conecta al proceso principal del contenedor |
kubectl cp | pods/exec con command=tar | La dirección (tar cf = copia desde el pod, tar -x… = copia hacia el pod) y las rutas |
kubectl debug (contenedor efímero) | patch sobre pods/ephemeralcontainers y luego pods/attach | La imagen y si comparte el espacio de procesos del objetivo |
kubectl port-forward | pods/portforward con ports= | Qué puerto: 5432, 6379 o 3306 son bases de datos |
kubectl proxy / proxy de API hacia un nodo | nodes/proxy | Acceso directo a la API del kubelet |
kubectl cp merece una revisión aparte: copiar un archivo hacia un pod es la forma de dejar herramientas sin descargar una imagen nueva, y copiar desde un pod es exfiltración a través del API server.
kubectl port-forward abre un túnel desde la máquina del cliente hacia la red de pods. Se salta los ingress controllers y, para el tráfico del cliente, las network policies que solo filtran tráfico entre pods. Un port-forward hacia un pod de base de datos hecho por una identidad que nunca lo hace es una pista sólida.
nodes/proxy: exec sin evento de exec
El subrecurso nodes/proxy permite hablar con la API del kubelet a través del API server. El kubelet puede ejecutar comandos en cualquier contenedor de ese nodo y leer sus logs. El API server audita la solicitud a nodes/proxy, pero no como un pods/exec, así que las detecciones basadas en exec no la ven. Las buenas prácticas de RBAC de Kubernetes incluyen el acceso al subrecurso proxy de los nodos entre las vías de escalada de privilegios.
Peor aún: quien pueda alcanzar el puerto del kubelet de un nodo con credenciales válidas habla con él directamente; esas llamadas nunca llegan al API server y nunca aparecen en el audit log. Ahí el control son los ajustes de autenticación y autorización del kubelet.
Distinguir trabajo de administración de abuso
El analizador de la página principal divide el exec en cuatro reglas:
| Regla | Severidad | Lógica |
|---|---|---|
| Comando interactivo en un contenedor (exec / attach) | Media | pods/exec o pods/attach permitido, por una identidad humana que no es de sistema |
| Una cuenta de servicio ejecutó un comando en un contenedor | Alta | Lo mismo, por una identidad system:serviceaccount: |
| Port-forward hacia un pod | Media | pods/portforward permitido, identidad que no es de sistema |
| API del kubelet alcanzada a través de nodes/proxy | Alta | nodes/proxy permitido, identidad que no es de sistema |
¿Por qué esta división? Los workloads casi nunca hacen exec en otros pods. Cuando una cuenta de servicio lo hace, suele ser un token sacado de un pod y reutilizado por una persona, a menudo con un user agent kubectl/ desde una IP inesperada, algo que detectan otras dos reglas (consulta robo de tokens de cuenta de servicio). Los sistemas de CI y algunos operadores hacen exec de forma legítima; añádelos explícitamente a la lista de permitidos tras comprobarlo.
Para identidades humanas, haz el triaje de cada sesión con cuatro preguntas:
- ¿Quién y desde dónde? ¿Admin conocido, rango de IPs conocido, user agent normal?
- ¿Cuándo? ¿Durante una ventana de cambios o un incidente, o a una hora rara?
- ¿Dónde? ¿Qué namespace y qué pod; es un pod privilegiado o un componente de
kube-system? - ¿Qué? El comando:
sh/bashinteractivo, o algo comocat /var/run/secrets/kubernetes.io/serviceaccount/token,curl … | sh,chroot /host,nsenter?
Un chroot /host o un nsenter --target 1 dentro de un pod con un hostPath de / o hostPID es un escape de contenedor; consulta pods privilegiados y escape de contenedores.
Consultas de búsqueda
Líneas JSON en bruto con jq:
jq -c 'select(.objectRef.subresource == "exec" or .objectRef.subresource == "attach"
or .objectRef.subresource == "portforward")
| {t: .requestReceivedTimestamp, user: .user.username, ip: .sourceIPs,
ns: .objectRef.namespace, pod: .objectRef.name,
sub: .objectRef.subresource, uri: .requestURI, code: .responseStatus.code}' audit.log
EKS, CloudWatch Logs Insights:
fields @timestamp, user.username, sourceIPs.0, objectRef.namespace, objectRef.name, requestURI
| filter @logStream like "kube-apiserver-audit"
| filter objectRef.subresource in ["exec", "attach", "portforward"]
| sort @timestamp desc
AKS, Log Analytics (tabla específica del recurso):
AKSAudit
| where RequestUri has "/exec" or RequestUri has "/attach" or RequestUri has "/portforward"
| project TimeGenerated, User, SourceIps, UserAgent, RequestUri, ResponseStatus
En GKE, filtra Cloud Logging por protoPayload.methodName:"pods.exec" (y pods.attach, pods.portforward).
Reduce la superficie de ataque
- Restringe
pods/exec,pods/attachypods/portforwarda un pequeño grupo de emergencia; son recursos RBAC separados, así que puedes concedergetsobre pods sin ellos. - No concedas
nodes/proxyni a personas ni a workloads. - Alerta en cada exec de una cuenta de servicio y en cada exec dentro de
kube-system. - Conserva el audit log: las sesiones exec son cortas y puede que el pod ya no exista mañana.
FAQ
¿Cómo se detecta kubectl exec en los audit logs de Kubernetes?
Busca eventos cuyo objectRef.subresource sea exec (o attach) sobre el recurso pods. Según las versiones del cliente y del servidor, el verbo es create o get, y el código de respuesta es 101 cuando el upgrade de streaming tiene éxito. El comando está en requestURI como parámetros command= repetidos.
¿Se puede ver lo que se tecleó dentro de una shell de kubectl exec?
No. El audit log registra la solicitud que abrió la sesión y su comando inicial, no los bytes intercambiados después. Las pulsaciones y los procesos dentro del contenedor requieren herramientas de runtime como Falco o un EDR en el nodo.
¿Aparece kubectl cp en los audit logs?
Sí, como un exec. kubectl cp ejecuta tar dentro del contenedor a través de pods/exec, así que el evento de auditoría muestra command=tar con sus argumentos en requestURI.