Todo el marco de la lección anterior —sujetos, dominios, matriz de acceso, mínimo privilegio— descansa sobre una palabra que aún no hemos definido: identidad. Un dominio de protección se asigna a un sujeto, y el sujeto se identifica por un número. El núcleo de Linux no conoce a meteora, ni a nuria, ni a carlos: conoce el 990, el 1002 y el 1001. Todo lo demás —el nombre, la contraseña, el grupo, la shell— vive en ficheros de texto de /etc que el núcleo ni siquiera lee.

Eso tiene una consecuencia que conviene interiorizar desde el primer minuto: la identidad en UNIX es una convención de espacio de usuario sostenida sobre un número. Si borras la línea de meteora de /etc/passwd, los 17.280.000 bytes de 2026-08-31.dat siguen perteneciendo al UID 990 y ls -l los muestra como 990. Si creas otro usuario con el 990, ese usuario es el dueño de los datos de Meteora sin haber tocado un solo fichero. El número es la identidad; el nombre es una etiqueta.

Esta lección recorre la cadena completa: los identificadores y por qué cada proceso lleva tres UID —lo que explica del todo el passwd setuid de 04-06—; los tres ficheros de /etc campo a campo; el ciclo de vida de una cuenta y el momento del que más incidentes salen, la baja; la autenticación en sí, con cómo se guarda una contraseña, qué significa el $y$j9T$... de /etc/shadow y qué ataques la amenazan —descritos a nivel conceptual, para poder defenderse—; PAM, la pieza que decide si entras y que casi nadie entiende hasta que la rompe; la autenticación por clave pública de SSH, que es la que usarás de verdad en meteo-01; y la elevación controlada con sudo, con el catálogo defensivo de los errores de configuración que convierten una cuenta normal en root.

Contenido

  1. UID, GID y los tres identificadores de un proceso
  2. Grupos suplementarios y la lectura de id, whoami y groups
  3. /etc/passwd, /etc/shadow y /etc/group campo a campo
  4. Cuentas de sistema frente a cuentas humanas
  5. Gestión y ciclo de vida de una cuenta
  6. Autenticación: los tres factores
  7. Cómo se almacena una contraseña
  8. Ataques a credenciales y sus contramedidas
  9. PAM: la arquitectura que decide si entras
  10. Autenticación por clave pública con SSH
  11. Elevación controlada de privilegios: su y sudo
  12. Escalada de privilegios: catálogo defensivo y auditoría

UID, GID y los tres identificadores de un proceso

Cada proceso lleva un juego de identificadores numéricos que el núcleo consulta en cada comprobación de acceso. Los de usuario son tres, y esa multiplicidad no es un capricho histórico: resuelve un problema concreto.

Identificador Abreviatura Para qué sirve
UID real ruid Quién eres: quién lanzó el proceso. Contabilidad, señales, auditoría
UID efectivo euid Qué puedes hacer: es el que el núcleo comprueba en cada acceso
UID guardado suid Qué podrías recuperar: copia que permite bajar el privilegio y volver a subirlo

Recupera el passwd setuid de 04-06 y ahora tendrá sentido completo. El usuario joan (UID 1000) lo ejecuta:

Antes del execve:   ruid=1000  euid=1000  suid=1000
Tras el execve:     ruid=1000  euid=0     suid=0
                      ↑          ↑
              quién lo lanzó   privilegio con
              (para auditar)   el que actúa

El programa necesita las dos cosas a la vez: euid=0 para escribir en /etc/shadow, y ruid=1000 para saber de quién debe cambiar la contraseña. Si solo existiera el efectivo, passwd no tendría forma fiable de saber quién lo invocó y cualquiera podría cambiar la contraseña de cualquiera. Los tres identificadores existen, en resumen, para que autoridad e identidad puedan ser distintas y ambas estén disponibles.

El guardado habilita el patrón más importante para escribir servicios seguros: soltar el privilegio.

/* El ingestor abre el socket privilegiado y baja a UID 990 para siempre */
abrir_socket_de_estaciones();                    /* necesita privilegio */

if (setgroups(0, NULL) != 0) return 1;           /* ORDEN CRÍTICO: */
if (setgid(990)        != 0) return 1;           /*   grupos → GID → UID */
if (setuid(990)        != 0) return 1;

if (setuid(0) == 0) { fprintf(stderr, "FATAL: privilegio recuperable\n"); return 1; }
bucle_principal();                               /* ya sin privilegio */

Tres detalles de los que dependen vulnerabilidades reales. El orden es obligatorio: setgroups y setgid van antes de setuid, porque después de perder el UID 0 ya no hay permiso para cambiar los grupos y el proceso conservaría los grupos suplementarios de root. setuid() llamado por root cambia los tres identificadores a la vez, y por eso la bajada es irreversible; con seteuid() el guardado seguiría siendo 0 y un atacante recuperaría root con una llamada. Y la verificación final no es paranoia: comprueba que la bajada fue definitiva, y su ausencia ha causado escaladas documentadas en software muy conocido. Con los GID ocurre lo mismo, y el bit setgid actúa sobre el efectivo igual que setuid sobre el suyo.

Grupos suplementarios y la lectura de id, whoami y groups

Un proceso pertenece a un grupo primario y a varios suplementarios, y para las comprobaciones de 04-06 cualquiera de ellos sirve para entrar en la clase de grupo.

$ id
uid=1001(carlos) gid=1001(carlos) grupos=1001(carlos),4(adm),27(sudo),990(meteora)
$ whoami        # equivale a `id -un`: el nombre del UID EFECTIVO
carlos
$ id meteora    # consultar otra identidad sin ser ella
uid=990(meteora) gid=990(meteora) grupos=990(meteora)

uid=1001(carlos) es el UID efectivo con su nombre traducido; gid=1001 es el grupo primario, que se asigna por defecto a los ficheros que cree —salvo que el directorio tenga setgid, como /var/lib/meteora—; y la lista grupos= son todos sus grupos. Los tres de carlos son tres decisiones de seguridad distintas y conviene que lo sean: adm le da lectura de los registros, sudo le habilita la elevación y meteora el acceso al grupo del servicio. Es separación de privilegios de 05-01 aplicada a una persona.

Un detalle que sorprende: whoami informa del efectivo, no del real. Dentro de un proceso setuid root dice root aunque lo lanzara joan. Para saber quién hay detrás se usa id -ru o, en una sesión interactiva, who am i, que consulta el registro de sesiones y sobrevive a los su encadenados.

Los grupos suplementarios se calculan al iniciar sesión y se heredan por fork. Un usermod -aG meteora carlos no afecta a las sesiones abiertas ni a los procesos arrancados. id mostrará el grupo nuevo porque relee /etc/group, pero el proceso seguirá sin el acceso: el núcleo usa las credenciales que el proceso lleva encima, no el fichero. Hay que reiniciar la sesión o el servicio.

La forma rápida de distinguirlo: id lee el fichero, y grep Groups /proc/<pid>/status muestra los grupos reales del proceso. Si difieren, ese es el problema.

/etc/passwd, /etc/shadow y /etc/group campo a campo

/etc/passwd: siete campos separados por dos puntos

root:x:0:0:root:/root:/bin/bash
carlos:x:1001:1001:Carlos Ruiz,Sistemas,,:/home/carlos:/bin/bash
meteora:x:990:990:Servicio Meteora,,,:/var/lib/meteora:/usr/sbin/nologin
# Campo Valor en meteora Qué significa
1 Nombre meteora El nombre de inicio de sesión. Único
2 Contraseña x Marcador histórico: el hash está en /etc/shadow
3 UID 990 La identidad real ante el núcleo
4 GID primario 990 Grupo por defecto de los ficheros que cree
5 GECOS Servicio Meteora,,, Campo libre: nombre, despacho, teléfonos
6 Directorio /var/lib/meteora El $HOME de la cuenta
7 Shell /usr/sbin/nologin Programa que se ejecuta al iniciar sesión

La x del campo 2 es historia viva: hasta principios de los noventa el hash estaba ahí mismo, en un fichero legible por todos porque muchos programas necesitan traducir UID a nombre. Cuando el poder de cómputo hizo viables los ataques por diccionario, los hashes se movieron a /etc/shadow, legible solo por root, y quedó la x como marca. Es un ejemplo perfecto de reducción de superficie: el dato sensible se saca del fichero que todo el mundo necesita leer.

El campo 7, /usr/sbin/nologin, es un programa real que imprime un aviso y sale con error; la diferencia con /bin/false es cosmética, pero dejar el campo vacío sí importa, porque entonces se usa /bin/sh.

Por qué meteora no tiene shell. Es mínimo privilegio aplicado a la identidad: la cuenta existe solo para ser dueña de unos ficheros y ser la identidad de unos procesos que arranca systemd. Si un atacante obtiene una credencial de meteora, no puede usarla para entrar por SSH ni abrir sesión: solo le sirve si ya está dentro. Una capa entera de defensa por una palabra en el campo 7.

/etc/shadow: nueve campos, y el que importa es el segundo

carlos:$y$j9T$FvB2kXqR8mNpL4wZ$3xKm9Qv...:20330:1:365:14:30::
meteora:!:20120:0:99999:7:::
# Campo Ejemplo Significado
1 Nombre carlos Enlaza con /etc/passwd
2 Hash $y$j9T$... La contraseña derivada (apartado 7)
3 Último cambio 20330 Días desde el 1/1/1970 en que se cambió
4 Días mínimos 1 No puede volver a cambiarla antes de 1 día
5 Días máximos 365 Caduca al año
6 Aviso 14 Avisa 14 días antes de caducar
7 Inactividad 30 Días de gracia tras caducar antes de bloquear
8 Caducidad (vacío) Fecha absoluta de expiración de la cuenta
9 Reservado (vacío) Sin uso

Los cuatro valores posibles del campo 2 se confunden constantemente: $y$... es una contraseña válida; ! o !hash es cuenta bloqueada (passwd -l), un bloqueo reversible porque el hash se conserva detrás y passwd -u lo restaura; * significa que nunca ha tenido contraseña ni la tendrá, lo normal en cuentas de paquetes; y vacío significa acceso sin credencial, que es una emergencia. Por eso meteora:!: es lo correcto para una cuenta de servicio.

Y un matiz decisivo para el apartado 5: bloquear la contraseña no bloquea el acceso por clave SSH, porque son mecanismos de autenticación distintos.

/etc/group: cuatro campos

adm:x:4:carlos,syslog
sudo:x:27:carlos
meteora:x:990:

Nombre, marcador de contraseña, GID y lista de miembros suplementarios. La sutileza: los miembros cuyo grupo primario es este NO aparecen en la lista. meteora:x:990: está vacío y sin embargo el usuario meteora pertenece al grupo, porque lo tiene como primario en /etc/passwd. Por eso id nuria es más fiable que leer /etc/group: combina las dos fuentes.

Dos reglas al editar estos ficheros. Usa vipw, vipw -s y vigr en lugar del editor a pelo: bloquean contra ediciones simultáneas y validan la sintaxis —un /etc/passwd corrupto deja el sistema sin poder resolver ningún nombre de usuario—. Y valida con pwck y grpck tras cualquier cambio manual.

Cuentas de sistema frente a cuentas humanas

Cuenta de sistema Cuenta humana
Ejemplo meteora (990), www-data (33) carlos (1001), nuria (1002)
Rango de UID en Debian 1–999 1000 en adelante
Shell /usr/sbin/nologin /bin/bash
Contraseña Bloqueada (! o *) Hash válido
Directorio Funcional (/var/lib/meteora) o inexistente /home/usuario
Inicio de sesión interactivo Nunca
Ciclo de vida El del servicio El de la relación laboral

La separación por rango no es una regla del núcleo: es una convención que aplican useradd y login leyendo /etc/login.defs. Su valor es operativo: permite escribir auditorías del tipo "toda cuenta con UID ≥ 1000 debe tener segundo factor" o "ninguna con UID < 1000 debe tener shell". Así se creó meteora:

sudo groupadd --system --gid 990 meteora
sudo useradd  --system --uid 990 --gid meteora --no-create-home \
              --home-dir /var/lib/meteora --shell /usr/sbin/nologin \
              --comment "Servicio Meteora" meteora
sudo passwd -l meteora          # bloquear explícitamente la contraseña

Cada opción es una decisión. --system sitúa el UID en el rango de servicios y evita crear un grupo personal. --no-create-home es correcto porque /var/lib/meteora lo crea el paquete con los permisos 2750 de 04-06, no useradd con los suyos. --shell /usr/sbin/nologin cierra el inicio de sesión. Y passwd -l es explícito aunque useradd --system ya deje la cuenta bloqueada: la seguridad se declara, no se supone.

Gestión y ciclo de vida de una cuenta

# ALTA de una persona
sudo useradd --create-home --shell /bin/bash --comment "Nuria Vidal,,," nuria
sudo passwd nuria && sudo chage -d 0 nuria       # forzar cambio en el primer acceso

# CAMBIO DE ROL
sudo usermod -aG adm nuria                       # ¡la -a es obligatoria!
sudo gpasswd -d nuria meteora                    # quitar de un grupo

# CADUCIDAD
sudo chage -M 365 -m 1 -W 14 -I 30 nuria         # máx, mín, aviso, inactividad
sudo chage -E 2027-06-30 externo_temporal        # fecha fin de un contrato
sudo chage -l nuria                              # consultar el estado

El error clásico está en la línea del cambio de rol: usermod -G sin la -a sustituye toda la lista de grupos suplementarios. Un usermod -G adm carlos bien intencionado lo deja fuera de sudo y de meteora, y el efecto no se nota hasta que alguien necesita esos accesos. La -a significa append, y la regla es no escribir nunca -G sin ella.

graph LR
    A["ALTA<br/>Cuenta y grupos<br/>mínimos"] --> B["OPERACIÓN<br/>Revisión periódica<br/>de accesos"]
    B --> C["CAMBIO DE ROL<br/>Añadir lo nuevo<br/><b>y QUITAR lo viejo</b>"]
    C --> B
    B --> D["BAJA<br/>Cerrar TODOS los<br/>caminos de acceso"]
    D --> E["ARCHIVO<br/>Reasignar<br/>la propiedad"]

Los incidentes se acumulan siempre en los dos mismos puntos.

El cambio de rol y la acumulación de privilegios. Cuando alguien cambia de puesto se le añaden los accesos nuevos y casi nunca se le quitan los viejos; tras cinco años y tres equipos acumula acceso a media empresa sin que nadie lo haya decidido. Es la violación silenciosa del mínimo privilegio, y la única defensa es la revisión periódica de accesos: cada seis meses, comprobar que cada grupo y cada regla de sudo siguen justificados. Es aburrido y funciona.

La baja, que es el mayor riesgo real del ciclo. Una cuenta viva tras la salida de una persona es una credencial válida sin dueño ni supervisión. Y el error habitual no es olvidar la baja, sino hacerla a medias. Tiene cinco bloques y ninguno sustituye a otro: cerrar los cuatro caminos de acceso (contraseña, shell, caducidad de la cuenta y authorized_keys), retirar los privilegios (grupos, reglas de sudo, ACL, tareas de cron), cortar las sesiones vivas, inventariar y reasignar los ficheros, y rotar los secretos que esa persona conocía. El procedimiento completo con sus comandos es el ejercicio 2.

El paso más olvidado es retirar authorized_keys, y es demoledor: usermod -L bloquea la contraseña pero no impide entrar con clave pública, porque SSH ni consulta el hash cuando la autenticación es por clave. El segundo más olvidado es cortar sesiones: bloquear una cuenta no expulsa a quien ya está dentro.

Sobre borrar la cuenta con userdel -r: hazlo solo después de archivar lo necesario. Y ahí llega la advertencia obligada: la retención de datos de personas, los registros de acceso y los plazos de conservación tienen implicaciones legales (RGPD y normativa laboral); antes de fijar una política de borrado, consúltala con el responsable de cumplimiento normativo o con asesoría jurídica. No es una decisión técnica.

Autenticación: los tres factores

Autenticar es demostrar que eres quien dices ser, y todos los métodos caben en tres familias:

Factor Base Ejemplos Debilidad
Algo que sabes Conocimiento Contraseña, PIN, frase de paso Se adivina, se reutiliza, se comparte, se filtra
Algo que tienes Posesión Clave SSH, TOTP, llave FIDO2 Se pierde, se roba, se puede clonar según el tipo
Algo que eres Biometría Huella, cara, iris No se puede cambiar si se compromete

La autenticación multifactor (MFA) exige elementos de dos familias distintas: contraseña más pregunta de seguridad no es MFA —ambas son "algo que sabes"—; contraseña más código TOTP sí. Es la separación de privilegios de 05-01 aplicada a la identidad: dos condiciones independientes en vez de una.

La debilidad de la biometría merece énfasis porque suele venderse como la más fuerte: una huella comprometida es un problema permanente. Puedes cambiar una contraseña en diez segundos; no puedes cambiar de dedos. Por eso se usa correctamente como factor local —desbloquear un dispositivo que guarda la clave real— y no como credencial que viaje por la red. Para meteo-01 el objetivo es claro: SSH con clave pública (algo que tienes) protegida por frase de paso (algo que sabes), y segundo factor para el acceso desde fuera de la red de gestión.

Cómo se almacena una contraseña

Regla sin excepciones: una contraseña nunca se guarda, ni en claro ni cifrada de forma reversible. Se guarda el resultado de pasarla por una función de derivación de clave (KDF), y al autenticar se repite la operación y se comparan los resultados.

Por qué no vale SHA-256: porque está diseñado para ser rápido, y esa velocidad juega a favor del atacante —una GPU calcula miles de millones por segundo—. Una KDF de contraseñas se diseña con tres propiedades opuestas: sal única y aleatoria, que hace que dos usuarios con la misma contraseña produzcan hashes distintos e invalida las tablas precalculadas; coste de trabajo ajustable, para hacerla deliberadamente lenta y subir el coste según mejora el hardware; y coste en memoria, que anula la ventaja de las GPU y los ASIC, con mucho cálculo y poca memoria por unidad.

Algoritmo Prefijo Coste en memoria Veredicto
DES tradicional (13 caracteres) No Obsoleto: solo 8 caracteres útiles
MD5-crypt $1$ No Obsoleto
SHA-256/512-crypt $5$, $6$ No Aceptable con muchas rondas
bcrypt $2b$ Poco Bueno, muy probado desde 1999
yescrypt $y$ Por defecto en Debian actual
Argon2id $argon2id$ Recomendación actual para desarrollo nuevo

Y así se lee el campo real de /etc/shadow:

$y$j9T$FvB2kXqR8mNpL4wZ$3xKm9QvW7rT2yH5nB8cF4dG6jK1mP0sA9zX3vC7bN2e
 │  │        │                              │
 │  │        │                              └─ HASH: resultado de la derivación
 │  │        └──────────────────────────────── SAL: aleatoria, distinta por usuario
 │  └───────────────────────────────────────── PARÁMETROS: coste de tiempo y memoria
 └──────────────────────────────────────────── ALGORITMO: y = yescrypt

La sal se guarda en claro y está bien que así sea: no es un secreto, su función es garantizar que cada contraseña se ataque por separado, y el sistema la necesita para repetir el cálculo. Al autenticar, el sistema lee la línea, extrae algoritmo, parámetros y sal, recalcula con exactamente esos valores y compara en tiempo constante, para no filtrar información por el tiempo de respuesta.

grep ENCRYPT_METHOD /etc/login.defs      # ENCRYPT_METHOD YESCRYPT
grep YESCRYPT_COST /etc/login.defs       # YESCRYPT_COST_FACTOR 5

El factor de coste es la palanca que compensa el avance del hardware: subirlo multiplica el trabajo de cada intento, para el sistema y para el atacante. El criterio práctico es fijarlo para que verificar una contraseña cueste entre 100 y 500 milisegundos en el hardware de producción: imperceptible para quien se autentica una vez, devastador para quien prueba millones. Vale igual para cualquier aplicación propia: nunca implementes tu esquema, usa libxcrypt, bcrypt o argon2 con parámetros revisados.

Ataques a credenciales y sus contramedidas

Descritos a nivel conceptual, para poder defenderse. No hay aquí instrucciones ni herramientas ofensivas: cada fila se cierra con la contramedida, que es lo que nos interesa.

Ataque En qué consiste (concepto) Contramedida en meteo-01
Diccionario Probar palabras y variaciones habituales Longitud mínima alta, comprobación contra listas filtradas
Fuerza bruta Recorrer el espacio de combinaciones Longitud: cada carácter multiplica el espacio
Tablas precalculadas Consultar hashes calculados de antemano La sal: las inutiliza por completo
Reutilización Probar credenciales filtradas de otros servicios Gestor de contraseñas, contraseñas únicas, MFA
Pulverización Una contraseña muy común contra muchas cuentas Detección por origen, no solo por cuenta; MFA
Interceptación Capturar la credencial en tránsito TLS y SSH siempre; nunca protocolos en claro
Ingeniería social Conseguir que la persona la entregue Formación, verificación, MFA resistente al phishing (FIDO2)

La conclusión cuantitativa que ordena media tabla: la longitud vence a la complejidad. Con 95 caracteres imprimibles, cada carácter adicional multiplica el espacio de búsqueda por 95, mientras que sustituir una a por una @ lo multiplica por menos de dos —y los diccionarios de ataque conocen esa sustitución desde hace décadas—. Una frase de paso de cuatro palabras poco relacionadas es más resistente y mucho más memorable que P@ssw0rd!, que además cumple formalmente cualquier política de complejidad. Por eso las guías modernas recomiendan exigir longitud, no complejidad, y eliminar la caducidad periódica obligatoria, que solo produce incrementos predecibles: es la aceptabilidad psicológica de 05-01 convertida en política.

sudo apt install libpam-pwquality
# /etc/security/pwquality.conf
minlen = 14          # longitud: la defensa principal
minclass = 3         # tres clases de caracteres, sin exigir las cuatro
dictcheck = 1        # rechaza palabras de diccionario
usercheck = 1        # rechaza variaciones del nombre de usuario

# /etc/security/faillock.conf
deny = 5             # 5 fallos...
unlock_time = 900    # ...bloquean 15 minutos
fail_interval = 900  # contados en una ventana de 15 minutos
even_deny_root = 0   # OJO: bloquear root puede dejarte fuera

Tres comentarios que evitan problemas reales. deny=5 con unlock_time=900 convierte la fuerza bruta en inviable: 480 intentos al día frente a los millones por segundo que el ataque necesita. even_deny_root=0 es deliberado: si un atacante puede bloquear root probando contraseñas, ha conseguido una denegación de servicio contra la administración justo cuando más falta hace. Y el desbloqueo temporal es preferible al permanente, porque un bloqueo permanente convierte cualquier pulverización en la parálisis de la organización entera. Se consulta con faillock --user carlos y se limpia con --reset.

PAM: la arquitectura que decide si entras

Antes de PAM, cada programa que autenticaba —login, su, sshd, passwd— llevaba su propio código, y añadir un método nuevo obligaba a recompilarlos todos. PAM (Pluggable Authentication Modules) mete una indirección: los programas llaman a la biblioteca, y esta consulta un fichero que decide qué módulos ejecutar y en qué orden.

graph LR
    A["sshd / login / sudo"] --> B["libpam"] --> C["/etc/pam.d/&lt;servicio&gt;"]
    C --> D["auth<br/>¿es quien dice ser?"]
    C --> E["account<br/>¿puede entrar AHORA?"]
    C --> F["password<br/>cambiar credencial"]
    C --> G["session<br/>preparar y cerrar"]
Pila Pregunta Módulos típicos
auth ¿Es quien dice ser? pam_unix, pam_faillock, pam_u2f, TOTP
account Siendo quien es, ¿puede entrar ahora? pam_nologin, pam_time, pam_access
password ¿Cómo se cambia la credencial? pam_pwquality, pam_unix
session ¿Qué preparar antes y limpiar después? pam_limits, pam_systemd, pam_mkhomedir

La distinción entre auth y account es la que más cuesta y la más útil: una cuenta caducada se autentica correctamente —la contraseña es válida— y aun así no entra, porque la pila account lo deniega. Son dos decisiones independientes.

Bandera Si el módulo falla Si tiene éxito
required La pila fallará, pero se sigue ejecutando hasta el final Continúa
requisite Corta inmediatamente y devuelve el fallo Continúa
sufficient Se ignora y se continúa Éxito inmediato si nada required anterior falló
optional Se ignora, salvo que sea el único módulo Continúa

required frente a requisite tiene una justificación elegante: required sigue ejecutando la pila aunque ya sepa que va a fallar, para que un atacante no deduzca en qué punto falló midiendo el tiempo de respuesta.

# /etc/pam.d/sshd — AUTENTICACIÓN
auth       required     pam_faillock.so preauth silent    # ¿bloqueada por fallos?
auth       [success=1 default=ignore] pam_unix.so         # contraseña UNIX
auth       required     pam_faillock.so authfail          # registra el fallo
auth       required     pam_deny.so                       # denegación final
auth       required     pam_permit.so
# CUENTA
account    required     pam_nologin.so   # ¿existe /etc/nologin? nadie entra
account    required     pam_unix.so      # ¿caducada? ¿bloqueada?
account    required     pam_faillock.so  # ¿superó el umbral de fallos?
# CONTRASEÑA
password   requisite    pam_pwquality.so retry=3
password   required     pam_unix.so obscure yescrypt shadow
# SESIÓN
session    required     pam_limits.so    # aplica /etc/security/limits.conf
session    required     pam_unix.so      # registra inicio y fin en wtmp
session    optional     pam_systemd.so   # crea la sesión de systemd

La pila auth tiene truco. [success=1 default=ignore] significa "si pam_unix tiene éxito, salta 1 línea", con lo que se salta authfail y llega a pam_permit.so, que concede el acceso. Si la contraseña falla, no salta: ejecuta authfail —que incrementa el contador— y luego pam_deny.so. Es un salto condicional, y en cuanto lo ves así cualquier fichero de PAM se lee sin dificultad. En account, pam_nologin implementa un mecanismo muy útil: si existe /etc/nologin, nadie salvo root inicia sesión y se muestra su contenido; es la forma estándar de vaciar un servidor antes de una intervención.

Un cambio de política de ejemplo, restringir SSH a unos grupos y a un horario:

# En /etc/pam.d/sshd, dentro de la pila account:
account    required     pam_access.so
account    required     pam_time.so

# /etc/security/access.conf — lista blanca, y denegación final
+ : root carlos (admins) : ALL
- : ALL : ALL

# /etc/security/time.conf — becarios, solo en horario laboral
sshd ; * ; becarios ; Wk0800-1900

access.conf se lee de arriba abajo y la primera línea que casa decide: se permite explícitamente a root, carlos y el grupo admins, y la última deniega todo lo demás. Es exactamente el principio de valores por defecto seguros de 05-01.

Advertencia práctica. Un error en /etc/pam.d/ puede dejarte fuera del sistema de forma irrecuperable, incluido root. Mantén una sesión root abierta, abre otra para verificar el cambio, y ten a mano la consola física o el KVM. Si la nueva falla, la que sigue abierta te permite deshacer.

Autenticación por clave pública con SSH

La criptografía asimétrica, en dos párrafos. Se generan dos claves relacionadas: una privada, que no sale nunca de tu máquina, y una pública, que puedes repartir sin riesgo. Lo que se hace con una solo se deshace con la otra, y —esto es lo esencial— conocer la pública no permite deducir la privada.

Para autenticar, el servidor envía un dato aleatorio y pide que se firme con la privada; quien la tiene produce una firma que el servidor verifica con la pública que ya guarda. La clave privada nunca viaja por la red, ni siquiera cifrada. Compáralo con una contraseña, que sí viaja —protegida por el canal, pero viaja— y que el servidor debe procesar: si el servidor está comprometido, tu contraseña se filtra; tu clave privada, no.

# 1. Generar el par (en TU máquina, nunca en el servidor)
ssh-keygen -t ed25519 -a 100 -C "carlos@portatil-2026" -f ~/.ssh/id_meteo01
#   -a 100 : rondas que protegen la clave con la frase de paso. Ponla SIEMPRE.
# -rw------- id_meteo01       ← PRIVADA: modo 600
# -rw-r--r-- id_meteo01.pub   ← pública: se reparte

# 2. Instalarla en el servidor y comprobar permisos (sshd los EXIGE)
ssh-copy-id -i ~/.ssh/id_meteo01.pub carlos@meteo-01
# drwx------  ~/.ssh          -rw-------  ~/.ssh/authorized_keys

# 3. El agente: teclear la frase de paso UNA vez por sesión
eval "$(ssh-agent -s)" && ssh-add -t 8h ~/.ssh/id_meteo01

Los puntos que hay que entender. La clave se genera en tu máquina: si la generas en el servidor, la privada ha existido en un sistema que no controlas del todo y ha pasado por sus discos y sus copias. La frase de paso la convierte de facto en un doble factor: quien robe el fichero necesita además conocerla. Los permisos son obligatorios, no una recomendación: sshd rechaza un authorized_keys, un ~/.ssh o incluso un $HOME demasiado abiertos, porque quien pudiera escribir ahí añadiría su propia clave. Y el agente existe por aceptabilidad psicológica: sin él, teclear la frase cincuenta veces al día lleva derecho a eliminarla; -t 8h limita el daño si alguien se deja la sesión abierta.

# /etc/ssh/sshd_config.d/99-meteora.conf
PermitRootLogin no                  # nunca root directo: entra y luego sudo
PasswordAuthentication no           # SOLO claves públicas
KbdInteractiveAuthentication no     # cierra la otra vía de contraseña
AuthenticationMethods publickey
AllowGroups admins                  # lista blanca de quién puede entrar
MaxAuthTries 3
LoginGraceTime 30
AllowAgentForwarding no             # evita reutilizar tu agente desde el servidor
ClientAliveInterval 300

Cada línea corresponde a un principio de 05-01. PermitRootLogin no es separación de privilegios y trazabilidad: obliga a entrar con la cuenta nominal y elevar con sudo, de modo que cada acción queda atribuida a una persona en lugar de a un root compartido. PasswordAuthentication no elimina de golpe diccionario, fuerza bruta, pulverización y reutilización: si no hay contraseña que probar, no hay ataque de contraseña. AllowGroups es lista blanca. Y AllowAgentForwarding no cierra un riesgo poco conocido: con reenvío activo, alguien con root en el servidor puede usar tu agente para saltar a otras máquinas donde tu clave vale.

Antes de cerrar la sesión: valida con sudo sshd -t, recarga con systemctl reload ssh y abre una segunda sesión para comprobar sin cerrar la primera. Deshabilitar las contraseñas sin haber probado la clave es la forma más común de perder el acceso a un servidor remoto.

Elevación controlada de privilegios: su y sudo

Un administrador necesita privilegios de vez en cuando, no todo el día. Trabajar siempre como root viola el mínimo privilegio de la peor manera: un rm -rf mal escrito y no hay red de seguridad. Hay dos formas de subir, y son muy distintas.

su sudo
Contraseña que pide La del usuario destino (la de root) La tuya propia
Granularidad Todo o nada: te conviertes en el usuario Por comando, usuario y máquina
Duración Una shell entera Un comando (con caché de minutos)
Trazabilidad "Alguien hizo su"; lo demás es anónimo Cada comando queda registrado con quién
Secreto compartido La contraseña de root la conoce todo el equipo Nadie necesita conocerla
Revocar a una persona Cambiar la contraseña de root y avisar a todos Quitar una línea de sudoers
Veredicto Legado; útil para su - meteora al depurar La forma correcta

La fila decisiva es la del secreto compartido: con su, la contraseña de root la conocen cinco personas y ninguna acción es atribuible; con sudo, la contraseña de root puede no existir siquiera —en Debian está bloqueada por defecto— y retirar el acceso es borrar una línea.

visudo no es opcional. Comprueba la sintaxis antes de guardar y se niega a escribir un fichero inválido; un /etc/sudoers con un error deja el sistema sin sudo, y si además PermitRootLogin no está activo y root no tiene contraseña, has perdido toda vía de administración salvo la consola física. La sintaxis de una regla es quién dónde = (como quién) qué comandos:

## --- Nivel 0: acceso total (el grupo sudo de Debian) ---
%sudo   ALL=(ALL:ALL) ALL

## --- Nivel 1: solo lo que el equipo necesita ---
Cmnd_Alias METEORA_SVC = /usr/bin/systemctl start meteo-api, \
                         /usr/bin/systemctl stop meteo-api,  \
                         /usr/bin/systemctl restart meteo-api
%meteora-ops  ALL=(root) METEORA_SVC

## --- Nivel 2: sin contraseña SOLO para lo inocuo y automatizado ---
%monitoring   ALL=(root) NOPASSWD: /usr/bin/systemctl status meteo-api

## --- Higiene general ---
Defaults  env_reset, timestamp_timeout=5, passwd_tries=3
Defaults  secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Defaults  logfile="/var/log/sudo.log", log_input, log_output

Los cuatro elementos importantes. Los Cmnd_Alias agrupan comandos y hacen la política legible, que es economía del mecanismo aplicada a la configuración. env_reset y secure_path son críticos: sin ellos, un usuario podría manipular PATH o variables como LD_PRELOAD para que el comando privilegiado cargue código suyo. timestamp_timeout=5 son los minutos de caché de la contraseña: 0 la pide siempre —seguro y cargante, y ya sabes a dónde lleva eso— y valores altos dejan una ventana si alguien deja el terminal desatendido. Y log_input, log_output graban la sesión completa de los comandos elevados, valiosísimo en una investigación.

Sobre NOPASSWD: su riesgo no es teórico. Convierte cualquier compromiso de la cuenta —una sesión abierta, una clave robada— en privilegio inmediato sin ninguna barrera, eliminando el segundo factor implícito de teclear la contraseña. Úsalo solo para comandos de solo lectura y sin argumentos libres, ejecutados por automatismos que no pueden teclear, y nunca con ALL.

Y la propiedad que hace de sudo una herramienta de seguridad y no solo de comodidad: toda elevación queda registrada, con éxito y con fracaso.

sep 01 11:42:17 meteo-01 sudo[4821]: carlos : TTY=pts/0 ; PWD=/home/carlos ;
    USER=root ; COMMAND=/usr/bin/systemctl restart meteo-api
sep 01 11:45:03 meteo-01 sudo[4855]: nuria : 3 incorrect password attempts ;
    TTY=pts/2 ; USER=root ; COMMAND=/usr/bin/cat /etc/shadow

Cada línea contiene quién, desde dónde, como quién y qué. La segunda es justo el evento que debe disparar una alerta. Consulta tus propios permisos con sudo -l, los de otra persona con sudo -l -U nuria, y olvida la contraseña cacheada con sudo -k. El tratamiento sistemático de estos registros es Auditoría, Registros y Respuesta a Incidentes.

Escalada de privilegios: catálogo defensivo y auditoría

Escalada de privilegios es pasar a un dominio de protección con más autoridad —en el vocabulario de 05-01, un cambio de dominio no previsto—. La tratamos exclusivamente desde la defensa: qué configuraciones la hacen posible y cómo detectarlas. No se describen técnicas de explotación.

Categoría de fallo Por qué permite la escalada Cómo se detecta
Binarios setuid innecesarios Corren como su propietario: un fallo suyo es un fallo con privilegio find / -perm -4000 contra una referencia
Ficheros con capabilities Igual, y no se ven en ls -l getcap -r /, con referencia
Reglas de sudo amplias Muchos comandos permiten ejecutar otros o escribir ficheros arbitrarios sudo -l, revisión de sudoers.d/
Comodines en rutas Un * casa con rutas no previstas, incluidos .. Revisión manual; evitar *
Scripts privilegiados escribibles Si puedes editar lo que root ejecuta, ejecutas como root find de escribibles por grupo u otros
Tareas programadas mal permisionadas Un cron de root que ejecuta un script editable Permisos de /etc/cron*
Rutas relativas en scripts privilegiados El programa ejecutado depende del PATH Revisión de código; rutas absolutas
Secretos en el entorno o el historial Una clave visible en ps, en environ o en .bash_history grep en historiales
Servicios como root sin necesidad Un fallo del servicio es un fallo con UID 0 ps -eo user,comm --sort user
Software sin actualizar Vulnerabilidades conocidas del núcleo o las bibliotecas apt list --upgradable
#!/bin/bash
# auditoria-privilegios.sh — ejecutar como root, periódicamente
echo "[1] Cuentas con UID 0 (solo debe salir root)"
awk -F: '($3==0){print "  "$1}' /etc/passwd
echo "[2] Cuentas SIN contraseña"
awk -F: '($2==""){print "  CRÍTICO: "$1}' /etc/shadow
echo "[3] Cuentas de sistema CON shell interactiva"
awk -F: '($3<1000 && $3>0 && $7!~/nologin|false/){print "  "$1" -> "$7}' /etc/passwd
echo "[4] setuid/setgid y [5] capabilities: por DIFERENCIA con la referencia"
find / -xdev \( -perm -4000 -o -perm -2000 \) -type f 2>/dev/null | sort > /tmp/suid.now
getcap -r / 2>/dev/null | sort > /tmp/caps.now
diff /root/ref/suid.ref /tmp/suid.now | grep '^>' || echo "  setuid: sin cambios"
diff /root/ref/caps.ref /tmp/caps.now | grep '^>' || echo "  caps:   sin cambios"
echo "[6] Reglas de sudo con NOPASSWD o comodines"
grep -rnE 'NOPASSWD|\*' /etc/sudoers /etc/sudoers.d/ 2>/dev/null | grep -v '^\s*#'
echo "[7] Tareas programadas de root escribibles por otros"
find /etc/cron* /var/spool/cron -type f \( -perm -0002 -o -perm -0020 \) -ls 2>/dev/null
echo "[8] Servicios corriendo como root"
ps -eo user:16,comm --no-headers | awk '$1=="root"' | sort -u -k2 | head -30
echo "[9] Posibles secretos en historiales de shell"
grep -rlE '(password|token|api[_-]?key|secret)=' /home/*/.bash_history 2>/dev/null

Cómo se interpreta. Los bloques [1] a [3] deben tener resultados fijos y conocidos; cualquier novedad exige explicación, y una cuenta de sistema con shell es justo lo que un atacante crea para persistir. [4] y [5] son detección por diferencia: no importa la lista completa, sino lo que ha aparecido desde la última vez; el [5] es el que más se olvida y menos ruido hace, porque ls -l no muestra las capabilities. [6] no marca errores sino cosas que revisar: cada NOPASSWD y cada comodín necesita una justificación escrita. [7] busca el patrón más rentable para un atacante: un fichero que él puede escribir y que root ejecuta solo. [8] debe ser lo más corto posible. Y [9] recuerda que un secreto tecleado queda en el historial en claro y para siempre, además de haber sido visible en ps mientras el comando corría.

Cómo se diseña meteo-01 para contener un compromiso

Decisión Qué impide si meteo-api cae
meteora con nologin y contraseña bloqueada No sirve para iniciar sesión, ni por SSH ni localmente
Configuración y binario propiedad de root No puede reescribirse a sí mismo ni persistir así
Ningún binario setuid propio del servicio No hay vía de subida a root en el paquete
/var/lib/meteora con nosuid,nodev,noexec No puede ejecutar un binario que deposite ahí
NoNewPrivileges=yes en la unidad No gana privilegio ni ejecutando un setuid del sistema
CapabilityBoundingSet= reducido No puede montar, cargar módulos ni depurar procesos
meteora no está en sudo ni tiene reglas No hay ninguna regla que pueda invocar
Perfil de AppArmor sin reglas de ejecución (05-01) No consigue una shell, primer paso de casi todo
Registros con grupo adm y chattr +a No puede borrar sus huellas fácilmente

Ninguna línea basta por sí sola. Juntas convierten un compromiso de la aplicación en un incidente acotado a la aplicación, que es el objetivo: no evitar todos los fallos —imposible— sino que un fallo no se propague.

Recordatorio de responsabilidad. Todo lo anterior sirve para auditar y defender sistemas propios. Cualquier comprobación de seguridad sobre un sistema ajeno requiere autorización expresa y por escrito del responsable; sin ella puede constituir un delito con independencia de la intención. Y las decisiones sobre cuentas, registros de acceso y conservación de datos personales tienen implicaciones de RGPD y de normativa laboral: revísalas con cumplimiento normativo o asesoría jurídica.

Errores Comunes y Consejos

Creer que passwd -l cierra una cuenta. Bloquea la contraseña y no toca la autenticación por clave SSH. Una baja completa exige cuatro acciones —contraseña, nologin, caducidad y authorized_keys— más cortar las sesiones vivas.

Usar usermod -G sin -a. Sustituye toda la lista de grupos suplementarios en lugar de añadir, y deja a la persona fuera de sudo y de lo que tuviera. El efecto aparece días después.

Esperar que un cambio de grupo tenga efecto inmediato. Los grupos se fijan al iniciar sesión. id muestra el cambio porque relee el fichero, pero el proceso conserva sus credenciales: compara con /proc/<pid>/status y reinicia la sesión o el servicio.

Editar /etc/sudoers sin visudo. Un error de sintaxis deja el sistema sin sudo, y con root sin contraseña y sin acceso SSH directo has perdido la administración. Usa visudo y ficheros separados en /etc/sudoers.d/.

NOPASSWD: ALL "temporalmente". Elimina la única barrera ante una sesión secuestrada. Si sudo molesta tanto, sube timestamp_timeout; no elimines la contraseña.

Generar la clave SSH en el servidor. La privada debe nacer y morir en tu máquina; si la generas allí, ha existido en un sistema que quizá no controlas y ha pasado por sus copias de seguridad.

Deshabilitar PasswordAuthentication sin haber probado la clave. Es la forma más habitual de quedarse fuera de un servidor remoto. Valida, recarga y abre una segunda sesión antes de cerrar la primera. Lo mismo vale para cualquier cambio en /etc/pam.d/.

Confundir las pilas de PAM. auth responde a "¿es quien dice ser?" y account a "¿puede entrar ahora?". Una cuenta caducada se autentica bien y aun así no entra.

Auditar setuid y olvidar getcap. Un binario con cap_dac_override no muestra ninguna marca en ls -l. Mantén dos listas de referencia y compáralas periódicamente.

Consejo: usa sudo -l como reflejo. Antes de suponer qué puedes hacer, pregúntalo; y como administrador, sudo -l -U usuario es la forma más rápida de auditar los privilegios reales de una persona, incluidos los que le llegan por grupos que ya nadie recuerda.

Ejercicios

Ejercicio 1: interpretar la identidad de un sistema

Sobre tu máquina o una máquina virtual de pruebas: (a) interpreta campo a campo tu línea de /etc/passwd y la de una cuenta de servicio; (b) identifica el algoritmo, los parámetros y la sal de un hash real de /etc/shadow, y explica qué significan !, * y el campo vacío; (c) localiza todas las cuentas con UID 0, las de sistema con shell interactiva y las que no tienen contraseña, explicando por qué importa cada comprobación; (d) demuestra empíricamente que un usermod -aG no afecta a una sesión abierta, comparando id con /proc/$$/status.

Ejercicio 2: baja segura de una cuenta

La analista nuria deja la empresa. Tenía cuenta con shell, clave SSH instalada, pertenencia a adm, una entrada de ACL sobre /var/lib/meteora/lecturas (04-06), una regla en /etc/sudoers.d/nuria, una tarea de cron diaria, ficheros en /home/nuria y en /var/lib/meteora/export, y conocía la clave de API del proveedor de datos. Escribe el procedimiento completo de baja, ordenado, con el comando exacto de cada paso, la justificación de qué quedaría abierto si se omitiera, la verificación final, y qué decisiones deben consultarse con asesoría jurídica.

Ejercicio 3: diseñar la política de sudo del equipo

Diseña /etc/sudoers.d/meteora para tres perfiles: %meteora-ops, que arranca, para, reinicia y consulta meteo-api, ingestor y agregador y lee sus registros; %meteora-dba, que ejecuta la copia de seguridad y la restauración; y %monitoring, una cuenta automatizada que solo consulta el estado, sin contraseña. Justifica los Defaults. Después explica por qué estas tres reglas alternativas serían peligrosas y qué permitirían exactamente:

%meteora-ops ALL=(root) NOPASSWD: /usr/bin/systemctl *
%meteora-dba ALL=(root) /usr/bin/tar *
%meteora-ops ALL=(root) /usr/bin/vim /etc/meteora/meteora.conf

Soluciones

Solución 1

getent passwd $USER ; getent passwd www-data          # (a)
sudo awk -F: '{print $1, substr($2,1,3)}' /etc/shadow # (b)
sudo awk -F: '($3==0){print "UID 0: "$1}' /etc/passwd                              # (c)
sudo awk -F: '($3<1000&&$3>0&&$7!~/nologin|false/){print "shell: "$1}' /etc/passwd
sudo awk -F: '($2==""){print "SIN CONTRASEÑA: "$1}' /etc/shadow

(a) carlos:x:1001:1001:Carlos Ruiz,Sistemas,,:/home/carlos:/bin/bash es nombre de sesión; x que remite a /etc/shadow; UID 1001, la identidad real ante el núcleo; GID primario; GECOS descriptivo; $HOME; y shell interactiva. La de servicio, www-data:x:33:33:...:/usr/sbin/nologin, difiere en tres puntos decisivos: UID por debajo de 1000, directorio funcional y nologin, que impide iniciar sesión aunque alguien conociera una credencial.

(b) $y$j9T$FvB2kXqR8mNpL4wZ$3xKm... se descompone por $: y es yescrypt; j9T son los parámetros de coste en tiempo y memoria; FvB2kXqR8mNpL4wZ es la sal, aleatoria por usuario y guardada en claro porque no es un secreto y el sistema la necesita para repetir el cálculo; el resto es el hash. ! es bloqueo reversible con el hash conservado detrás; * es "nunca la tuvo"; y vacío es acceso sin credencial.

(c) Las tres importan por razones distintas. Una segunda cuenta con UID 0 es root con otro nombre —la identidad es el número—, y es una técnica de persistencia que no aparece en ninguna lista de "administradores". Una cuenta de sistema con shell es una vía de inicio de sesión que no debería existir y un patrón habitual de puerta trasera. Y una cuenta sin contraseña entra sin credencial. En un sistema sano la primera devuelve solo root, la tercera nada, y la segunda solo casos justificados como sync.

(d)

sudo groupadd pruebagrp && sudo usermod -aG pruebagrp $USER
id -Gn                          # ← SÍ aparece pruebagrp (id relee /etc/group)
grep Groups /proc/$$/status     # ← NO aparece su GID (el proceso no cambió)
sg pruebagrp -c 'grep Groups /proc/$$/status'   # con credenciales nuevas, SÍ
sudo groupdel pruebagrp

La discrepancia es la demostración: id consulta el fichero, y el núcleo usa las credenciales que el proceso lleva encima desde que se creó. Los grupos suplementarios se fijan al iniciar sesión y se heredan por fork (02-01), así que solo una sesión nueva los incorpora. Es la explicación de "ya le he dado el grupo y sigue sin funcionar".

Solución 2

FECHA=$(date +%F); mkdir -p /root/bajas/nuria-$FECHA
# --- 0. INVENTARIO PREVIO (antes de tocar nada) ---
sudo -l -U nuria > /root/bajas/nuria-$FECHA/sudo.txt
sudo crontab -l -u nuria > /root/bajas/nuria-$FECHA/cron.txt 2>/dev/null
sudo find / -xdev -user nuria -ls 2>/dev/null > /root/bajas/nuria-$FECHA/ficheros.txt
# --- 1. CERRAR LOS CUATRO CAMINOS DE ACCESO ---
sudo usermod -L nuria                                    # a) contraseña
sudo usermod -s /usr/sbin/nologin nuria                  # b) shell
sudo chage -E 0 nuria                                    # c) cuenta caducada
sudo mv /home/nuria/.ssh/authorized_keys \
        /root/bajas/nuria-$FECHA/authorized_keys.revocado # d) CLAVES SSH
# --- 2. RETIRAR PRIVILEGIOS ---
sudo gpasswd -d nuria adm && sudo rm -f /etc/sudoers.d/nuria && sudo visudo -c
sudo setfacl -x u:nuria /var/lib/meteora/lecturas
sudo setfacl -x d:u:nuria /var/lib/meteora/lecturas      # también la ACL por defecto
sudo crontab -r -u nuria
# --- 3. CORTAR SESIONES VIVAS ---
sudo loginctl terminate-user nuria ; pgrep -u nuria && sudo pkill -KILL -u nuria
# --- 4. FICHEROS: evitar huérfanos ---
sudo tar --acls -czf /root/bajas/nuria-$FECHA/home.tar.gz /home/nuria
sudo chown -R carlos:meteora /var/lib/meteora/export/nuria-*
# --- 5. ROTAR SECRETOS: clave de API del proveedor y contraseñas compartidas ---
sudo systemctl restart meteo-api

Justificación. El paso 0 va primero porque si bloqueas antes de inventariar pierdes la foto de lo que tenía, y esa foto es el punto de partida de cualquier investigación posterior.

En el paso 1, los cuatro subpasos cierran caminos independientes: (a) la autenticación por contraseña; (b) la obtención de shell aunque sobreviva alguna vía; (c) el acceso desde la pila account de PAM, con independencia de cómo se autentique. Y (d) es el imprescindible y el más olvidado: sin él, nuria sigue entrando por SSH exactamente igual que antes, porque la autenticación por clave pública no consulta el hash de la contraseña.

El paso 2 existe porque los privilegios están atados al nombre y al UID, no a la capacidad de iniciar sesión: si el UID 1002 se reasigna, la persona nueva heredaría la ACL, el grupo y la regla de sudo. La ACL por defecto es una entrada distinta de la normal y hay que quitar las dos, y una tarea de cron seguiría ejecutándose aunque la cuenta esté bloqueada. El paso 3 es necesario porque bloquear no expulsa a quien ya está dentro. El paso 4 evita los huérfanos de 04-06, que serían heredados por el próximo UID 1002; --acls conserva las ACL que un tar normal perdería en silencio. Y el paso 5 parte de que un secreto conocido por alguien que ya no está debe considerarse comprometido: no se puede "desconocer".

# --- VERIFICACIÓN ---
sudo chage -l nuria ; sudo -l -U nuria ; ls -l /home/nuria/.ssh/
getfacl /var/lib/meteora/lecturas | grep nuria || echo "ACL limpia"
who | grep nuria || echo "sin sesiones"
grep -r nuria /etc/sudoers /etc/sudoers.d/ /etc/group || echo "sin referencias"

Asesoría jurídica. El borrado definitivo (userdel -r), el plazo de conservación del archivo de /home/nuria, el tratamiento de su correo y la conservación de los registros de acceso que la identifican tienen implicaciones de RGPD y de normativa laboral, donde las obligaciones de conservación pueden chocar con la minimización de datos. Deben acordarse con cumplimiento normativo o asesoría jurídica antes de ejecutarlas, y el procedimiento debe recogerlas por escrito para no improvisar en cada caso.

Solución 3

# /etc/sudoers.d/meteora — editar SIEMPRE con: visudo -f /etc/sudoers.d/meteora
# Cada verbo y cada unidad enumerados: NADA de comodines
Cmnd_Alias METEO_CTL = /usr/bin/systemctl start meteo-api,  /usr/bin/systemctl stop meteo-api,  \
                       /usr/bin/systemctl restart meteo-api,/usr/bin/systemctl status meteo-api,\
                       /usr/bin/systemctl start ingestor,   /usr/bin/systemctl stop ingestor,   \
                       /usr/bin/systemctl restart ingestor, /usr/bin/systemctl status ingestor, \
                       /usr/bin/systemctl start agregador,  /usr/bin/systemctl stop agregador,  \
                       /usr/bin/systemctl restart agregador,/usr/bin/systemctl status agregador
Cmnd_Alias METEO_LOG = /usr/bin/journalctl -u meteo-api*, /usr/bin/journalctl -u ingestor*, \
                       /usr/bin/journalctl -u agregador*
Cmnd_Alias METEO_BAK = /usr/local/sbin/meteora-backup.sh, /usr/local/sbin/meteora-restore.sh

%meteora-ops  ALL=(root) METEO_CTL, METEO_LOG
%meteora-dba  ALL=(root) METEO_BAK
%monitoring   ALL=(root) NOPASSWD: /usr/bin/systemctl status meteo-api

Defaults:%meteora-ops  timestamp_timeout=5, log_input, log_output
Defaults:%meteora-dba  timestamp_timeout=0
Defaults  env_reset, passwd_tries=3, logfile="/var/log/sudo.log"
Defaults  secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

Los Defaults. env_reset y secure_path son los críticos: impiden manipular PATH, LD_PRELOAD u otras variables para que el comando privilegiado cargue código del usuario, algo que convertiría cualquier regla, por estrecha que sea, en ejecución arbitraria como root. timestamp_timeout=5 da cinco minutos de caché a operaciones frecuentes, equilibrio entre seguridad y aceptabilidad psicológica; para %meteora-dba se pone a 0 porque restaurar una copia es destructivo y poco frecuente, y debe confirmarse siempre. log_input, log_output graba las sesiones del grupo que más toca el servicio. Y el NOPASSWD de %monitoring es aceptable solo porque el comando es único, de solo lectura, sin argumentos libres y lo ejecuta un automatismo que no puede teclear.

Por qué las tres alternativas son peligrosas.

1. NOPASSWD: /usr/bin/systemctl *. El comodín cubre todos los subcomandos y todas las unidades del sistema, no solo las de Meteora: incluye parar el cortafuegos, parar auditd o deshabilitar SSH, y sobre todo los subcomandos que crean o editan unidades —quien pueda escribir una unidad hace que el sistema ejecute lo que quiera como root en el próximo arranque—. Con NOPASSWD, equivale a conceder root sin barrera alguna. Corrección: enumerar los comandos exactos con la unidad incluida, como en METEO_CTL.

2. /usr/bin/tar *. tar no se puede acotar con un comodín, por dos razones independientes. Al extraer como root, puede escribir en cualquier ruta absoluta contenida en el archivo —/etc/sudoers.d/, /root/.ssh/authorized_keys, /etc/shadow— y el contenido del archivo lo controla quien lo proporciona. Y admite opciones que ejecutan programas externos como parte de su funcionamiento normal, que con * quedan permitidas. Corrección: un script propio con ruta fija y parámetros validados, como METEO_BAK.

3. /usr/bin/vim /etc/meteora/meteora.conf. Parece la más acotada y es igual de peligrosa: los editores completos ejecutan comandos del sistema y abren otros ficheros desde dentro, así que una vez vim corre como root la restricción del argumento es irrelevante. Lo mismo vale para less, more, man, awk o find. Corrección: sudoedit (o sudo -e), que copia el fichero a un temporal, lo edita con los permisos del usuario y lo devuelve —el editor nunca corre como root—: %meteora-ops ALL=(root) sudoedit /etc/meteora/meteora.conf.

La lección general: una regla de sudo no acota lo que el usuario puede hacer, sino lo que puede ejecutar. Ante cualquier regla, pregúntate: ¿puede este comando, con estos argumentos, escribir un fichero arbitrario o ejecutar otro programa?

Conclusión

La identidad en Linux es un número, el UID; todo lo demás vive en ficheros de /etc que el núcleo ni siquiera lee, y de ahí la consecuencia que ordena la lección: quien controla el número controla la identidad, por lo que una segunda cuenta con UID 0 es root con otro nombre. Cada proceso lleva tres UID —real, efectivo y guardado— porque autoridad e identidad deben poder ser distintas y ambas estar disponibles: es lo que permite a passwd escribir en /etc/shadow con el efectivo mientras usa el real para saber a quién cambiarle la contraseña. Y soltar privilegiosetgroups, setgid, setuid, en ese orden, y verificar que no se puede volver atrás— es el mínimo privilegio en el tiempo hecho código.

Los tres ficheros se leen campo a campo. /etc/passwd tiene siete, con la x que recuerda por qué los hashes se sacaron de un fichero legible por todos, y un séptimo campo, nologin, que es toda una capa de defensa para meteora. /etc/shadow tiene nueve, y el segundo lo dice todo: $y$... es contraseña válida, ! bloqueo reversible, * "nunca la tuvo" y vacío es una emergencia. /etc/group guarda solo los miembros suplementarios, por lo que id es más fiable que leerlo. En el ciclo de vida, los dos puntos de riesgo son universales: la acumulación de privilegios en los cambios de rol, que solo corrigen las revisiones periódicas, y la baja, que debe cerrar cuatro caminos —contraseña, shell, caducidad y authorized_keys—, cortar sesiones, reasignar ficheros y rotar secretos, porque bloquear la contraseña no cierra el acceso por clave SSH.

En autenticación, los tres factores y la regla de que MFA exige dos familias distintas. Las contraseñas no se guardan: se derivan con una KDF que aporta sal única, coste de trabajo y coste en memoria, y por eso el $y$j9T$sal$hash de Debian se lee como algoritmo, parámetros, sal y resultado, con la sal en claro porque no es un secreto. Frente a diccionario, fuerza bruta, tablas precalculadas, reutilización y pulverización, las contramedidas son concretas: longitud antes que complejidad, pam_pwquality, pam_faillock con desbloqueo temporal y, sobre todo, MFA. PAM organiza todo eso en cuatro pilas —auth, account, password, session—, con banderas que se leen sin dificultad en cuanto entiendes que [success=1] es un salto condicional, y con la distinción entre autenticarse bien y poder entrar ahora repartida en pilas separadas.

Para meteo-01 la configuración correcta es clave pública con frase de paso y agente, con PermitRootLogin no y PasswordAuthentication no: si no hay contraseña que probar desaparece toda una familia de ataques, y entrar con la cuenta nominal hace que cada acción sea atribuible. La elevación se hace con sudo, no con su: granularidad por comando, sin compartir la contraseña de root, revocable borrando una línea y con registro de cada elevación. Sus trampas son NOPASSWD, los comodines y la más sutil: una regla de sudo no acota lo que el usuario puede hacer, sino lo que puede ejecutar, así que cualquier comando capaz de lanzar otros programas o escribir rutas arbitrarias concede root aunque la regla parezca estrecha. Y el catálogo defensivo de escalada de privilegios —setuid innecesarios, capabilities invisibles en ls -l, reglas amplias, comodines, scripts escribibles, tareas mal permisionadas, secretos en el entorno, servicios como root— se audita con detección por diferencia contra una línea base, porque lo que importa no es la lista completa sino lo que ha aparecido desde la última revisión.

Ya tenemos identidad y control de acceso. Pero todo lo anterior supone que el atacante entra por la puerta: que intenta autenticarse, que abusa de una regla, que usa una credencial. ¿Y si no lo hace? ¿Qué ocurre cuando el fallo está en el código de meteo-api y no hace falta ninguna credencial para dispararlo? ¿Qué clases de error de programación lo permiten, qué defensas pone el sistema operativo por debajo —ASLR, pila no ejecutable, canarios— y cómo se comprueba que están activas? ¿Y qué medidas concretas, en orden y con su justificación, convierten un Debian recién instalado en un servidor endurecido?

Eso es Amenazas Comunes y Endurecimiento del Sistema, donde clasificaremos las amenazas de un servidor real con el indicio que cada una dejaría en meteo-01, construiremos el modelo de amenazas de Meteora, veremos las clases de vulnerabilidad de programa y las defensas del núcleo, y montaremos el endurecimiento completo: cortafuegos, aislamiento con systemd, cifrado, gestión de secretos y copias de seguridad como control de seguridad.

Fundamentos de Sistemas Operativos

Módulo 1: Introducción a los Sistemas Operativos

Módulo 2: Gestión de Recursos

Módulo 3: Concurrencia

Módulo 4: Estructuras de Archivos

Módulo 5: Protección y Seguridad del Sistema

Módulo 6: Virtualización y Contenedores

Módulo 7: Administración y Diagnóstico en la Práctica

© Copyright 2026. Todos los derechos reservados