projects / port-scanner-es

Port Scanner

Live

Un escáner de puertos TCP en Python. Lee banners de los puertos abiertos y pone nombre a los servicios habituales. Sin Nmap ni Scapy.

overview

Port Scanner

Enfoque

Un escáner TCP connect en Python. Sin Nmap ni Scapy. Usa la biblioteca estándar (socket, threading, queue y selectors) más colorama para el color.

El motor por defecto, threads, hace que cada hilo de trabajo saque un puerto de una queue.Queue y haga un connect_ex() bloqueante. Con --engine select, las conexiones son no bloqueantes y se multiplexan con selectors.

El objetivo puede ser un nombre de host, una dirección IPv4 o una IPv6. Un nombre de host se resuelve una sola vez, antes de escanear, y primero a IPv4. --ipv6 fuerza IPv6 para los nombres de host. Una conexión del escáner consigo mismo no se cuenta como abierta.

Si no pasas --no-banner, intenta leer un banner de cada puerto abierto:

  • Los puertos 80, 8000, 8008, 8080 y 8888 reciben HEAD / HTTP/1.0.
  • Los puertos TLS (443 y 8443) no se sondean.
  • Los puertos 21, 22 y 25 se anuncian solos, así que solo escucha, hasta --timeout.
  • Cualquier otro puerto abierto se escucha como máximo 0,5 s.

La respuesta se reduce a ASCII imprimible, las rachas de espacios en blanco pasan a ser un solo espacio y se corta a 200 caracteres, así que los datos binarios no pueden romper la salida. Da nombre a 39 puertos conocidos, según el número de puerto y no según el tráfico.

Solo se toma un lock cuando un puerto resulta estar abierto, para añadir el resultado e imprimirlo. Los puertos abiertos se imprimen en vivo y al final se vuelve a imprimir la lista ordenada por puerto.

En Windows, los puertos cerrados en direcciones de loopback se rechazan tras unos 2 s salvo que la opción de retransmisión de SYN esté desactivada. El escáner la desactiva solo para objetivos de loopback (SIO_TCP_INITIAL_RTO mediante ctypes), y nunca para ninguna otra dirección.

Límites: es solo un escaneo TCP connect. El handshake completo se produce, así que aparece en los registros del objetivo. Un puerto filtrado y uno cerrado se muestran igual, como "no abierto". No envía nada a servicios que no sean HTTP.

Los tests se ejecutan con python -m unittest. Cubren el análisis de puertos, la limpieza de banners, los dos motores en IPv4 e IPv6, la ruta del HEAD, la autoconexión, el JSON, Ctrl+C y los errores de la CLI. Solo se conectan a 127.0.0.1 y ::1. Un workflow de GitHub Actions los ejecuta en Ubuntu y Windows, con Python 3.10 y 3.13. Lo desarrollé y probé con Python 3.13 en Windows 10.

Uso

pip install -r requirements.txt

El único requisito es colorama.

python portscanner.py <objetivo> [--ports 1-1024] [--threads 500] [--output archivo.txt] [--no-banner] [--timeout 1.0] [--engine threads|select] [--ipv6] [--json] [--version]
# rango por defecto, 1-1024
python portscanner.py <objetivo>

# puertos concretos y un rango, guardados en un archivo
python portscanner.py <objetivo> --ports 22,80,8000-8100 --output informe.txt

# sin banners, motor select
python portscanner.py <objetivo> --no-banner --engine select

# JSON por la salida estándar, y también guardado en un archivo
python portscanner.py <objetivo> --json --output informe.json
  • --ports acepta un rango (1-1024), una lista (22,80,443) o ambas cosas (22,80,8000-8100). Los puertos válidos van del 1 al 65535. Una entrada incorrecta se rechaza con un mensaje que nombra la parte errónea.
  • --threads vale 500 por defecto, va de 1 a 1000 y nunca es mayor que el número de puertos. Con --engine select es el número de conexiones en vuelo, de 1 a 4096.
  • --timeout vale 1,0 s por defecto. Se aplica a cada conexión y a un banner de saludo.
  • --output ARCHIVO guarda un informe en texto plano ordenado por puerto. Con --json el informe es JSON. --json también imprime JSON por la salida estándar en lugar de la tabla, y los avisos van a stderr.

Ctrl+C detiene el escaneo tras los puertos en curso, imprime y guarda lo que ha encontrado, y marca el informe como "interrupted".

Códigos de salida:

  • 0: el escaneo terminó.
  • 1: error en ejecución, como un host que no se resuelve o un archivo de salida que no se puede escribir.
  • 2: argumentos no válidos.
  • 130: interrumpido.

El grupo de descargas tiene portscanner.py, README.md, requirements.txt, LICENSE, .gitignore, benchmark.py, los tests y el workflow de GitHub Actions. La licencia es GPL v3.

Stack

Python, con socket, threading, queue y selectors de la biblioteca estándar, más colorama.

Uso responsable

Escanea solo equipos que sean tuyos o para los que tengas permiso. Escanear sin autorización puede ser ilegal según dónde estés. El escáner imprime este recordatorio en cada ejecución.