Saltar al contenido
sudoquest@root
Blue Team

Cerrar la puerta

Endurecer SSH antes de abrir: lo que de verdad usa sshd, entrar con clave, sin contraseñas ni root, ufw limit, y apagar lo que sobra y actualizar.

30 min de lectura

Son las seis de la mañana en el SOC. Llega un informe de exposición del servidor de la Clínica Sonrisa, la clínica dental, que abre a las ocho: desde Internet se puede entrar por SSH con contraseña, root también, hay un servicio escuchando que nadie usa y dos paquetes de seguridad sin poner. Nadie ha entrado todavía, pero los barridos que prueban contraseñas no paran. Tienes dos horas para cerrar la puerta sin dejar a la clínica sin su agenda de citas.

Endurecer (en inglés, hardening) es eso: quitar lo que sobra y cerrar lo que no tiene por qué estar abierto, antes de que haga falta. En este tema lo haces con SSH, la puerta principal de cualquier servidor Linux.

Lo que de verdad usa sshd

La configuración de sshd está en /etc/ssh/sshd_config. Casi todo viene comentado (con su valor de serie), y arriba del todo hay una línea que importa mucho:

tecnico@srv-clinica:~$ sudo grep -nE "^(Include|PermitRootLogin|PasswordAuthentication)" /etc/ssh/sshd_config
12:Include /etc/ssh/sshd_config.d/*.conf
33:PermitRootLogin yes

El Include mete ahí, en la línea 12, lo que haya en /etc/ssh/sshd_config.d/. Y en sshd gana el primer valor: si una opción sale dos veces, vale la que se lee antes. Lo de los drop-ins se lee antes que casi todo el archivo principal, así que manda sobre él.

Para no tener que hacer esas cuentas, sshd -T dice la configuración que usaría, ya resuelta:

tecnico@srv-clinica:~$ sudo sshd -T | grep -iE "^(permitrootlogin|passwordauthentication)"
permitrootlogin yes
passwordauthentication yes

El archivo principal no dice nada de contraseñas (la línea está comentada: vale lo de serie, que es yes). Pero aunque la pusieras a no ahí, seguiría en yes. El culpable está en los drop-ins:

tecnico@srv-clinica:~$ ls -l /etc/ssh/sshd_config.d
total 4
-rw------- 1 root root 27 Sep  2 10:00 50-cloud-init.conf
tecnico@srv-clinica:~$ sudo cat /etc/ssh/sshd_config.d/50-cloud-init.conf
PasswordAuthentication yes

Ejercicio

Lo que de verdad usa sshd: sshd -T y los drop-ins

srv-clinica abre a las 8:00 y el informe dice que deja entrar con contraseña. Antes de tocar nada, mira qué configuración usa sshd de verdad y de dónde sale.

Preparando la máquina…

La clave, antes que la puerta

Si quitas las contraseñas sin tener otra forma de entrar, te quedas fuera tú también. Lo primero es que el SOC pueda entrar con su clave: un par de claves, la privada en el SOC y la pública en el servidor, en el authorized_keys de la cuenta con la que se entra. El SOC te ha dejado la suya en ~/atalaya.pub:

tecnico@srv-clinica:~$ cat ~/atalaya.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBBtsWJTmPly6Hz/dPJgOz7gZM7vPZNBycHiMkAk1J/h soc@atalaya
tecnico@srv-clinica:~$ mkdir -p ~/.ssh
tecnico@srv-clinica:~$ cat ~/atalaya.pub >> ~/.ssh/authorized_keys
tecnico@srv-clinica:~$ ls -la ~/.ssh
total 12
drwxrwxr-x 2 tecnico tecnico 4096 Oct 13 06:00 .
drwxr-x--- 3 tecnico tecnico 4096 Oct 13 06:00 ..
-rw-rw-r-- 1 tecnico tecnico   93 Oct 13 06:00 authorized_keys

Así no vale. sshd es exigente con los permisos (su opción StrictModes): si la carpeta o el archivo los puede escribir alguien más que su dueño (esa w del grupo), no se fía de ellos y apunta en auth.log Authentication refused: bad ownership or modes for directory /home/tecnico/.ssh. La carpeta, solo para ti; el archivo, sin escritura para nadie más:

tecnico@srv-clinica:~$ chmod 700 ~/.ssh
tecnico@srv-clinica:~$ chmod 600 ~/.ssh/authorized_keys
tecnico@srv-clinica:~$ ls -la ~/.ssh
total 12
drwx------ 2 tecnico tecnico 4096 Oct 13 06:00 .
drwxr-x--- 3 tecnico tecnico 4096 Oct 13 06:00 ..
-rw------- 1 tecnico tecnico   93 Oct 13 06:00 authorized_keys

Para comprobar que es la clave que toca, su huella: la compara el SOC con la que tiene en su ficha.

tecnico@srv-clinica:~$ ssh-keygen -lf ~/.ssh/authorized_keys
256 SHA256:cmgqZsHYzubOGaBt+PKEpR5iE7wtxRiKRl1BDn1VShs soc@atalaya (ED25519)

Cuando el SOC vuelve a entrar, auth.log dice con qué: Accepted publickey for tecnico es con su clave; Accepted password for tecnico, todavía con contraseña.

Otra clave para la misma cuenta se añade al final, sin pisar las que ya hay (cat otra.pub | sudo tee -a /home/<cuenta>/.ssh/authorized_keys, nunca con > ni con cp). Y a quien ya no debe entrar no basta con bloquearle la contraseña (usermod -L), porque su clave sigue valiendo: se le quita del authorized_keys o se caduca la cuenta (sudo usermod -L -e 1 <cuenta>).

Ejercicio

La clave del SOC, antes que nada: authorized_keys y sus permisos

Antes de quitar las contraseñas, el SOC tiene que poder entrar con su clave. La tienes en ~/atalaya.pub: ponla en tu authorized_keys, con los permisos que pide sshd.

Preparando la máquina…

Sin contraseñas y sin root

Con la clave puesta, ya se pueden cerrar las dos puertas: PasswordAuthentication no (nadie entra con contraseña) y PermitRootLogin no (root no entra por SSH; quien administra entra con su cuenta y usa sudo). Se pueden cambiar con nano o con sed:

tecnico@srv-clinica:~$ sudo sed -i -E 's/^#?PasswordAuthentication .*/PasswordAuthentication no/; s/^#?PermitRootLogin .*/PermitRootLogin no/' /etc/ssh/sshd_config

Antes de que sshd la lea, se comprueba. sshd -t no dice nada si está bien; si hay una errata (aquí, una línea añadida al final con PasswordAuthentification, con letras de más), dice dónde:

tecnico@srv-clinica:~$ sudo sshd -t
/etc/ssh/sshd_config: line 123: Bad configuration option: PasswordAuthentification
/etc/ssh/sshd_config: terminating, 1 bad configuration options

Arreglada la línea 123, vuelve a callar. Y sshd -T dice lo que pasaría:

tecnico@srv-clinica:~$ sudo sshd -T | grep -iE "^(permitrootlogin|passwordauthentication)"
permitrootlogin no
passwordauthentication yes

Las contraseñas, aún en yes: es el drop-in. Se cambia también (o se borra):

tecnico@srv-clinica:~$ sudo sed -i 's/yes/no/' /etc/ssh/sshd_config.d/50-cloud-init.conf
tecnico@srv-clinica:~$ sudo sshd -T | grep -iE "^(permitrootlogin|passwordauthentication)"
permitrootlogin no
passwordauthentication no

Hasta aquí, sshd sigue con lo de antes: lo que está escrito no vale hasta que lo vuelva a leer. Se le pide con reload:

tecnico@srv-clinica:~$ sudo systemctl reload ssh
tecnico@srv-clinica:~$ sudo journalctl -u ssh -n 5 --no-pager
Oct 13 06:00:21 srv-clinica systemd[1]: Reloading OpenBSD Secure Shell server...
Oct 13 06:00:21 srv-clinica sshd[141]: Received SIGHUP; restarting.
Oct 13 06:00:21 srv-clinica systemd[1]: Reloaded OpenBSD Secure Shell server.
Oct 13 06:00:21 srv-clinica sshd[141]: Server listening on 0.0.0.0 port 22.
Oct 13 06:00:21 srv-clinica sshd[141]: Server listening on :: port 22.

Desde ahora, quien intente entrar con contraseña recibe Permission denied (publickey).: la única puerta que queda es la de las claves. La contraseña de root sigue valiendo en la consola; si no debe valer en ningún sitio, sudo passwd -l root la bloquea (sudo sigue funcionando).

Ejercicio

Sin contraseñas y sin root: sshd_config, sshd -t y reload

Con la clave del SOC ya puesta, que nadie entre por SSH con contraseña y que root no entre. Cuidado con el drop-in, y comprueba antes de recargar.

Preparando la máquina…

Frenar las ráfagas: ufw limit

El SSH sigue abierto a Internet (el proveedor de la agenda entra desde fuera, con su clave). Sin contraseñas ya no hay nada que adivinar, pero los barridos siguen llenando los registros. ufw limit los frena: deja pasar las conexiones, pero corta a la IP que abra 6 en 30 segundos.

tecnico@srv-clinica:~$ sudo ufw status numbered
Status: active

   To                         Action      From
   --                         ------      ----
[ 1] OpenSSH                    ALLOW IN    Anywhere
[ 2] 443/tcp                    ALLOW IN    Anywhere
[ 3] 8080/tcp                   ALLOW IN    Anywhere
[ 4] OpenSSH (v6)               ALLOW IN    Anywhere (v6)
[ 5] 443/tcp (v6)               ALLOW IN    Anywhere (v6)
[ 6] 8080/tcp (v6)              ALLOW IN    Anywhere (v6)
tecnico@srv-clinica:~$ sudo ufw limit OpenSSH
Rule updated
Rule updated (v6)
tecnico@srv-clinica:~$ sudo ufw status numbered
Status: active

   To                         Action      From
   --                         ------      ----
[ 1] OpenSSH                    LIMIT IN    Anywhere
[ 2] 443/tcp                    ALLOW IN    Anywhere
[ 3] 8080/tcp                   ALLOW IN    Anywhere
[ 4] OpenSSH (v6)               LIMIT IN    Anywhere (v6)
[ 5] 443/tcp (v6)               ALLOW IN    Anywhere (v6)
[ 6] 8080/tcp (v6)              ALLOW IN    Anywhere (v6)

La regla no se ha añadido: se ha cambiado (de ALLOW a LIMIT), en su sitio.

Ejercicio

Frenar las ráfagas: ufw limit

El SSH tiene que seguir abierto (el proveedor de la agenda entra desde fuera), pero sin dejar probar contraseñas a ráfagas. ufw lo frena: limit corta a quien abra 6 conexiones en 30 segundos.

Preparando la máquina…

Lo que sobra y lo que falta

Cada servicio escuchando es una puerta más. ss -tlnp dice qué escucha y qué programa es:

tecnico@srv-clinica:~$ sudo ss -tlnp
State     Recv-Q    Send-Q       Local Address:Port        Peer Address:Port    Process
LISTEN    0         2048               0.0.0.0:8080             0.0.0.0:*        users:(("agenda-pruebas",pid=401,fd=5))
LISTEN    0         4096         127.0.0.53%lo:53               0.0.0.0:*        users:(("systemd-resolve",pid=102,fd=4))
LISTEN    0         128                0.0.0.0:22               0.0.0.0:*        users:(("sshd",pid=141,fd=3))
LISTEN    0         2048               0.0.0.0:443              0.0.0.0:*        users:(("agenda",pid=400,fd=5))
LISTEN    0         128                   [::]:22                  [::]:*        users:(("sshd",pid=141,fd=4))

La agenda de citas (agenda, en el 443) es lo que la clínica usa. La del 8080 es la copia de pruebas que dejó el instalador: sobra. Se para y se deshabilita a la vez, para que no vuelva al reiniciar:

tecnico@srv-clinica:~$ sudo systemctl disable --now agenda-pruebas
Removed /etc/systemd/system/multi-user.target.wants/agenda-pruebas.service.

Y lo que falta, las actualizaciones. apt update dice si hay algo esperando, y apt list --upgradable, qué:

tecnico@srv-clinica:~$ sudo apt update
Hit:1 http://archive.ubuntu.com/ubuntu jammy InRelease
Get:2 http://archive.ubuntu.com/ubuntu jammy-updates InRelease [128 kB]
Hit:3 http://archive.ubuntu.com/ubuntu jammy-backports InRelease
Get:4 http://security.ubuntu.com/ubuntu jammy-security InRelease [129 kB]
Fetched 257 kB in 1s (214 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
2 packages can be upgraded. Run 'apt list --upgradable' to see them.
tecnico@srv-clinica:~$ apt list --upgradable
Listing... Done
libssl3/jammy-updates,jammy-security 3.0.2-0ubuntu1.30 amd64 [upgradable from: 3.0.2-0ubuntu1.29]
openssl/jammy-updates,jammy-security 3.0.2-0ubuntu1.30 amd64 [upgradable from: 3.0.2-0ubuntu1.29]

Las dos vienen de jammy-security: son arreglos de seguridad (de OpenSSL, la biblioteca que cifra casi todo). sudo apt upgrade las pone. En la sala, eso son 20 minutos de reloj: piénsalo antes.

Ejercicio

Lo que sobra y lo que falta: ss, systemctl disable --now y apt upgrade

El informe dice que hay algo escuchando que no debería y paquetes de seguridad sin poner. Apaga lo que sobra sin tocar la agenda de citas, y actualiza.

Preparando la máquina…

En la sala: antes de abrir

En la sala vuelves al SOC, a las seis de la mañana, con el informe de exposición de la clínica en la pared. Mientras el servidor siga expuesto, las alertas suben de tono; a las ocho abre la clínica. Cada orden gasta minutos, como en el tema 1, y el orden importa: la clave del SOC antes de quitar las contraseñas, sshd -t antes del reload, y nada que tire la agenda de citas o deje fuera al proveedor.