La lista de puertos abiertos es la lista de caminos hacia su servidor. Todo lo demás —cortafuegos, WAF, detección de intrusiones— se construye encima. Por eso «qué está escuchando aquí» va primero en cualquier auditoría, y por eso es también la comprobación que más a menudo da un resultado desagradable: la mitad de lo que aparece no lo abrió usted, sino el instalador de algún paquete.

Leer la salida

sudo ss -tulpn

Las opciones: t para TCP, u para UDP, l solo lo que escucha, p el proceso, n que los números sigan siendo números. Sin sudo, la columna del proceso queda vacía y el ejercicio pierde el sentido.

Lo que importa es la columna Local Address:Port, y la diferencia ahí es fundamental:

  • 127.0.0.1:3306: el servicio solo es accesible desde la propia máquina. Eso está bien;
  • 0.0.0.0:3306: desde cualquier dirección IPv4, es decir, desde internet. Eso es lo que hay que revisar;
  • [::]:3306: lo mismo para IPv6. Una línea aparte, y se pasa por alto con regularidad;
  • 203.0.113.25:443: en una dirección concreta, normalmente a propósito.

Después, para cada línea con 0.0.0.0 o [::], hágase una pregunta: ¿debe un desconocido poder llegar hasta aquí? Para 80 y 443 la respuesta es sí. Para casi todo lo demás, no.

Los hallazgos habituales

Redis, puerto 6379. La línea más peligrosa que existe. Por omisión Redis no exige contraseña, y sus comandos permiten escribir un archivo en el disco, es decir, una clave ajena en authorized_keys. Entre la aparición de Redis en una dirección pública y su aprovechamiento pasan horas, a veces menos. Compruebe bind 127.0.0.1 y protected-mode yes en la configuración.

Memcached, 11211/UDP. Aunque dentro no haya nada valioso, su servidor se convierte en amplificador de ataques ajenos, y la reclamación llegará del proveedor.

MySQL y PostgreSQL, 3306 y 5432. Hay contraseña, pero se prueba contra ella sin parar, y las versiones de bases de datos se actualizan menos a menudo de lo deseable. Casi nunca necesitan estar expuestas: la aplicación vive en la misma máquina y para su trabajo basta un túnel SSH.

Elasticsearch 9200, MongoDB 27017. Históricamente sin autenticación por omisión. Las instancias públicas de estos servicios son una fuente constante de noticias sobre filtraciones.

La API de Docker, 2375. Un puerto de control de Docker abierto es root en el anfitrión sin contraseña alguna. Suele aparecer tras experimentos con acceso remoto a Docker.

Paneles de gestión y phpMyAdmin en sus puertos: 8080, 8083, 10000. No es que nunca deban abrirse, pero son ellos los que concentran el grueso de los intentos.

Arreglarlo en el servicio, no en el cortafuegos

Las ganas de cerrar cada hallazgo con una regla de UFW se entienden, pero esa es la segunda línea, no la primera. Una regla puede borrarse por error, un cortafuegos apagarse temporalmente durante una depuración, y Docker publica puertos esquivando UFW por completo. Un ajuste de vinculación en la configuración del servicio sobrevive a todo eso:

  • MySQL/MariaDB: bind-address = 127.0.0.1;
  • PostgreSQL: listen_addresses = 'localhost';
  • Redis: bind 127.0.0.1 ::1;
  • Docker Compose: publicación como "127.0.0.1:5432:5432".

El cortafuegos se pone encima como seguro, no en su lugar.

Encontrar el proceso cuando no está claro

ss da el nombre y el pid. Después:

sudo systemctl status <pid>
sudo lsof -i :8080

El primer comando nombra la unidad de systemd a la que pertenece el proceso, y eso suele bastar para entender qué es y si hace falta. Un proceso desconocido que escucha en un puerto alto y se ha lanzado fuera de los directorios del sistema —desde /tmp o /dev/shm, por ejemplo— ya no es una cuestión de configuración sino motivo de una investigación aparte.

La comprobación desde fuera es obligatoria

ss responde a «qué está escuchando», no a «hasta qué se puede llegar». Entre ambas cosas están el cortafuegos, el NAT y las reglas del proveedor. La única respuesta honesta la da un escaneo desde otra máquina:

nmap -Pn -p- 203.0.113.25
nmap -Pn -p- -6 2001:db8::1

No omita el segundo comando: casi todo VPS tiene dirección IPv6, sus reglas se escriben aparte y el servicio escucha en ambas versiones del protocolo a la vez.

No es una comprobación puntual

La lista de puertos abiertos cambia sola. Instaló un paquete y trajo un servicio que abrió un puerto. Actualizó un panel y este restauró un valor por omisión. Lanzó un contenedor y publicó un puerto esquivando el cortafuegos. Una comprobación puntual responde por el día de hoy y por nada más.

El valor no está en la lista en sí, sino en sus cambios: un puerto nuevo que ayer no estaba es una señal breve y muy elocuente. Así es como conviene mirarla: como una instantánea con histórico, y no como una salida de ss recordada de memoria. La página de demostración de abajo muestra exactamente eso.