Una API de desarrollo funciona en tu equipo. El navegador abre la página y las pruebas pasan. Pero hay otra pregunta que esas comprobaciones no responden: ¿desde dónde se puede conectar alguien a ese servicio?
Un proceso puede estar disponible únicamente para aplicaciones de la misma máquina o aceptar conexiones por sus interfaces de red. Cambiar esa decisión puede reducir exposición sin apagar la aplicación. Nmap ayuda a observar el resultado; para interpretarlo necesitamos combinar sus hallazgos con la configuración del servicio y el punto desde el que hacemos la prueba.
Este artículo parte de un laboratorio ejecutado en Kali Linux ARM64, dentro de UTM en un Mac. Creamos un servidor HTTP con datos ficticios, comprobamos su acceso desde Kali y desde el Mac, restringimos su dirección de escucha y repetimos las pruebas. No se escaneó la red completa ni se utilizaron objetivos públicos.
Superficie de ataque, servicios y puertos
La superficie de ataque reúne los puntos por los que un sistema recibe interacciones potencialmente hostiles: endpoints, interfaces de administración, entradas de archivos o servicios de red, entre otros. Aquí estudiaremos una parte concreta: un servicio HTTP sobre TCP.
Un servicio de red es un proceso que ofrece una función a otros procesos mediante comunicaciones de red. Para aceptar conexiones TCP, un servidor prepara un socket y escucha en una combinación de dirección y puerto. La dirección determina dónde recibe conexiones; el puerto permite distinguir ese servicio de otros que utilizan el mismo equipo.
Que 8080/tcp esté abierto no prueba una vulnerabilidad. Puede ser exactamente lo que necesita una aplicación. La decisión de seguridad requiere más contexto: quién necesita conectarse, qué autenticación existe, qué datos procesa el servicio y cómo se mantiene. Un servicio innecesario o accesible desde más lugares de los previstos sí merece revisión.
Este enfoque complementa la metodología de pentesting web: primero delimitamos y observamos; después evaluamos el significado de la evidencia.
Qué puede decir Nmap sobre un puerto
Nmap es una herramienta de descubrimiento y reconocimiento de redes. Puede examinar puertos y realizar sondeos para identificar servicios. No convierte automáticamente cada respuesta en una vulnerabilidad confirmada.
Para este laboratorio nos interesan tres estados:
| Estado | Interpretación en un escaneo TCP |
|---|---|
open | Hay una aplicación aceptando conexiones en el destino observado |
closed | El destino responde, pero no hay una aplicación escuchando en ese puerto y dirección |
filtered | Nmap no consigue determinar si está abierto porque el filtrado impide obtener una respuesta concluyente |
El estado depende del origen, la ruta y el tipo de prueba. Un mismo servicio puede verse abierto desde un punto y filtrado desde otro. Nmap distingue también otros estados; esta tabla no pretende cubrirlos todos. Referencia oficial: estados de puertos.
No observamos filtered en este laboratorio. Lo explicamos para interpretar futuros resultados; no cambiamos reglas de firewall para fabricar ese escenario.
Preparar un laboratorio limitado y verificable
El laboratorio se realizó el 8 de noviembre de 2026 en una VM con Kali Linux ARM64, alojada en UTM sobre macOS. Se utilizaron Nmap 7.991, Python 3.14.8, curl 8.22.0 e iproute2 7.2.0. Estas versiones documentan el entorno de la prueba; no son requisitos exactos para repetir el ejercicio.
Comprobar herramientas, red y puerto
En una terminal de Kali:
uname -m
command -v nmap python3 curl ss
nmap --version
python3 --version
curl --version
ss -V
ip -brief -4 address
ss -ltn 'sport = :8080'La arquitectura observada fue aarch64. Anota la IPv4 de la interfaz de la VM si después vas a realizar la prueba desde el Mac. No confundas esa dirección con la del host ni copies rangos de red como objetivos de escaneo.
En la última orden, -l selecciona sockets en escucha, -t TCP y -n evita resolver nombres. El filtro limita la consulta al puerto elegido. Manual de ss.
Antes de iniciar el servidor, ss mostró únicamente la cabecera: no había un socket TCP escuchando en 8080. Si en tu entorno aparece una fila, no detengas un servicio desconocido. Elige otro puerto libre y úsalo consistentemente en todo el ejercicio.
Comprobamos también el estado inicial con Nmap:
nmap -sT -Pn -n -p 8080 --reason 127.0.0.1El resultado inicial fue closed, con motivo conn-refused. Veamos por qué el comando está deliberadamente acotado:
-sTpide un escaneo TCP connect: utiliza conexiones del sistema operativo y no requiere paquetes raw nisudoen este entorno. Puede generar registros en el servicio.-p 8080limita los puertos examinados a uno.-nevita consultas DNS de resolución de nombres.-Pnomite el descubrimiento previo de hosts. Lo usamos porque el único objetivo es nuestro localhost conocido.--reasonañade la razón del estado informado.
-Pn no demuestra que un equipo esté activo. La línea Host is up puede reflejar esa suposición; hay que examinar las respuestas posteriores. Técnicas TCP, descubrimiento de hosts.
Crear únicamente contenido ficticio
En la misma terminal:
LAB_DIR=$(mktemp -d /tmp/nmap-lab.XXXXXXXX)
mkdir "$LAB_DIR/site"
cat > "$LAB_DIR/site/index.html" <<'HTML'
<h1>Laboratorio Nmap</h1>
<p>Solo datos ficticios.</p>
HTML
printf 'Directorio del laboratorio: %s\n' "$LAB_DIR"Conserva esa terminal: allí seguirá definida LAB_DIR. Servimos exclusivamente site, no tu carpeta personal ni el repositorio de un proyecto. No coloques archivos .env, claves ni enlaces simbólicos dentro de ella.
El módulo http.server es apropiado para esta demostración, pero Python no lo recomienda para producción. El propósito es observar la escucha y las respuestas, no construir un servidor endurecido. Documentación oficial.
Escenario A: escuchar en todas las interfaces IPv4
Inicia el servidor en la primera terminal y déjalo ejecutándose:
python3 -m http.server 8080 \
--bind 0.0.0.0 \
--directory "$LAB_DIR/site"--bind selecciona la dirección de escucha y --directory el directorio servido. Puedes comprobar las opciones disponibles con python3 -m http.server --help. Interfaz de línea de comandos de Python.
En una segunda terminal de Kali:
ss -ltn 'sport = :8080'
curl --noproxy '*' --include --silent --show-error \
--max-time 3 http://127.0.0.1:8080/La fila relevante de ss fue:
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 5 0.0.0.0:8080 0.0.0.0:*Mira Local Address:Port. 0.0.0.0 en un binding IPv4 significa aceptar conexiones dirigidas a las direcciones locales de las interfaces; no es una lista de clientes autorizados ni significa automáticamente «publicado en Internet». La ruta y los filtros siguen importando. La columna Peer Address de un socket en escucha no demuestra que haya clientes conectados. Linux ip(7).
curl recibió HTTP 200 y el HTML ficticio. Este extracto omite cabeceras de fecha y longitud:
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.14.8
Content-type: text/html
<h1>Laboratorio Nmap</h1>
<p>Solo datos ficticios.</p>--noproxy '*' evita que la comprobación use un proxy configurado en el entorno; consultamos directamente el destino del laboratorio. Manual de curl. Nmap nos dirá si se puede conectar por TCP; curl añade una comprobación concreta del protocolo HTTP y del recurso /.
Escenario B: reconocer el servicio, no adivinarlo por el número
Repite el escaneo limitado:
nmap -sT -Pn -n -p 8080 --reason 127.0.0.1Resultado observado:
PORT STATE SERVICE REASON
8080/tcp open http-proxy syn-ack¿Instalamos un proxy? No. Sin detección de versiones, la columna SERVICE puede proceder de la asociación del número de puerto con el catálogo de Nmap. El nombre no demuestra qué proceso está ejecutándose.
Ahora solicita detección del servicio:
nmap -sT -Pn -n -sV --version-light -p 8080 127.0.0.1En esta ejecución obtuvimos:
PORT STATE SERVICE VERSION
8080/tcp open http SimpleHTTPServer 0.6 (Python 3.14.8)-sV envía sondeos para reconocer el protocolo y la aplicación a partir de sus respuestas. --version-light reduce la intensidad de esos sondeos; no convierte la operación en pasiva ni garantiza identificar todos los servicios. Detección de servicios y versiones de Nmap.
Aquí la identificación coincide con el servidor que iniciamos. En otro sistema, una cabecera puede estar modificada, un intermediario puede responder o una versión puede incorporar parches sin cambiar el identificador visible. Para evaluar una vulnerabilidad, contrasta producto, configuración, paquetes y aviso del fabricante; no derives un CVE únicamente de esta línea.
Captura auténtica de la terminal de Kali mostrando las salidas guardadas durante el laboratorio. El servidor ya estaba detenido al tomar la captura; se conserva la fecha original del escaneo.
Escenario C: mantener el servicio, reducir su alcance
Vuelve a la primera terminal y detén únicamente el servidor de prueba con Ctrl+C. Inícialo de nuevo, cambiando solo el binding:
python3 -m http.server 8080 \
--bind 127.0.0.1 \
--directory "$LAB_DIR/site"Repite desde Kali:
ss -ltn 'sport = :8080'
curl --noproxy '*' --include --silent --show-error \
--max-time 3 http://127.0.0.1:8080/
nmap -sT -Pn -n -p 8080 --reason 127.0.0.1La fila de escucha cambió:
LISTEN 0 5 127.0.0.1:8080 0.0.0.0:*Y el escaneo de localhost siguió mostrando:
PORT STATE SERVICE REASON
8080/tcp open http-proxy syn-ackEse open es el resultado correcto. El proceso continúa escuchando en loopback y curl dentro de Kali volvió a recibir HTTP 200. También repetimos -sV --version-light: identificó el mismo servidor HTTP de Python.
La comprobación desde el Mac hacia la IP privada de Kali pasó de HTTP 200 con el binding 0.0.0.0 a un fallo de conexión con 127.0.0.1. curl terminó con código de salida 7, sin respuesta HTTP. Ese error no equivale a una clasificación filtered de Nmap.
Comparar sin confundir alcance con disponibilidad
| Comprobación | Binding 0.0.0.0 | Binding 127.0.0.1 |
|---|---|---|
ss, dirección local | 0.0.0.0:8080 | 127.0.0.1:8080 |
Nmap contra 127.0.0.1 desde Kali | open | open |
| curl a localhost desde Kali | HTTP 200 | HTTP 200 |
Detección -sV desde Kali | HTTP, servidor de Python | HTTP, servidor de Python |
| curl desde el Mac hacia la IP privada de Kali | HTTP 200 | Fallo de conexión, salida 7 |
Todos los resultados de esta tabla fueron observados. El cambio redujo el alcance del socket sin apagar la funcionalidad local. No modificamos autenticación, contenido ni firewall.
El experimento cubre IPv4. :: y ::1 pertenecen a IPv6; un despliegue real debe revisar ambas familias y sus ajustes. Tampoco extrapoles directamente a contenedores: su localhost pertenece a su espacio de red. Si trabajas con ellos, revisa además redes y publicación de puertos en Docker Compose.
Binding, firewall y segmentación resuelven problemas distintos
El binding elige las direcciones locales donde escucha el proceso. Un firewall permite o bloquea tráfico según su política. La segmentación separa zonas y ayuda a controlar qué comunicaciones pueden cruzarlas. Una VLAN por sí sola no expresa toda la política de autorización entre zonas: revisa también el enrutamiento y las reglas que lo acompañan.
Para aplicar lo aprendido, usaría este orden de revisión:
- Necesidad: elimina o detén servicios que no tengan una función justificada.
- Alcance: utiliza loopback cuando el único consumidor sea local. Si hay consumidores remotos, identifica interfaces y orígenes necesarios.
- Acceso: establece reglas de red acordes con esos consumidores y comprueba el acceso desde los puntos pertinentes.
- Privilegios: ejecuta el servicio con una identidad que solo pueda leer y modificar lo que necesita. No uses root por comodidad.
- Aplicación: aplica autenticación, autorización, actualizaciones y protección del transporte según el caso.
- Operación: registra responsable, finalidad, exposición prevista y fecha de revisión; repite la comprobación tras cambios relevantes.
Es una propuesta de trabajo, no una certificación de seguridad. Restringir el binding no evita que otro proceso local acceda al servicio, ni neutraliza por sí solo una SSRF, un túnel o un proxy que lo haga alcanzable por otra vía.
Para una API de producción, continúa con los controles esenciales de seguridad en Node.js. La protección de interfaces administrativas se desarrolla también en seguridad de switches y redes de gestión.
Cerrar el laboratorio y conservar lo que demuestra
En la primera terminal, detén el servidor con Ctrl+C. En la segunda, comprueba:
ss -ltn 'sport = :8080'
nmap -sT -Pn -n -p 8080 --reason 127.0.0.1Al finalizar nuestra prueba, ss no mostró sockets en escucha en ese puerto y Nmap informó:
PORT STATE SERVICE REASON
8080/tcp closed http-proxy conn-refusedAhora sí dejó de existir la escucha local. No es lo mismo que restringirla a localhost.
La segunda captura reúne las salidas reales guardadas: primero el servicio restringido a loopback y, debajo, el resultado tras detenerlo. No son estados simultáneos.
En la primera terminal puedes retirar los archivos ficticios que creaste, conservando por separado las evidencias:
rm -- "$LAB_DIR/site/index.html"
rmdir -- "$LAB_DIR/site"
rmdir -- "$LAB_DIR"Se eliminan archivos concretos y directorios vacíos, sin búsquedas amplias ni borrado de otros proyectos. En nuestra ejecución, los procesos de prueba quedaron detenidos, se retiró el directorio servido y se conservaron las salidas para revisión. La VM continuó encendida.
Lo importante es desde dónde responde
Un inventario de puertos cobra valor cuando puedes explicar cada resultado: qué proceso lo abre, quién necesita utilizarlo y desde qué lugar responde. En este laboratorio, localhost permaneció abierto después de reducir la exposición. Buscar simplemente «cero puertos abiertos» habría llevado a una interpretación equivocada.
La próxima vez que levantes un servicio, combina tres perspectivas: su socket con ss, su accesibilidad TCP con Nmap y una petición de aplicación con curl. Después comprueba el acceso desde el origen que realmente te interesa. Esa cadena de evidencias es más útil que convertir una etiqueta de escáner en un diagnóstico automático.
Referencias
- Nmap: libro y guía oficial.
- Nmap: estados de los puertos.
- Nmap: técnicas de escaneo TCP.
- Nmap: descubrimiento de hosts y
-Pn. - Nmap: detección de servicios y versiones.
- Python:
http.server. - Linux: direcciones y sockets IPv4, ip(7).
- Linux: consulta de sockets con ss(8).
- curl: opciones y códigos de salida.


