Problema

En entornos de producción auto‑gestionados es frecuente que el acceso SSH se mantenga mediante claves públicas/privadas, mientras que la autenticación sudo sigue basada en una contraseña local. Cuando esa contraseña se pierde, el administrador queda bloqueado para ejecutar tareas que requieren privilegios de root. La solución tradicional implica arrancar el servidor en modo de recuperación o usar un live‑CD, lo que requiere acceso físico y tiempo de inactividad. En clústers Docker Swarm o Kubernetes, sin embargo, el propio motor de contenedores ya tiene la capacidad de montar el sistema de archivos del host y ejecutar con privilegios elevados. Esa característica, si se usa sin restricciones, permite modificar directamente archivos críticos como /etc/shadow y restaurar el acceso sin tocar el hardware.

Causa

El punto débil no es el contenedor en sí, sino la combinación de dos configuraciones que se encuentran en muchas instalaciones:

  1. Contenedores privilegiados (--privileged o capacidades ampliadas) que otorgan al proceso dentro del contenedor prácticamente todas las capacidades del kernel del host.
  2. Montaje del filesystem del host (-v /:/host) que expone la raíz completa del servidor al contenedor.
  3. Política de autenticación basada en contraseña para sudo, que depende exclusivamente del hash almacenado en /etc/shadow. Si el hash se pierde, no hay otro mecanismo de recuperación.

Cuando cualquiera de estos elementos está presente sin una capa de control de acceso (por ejemplo, políticas de AppArmor/SELinux o un escaneo de vulnerabilidades), un atacante que consiga ejecutar código dentro del contenedor puede escalar a root del host con un simple chroot o edición de archivos.

Solución

La estrategia consiste en usar un contenedor privilegiado de forma controlada para reemplazar el hash de la contraseña en /etc/shadow. El proceso se divide en tres fases:

  1. Preparar una imagen mínima que incluya awk, sed y openssl (cualquier distro base sirve).
  2. Ejecutar el contenedor con los flags necesarios:
    • --privileged para obtener todas las capacidades del kernel.
    • -v /:/host para montar la raíz del host bajo /host.
    • Opcionalmente, limitar la red (--network none) para evitar exfiltración.
  3. Dentro del contenedor, generar un nuevo hash con openssl passwd -6 (SHA‑512) y sobrescribir la línea correspondiente en /host/etc/shadow.

Este método es aplicable tanto a nodos individuales como a workers de un Swarm, siempre que el nodo siga aceptando despliegues de contenedores. La clave está en ejecutar la operación una única vez y luego eliminar el contenedor para cerrar la brecha.

Alternativas prácticas

  • Uso de nsenter: si el host permite nsenter desde el contenedor, se puede entrar al namespace de PID 1 y ejecutar passwd directamente, evitando la edición manual del archivo.
  • Recuperación mediante systemd‑boot: en servidores con systemd y acceso a la consola, añadir init=/bin/bash al kernel es otra vía, pero requiere reinicio físico.
  • Gestión de contraseñas con LDAP/SSSD: migrar a autenticación centralizada elimina la dependencia de un hash local y reduce la superficie de ataque.

Cuándo aplicar esta solución

  • Síntomas claros: el usuario puede iniciar sesión por SSH con claves, pero sudo rechaza la contraseña y no hay otro método de escalado.
  • Entorno Docker/Kubernetes: el nodo forma parte de un clúster y acepta despliegues de contenedores con permisos elevados.
  • Acceso físico limitado: no se dispone de consola o medio de arranque externo.

No aplicar si:

  • El clúster está configurado con políticas de seguridad estrictas (AppArmor, SELinux) que bloquean --privileged o el montaje de /.
  • El host está bajo un esquema de autenticación externa (LDAP, Kerberos) donde el hash local no es relevante.
  • La pérdida de contraseña es parte de un incidente de seguridad mayor; en ese caso, es preferible reinstalar o aislar el nodo.

Código

# 1. Crear una imagen temporal (Dockerfile)
cat > Dockerfile <<'EOF'
FROM alpine:latest
RUN apk add --no-cache openssl
EOF
docker build -t pwd-reset:latest .

# 2. Ejecutar el contenedor privilegiado
docker run --rm -i \
  --privileged \
  -v /:/host \
  --network none \
  pwd-reset:latest sh -c '
  NEW_HASH=$(openssl passwd -6 "NuevaContraseñaSegura")
  USER=$(grep "^$(whoami):" /host/etc/passwd | cut -d: -f1)
  # Sustituir el hash en /etc/shadow
  sed -i "s|^\($USER:[^:]*:\)[^:]*|\1$NEW_HASH|" /host/etc/shadow
  echo "Hash de $USER actualizado"
  '

Verificación

  1. Salir del contenedor y volver a la sesión SSH original.
  2. Ejecutar sudo -k && sudo -l para forzar la petición de contraseña.
  3. Introducir la nueva contraseña definida en el script.
  4. Si sudo concede privilegios, la operación fue exitosa.
  5. Opcional: revisar el archivo /etc/shadow del host para confirmar que el hash corresponde a la nueva contraseña (grep "^$(whoami):" /etc/shadow).

Notas adicionales

  • Eliminar el contenedor inmediatamente después de la operación. Mantener un contenedor privilegiado activo es una puerta trasera permanente.
  • Auditar los logs de Docker (/var/log/docker.log) y del sistema (/var/log/auth.log) para detectar intentos no autorizados.
  • Restringir la capacidad de lanzar contenedores privilegiados mediante políticas de autorización en Docker Swarm (docker swarm update --task-history-limit no ayuda directamente, pero combinar con RBAC y certificados firmados reduce el riesgo).
  • Rotar la contraseña de sudo y actualizar cualquier script de automatización que dependa de ella.
  • Considerar el uso de sudo sin contraseña para usuarios de confianza dentro de entornos de contenedores, siempre que se acompañe de una auditoría de comandos permitidos (/etc/sudoers.d/).
  • Backup de /etc/shadow: mantener una copia cifrada fuera del host permite restaurar rápidamente sin necesidad de contenedores privilegiados.

Con este enfoque se recupera el acceso administrativo sin interrumpir los servicios críticos y, al mismo tiempo, se evidencia la necesidad de reforzar la política de contenedores privilegiados en cualquier infraestructura auto‑gestionada.