
La red es un campo de batalla. Cada sistema expone sus puntos de entrada, sus arterias digitales abiertas al escrutinio. Ignorar la superficie de ataque es como dejar la puerta principal sin cerrar esperando que los invitados llamen. Hoy no vamos a hablar de fantasmas en la máquina, sino de cómo detectar las luces encendidas en la oscuridad: los puertos abiertos. Vamos a construir tu propia herramienta de reconocimiento. Vamos a desarmar un escáner de puertos con Python.
Tabla de Contenidos
Tabla de Contenidos
- Introducción Técnica: El Reconocimiento es Clave
- El Arsenal del Operador: Lo que Necesitas
- Taller Práctico: Escáner de Puertos TCP con Sockets
- Expandiendo Capacidades: Escaneo Básico de UDP
- Optimizando el Rendimiento: Concurrencia para la Velocidad
- Veredicto del Ingeniero: ¿Vale la Pena Construirlo?
- Preguntas Frecuentes
- El Contrato: Tu Primer Reconocimiento
Introducción Técnica: El Reconocimiento es Clave
En el mundo del pentesting y la seguridad ofensiva, la fase de reconocimiento es fundamental. Antes de lanzar cualquier ataque, debemos entender la topografía del terreno. Un escáner de puertos es una de las herramientas más básicas y efectivas para mapear la superficie de ataque de un host o una red. Identifica qué servicios están corriendo y escuchando en qué puertos, proporcionando pistas valiosas sobre posibles vulnerabilidades a explotar.
Python, con su sintaxis limpia y su vasta biblioteca estándar, incluyendo el módulo `socket`, es la elección perfecta para construir este tipo de utilidades. Olvida las herramientas genéricas por un momento; entender cómo funcionan por dentro es lo que separa a un entusiasta de un profesional.
El Arsenal del Operador: Lo que Necesitas
Antes de ensuciarte las manos con código, asegúrate de tener lo esencial. No necesitas un arsenal de última generación para empezar, pero sí las herramientas adecuadas para un análisis profesional. Para esta misión, te recomiendo:
Python 3.x: La versión más reciente es preferible por sus mejoras de rendimiento y seguridad. Si aún usas Python 2, es hora de migrar. Considera plataformas como python.org.
Un IDE o Editor de Código: Si bien puedes usar un editor de texto plano, herramientas como Visual Studio Code (con extensiones de Python) o PyCharm ofrecen funcionalidades de depuración y autocompletado que aceleran el desarrollo. Para un entorno Linux/macOS, Vim o Emacs con la configuración adecuada también son potentes.
Conocimientos de Redes TCP/IP: Entender cómo funcionan los protocolos TCP (Transmission Control Protocol) y UDP (User Datagram Protocol) es crucial. Saber qué es un socket, una dirección IP, un número de puerto y el handshake TCP te permitirá comprender el código que vas a escribir.
Un Entorno de Pruebas: ¡Nunca escanees redes sin permiso! Utiliza máquinas virtuales (VirtualBox, VMware) con sistemas operativos vulnerables (como Metasploitable) o redes de laboratorio dedicadas. Las plataformas como Hack The Box o TryHackMe ofrecen entornos legales para practicar tus habilidades.
Claro, podrías recurrir a escáneres comerciales avanzados o a herramientas integradas en suites como Kali Linux. Pero para fines didácticos y para comprender la mecánica subyacente, construir tu propio escáner es insustituible. Es el equivalente a desmontar un motor para entender cómo funciona antes de ponerte al volante de un Ferrari.
Taller Práctico: Escáner de Puertos TCP con Sockets
Vamos a empezar con lo básico: un escáner de puertos TCP. Utilizaremos el módulo `socket` de Python para intentar establecer una conexión con un puerto específico en un host de destino. Si la conexión es exitosa, el puerto está abierto.
Aquí tienes los pasos y el código. Recuerda, este es un ejemplo educativo; para pentesting profesional, herramientas optimizadas son necesarias, las cuales puedes explorar en cursos avanzados de Bug Bounty o Pentesting.
Código Fuente (Escáner TCP Básico)
import socket
import sys
from datetime import datetime
def scan_tcp_port(target_host, port):
"""Intenta conectar a un puerto TCP en el host de destino."""
try:
# Crea un objeto socket
# AF_INET indica que usaremos IPv4
# SOCK_STREAM indica que usaremos el protocolo TCP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Establece un tiempo de espera para la conexión (en segundos)
# Un tiempo de espera corto es crucial para no bloquear el escaneo.
sock.settimeout(1)
# Intenta conectarse al host y puerto
# El método connect_ex devuelve 0 si la conexión es exitosa,
# o un código de error si falla.
result = sock.connect_ex((target_host, port))
if result == 0:
print(f"Puerto {port}: Abierto")
else:
# print(f"Puerto {port}: Cerrado (Error: {result})")
pass # Opcional: comentar para ver puertos cerrados
sock.close()
except socket.gaierror:
print(f"Error: No se pudo resolver el nombre de host '{target_host}'")
sys.exit()
except socket.error:
print(f"Error: No se pudo conectar al servidor para el puerto {port}")
sys.exit()
except KeyboardInterrupt:
print("\n[!] Escaneo interrumpido por el usuario.")
sys.exit()
def main():
"""Función principal para ejecutar el escáner."""
if len(sys.argv) != 3:
print("\nUso: python port_scanner.py ")
print("Ejemplo: python port_scanner.py 192.168.1.1 1-1000\n")
sys.exit()
try:
target = sys.argv[1]
ports_range = sys.argv[2]
# Resolución del nombre de host a IP si es necesario
target_ip = socket.gethostbyname(target)
print("-" * 60)
print(f"Escaneando Host: {target} ({target_ip})")
print(f"Escaneando en Rango de Puertos: {ports_range}")
print(f"Hora de Inicio: {datetime.now()}")
print("-" * 60)
# Separar el rango de puertos
start_port, end_port = map(int, ports_range.split('-'))
for port in range(start_port, end_port + 1):
scan_tcp_port(target_ip, port)
except ValueError:
print("Error: Formato de rango de puertos inválido. Use 'inicio-fin'.")
sys.exit()
except KeyboardInterrupt:
print("\n[!] Escaneo interrumpido por el usuario.")
sys.exit()
except Exception as e:
print(f"Ocurrió un error inesperado: {e}")
sys.exit()
if __name__ == "__main__":
main()
Explicación del Código:
- Importaciones: Importamos `socket` para las operaciones de red, `sys` para leer argumentos de la línea de comandos y `datetime` para registrar la hora de inicio.
scan_tcp_port(target_host, port)
:- Crea un socket TCP (`socket.SOCK_STREAM`).
- Establece un tiempo de espera (`settimeout(1)`) para evitar que el escaneo se detenga si un puerto tarda demasiado en responder. Un valor bajo es clave para la rapidez.
- Utiliza `sock.connect_ex((target_host, port))` que devuelve 0 si la conexión es exitosa, indicando un puerto abierto. Es el método preferido sobre `connect()` porque no lanza una excepción si falla.
- Imprime si el puerto está abierto. Para un escaneo sigiloso o si solo buscas puertos abiertos, puedes omitir la impresión de puertos cerrados.
- Cierra el socket.
main()
:- Verifica que se hayan proporcionado los argumentos correctos: la dirección IP o nombre de host y el rango de puertos (ej: "1-1024").
- Convierte el nombre de host a una dirección IP usando `socket.gethostbyname()`. Esto también actúa como una verificación de si el host es resoluble.
- Imprime la información del escaneo: host, IP, rango y hora de inicio.
- Itera a través del rango de puertos especificado y llama a `scan_tcp_port()` para cada uno.
- Incluye manejo de errores para entradas inválidas, interrupciones del usuario (`Ctrl+C`) y otros posibles problemas.
Para ejecutar este script, guárdalo como `port_scanner.py` y ejecútalo desde tu terminal:
python port_scanner.py <IP_DEL_HOST_OBJETIVO> <RANGO_DE_PUERTOS>
Por ejemplo: python port_scanner.py 192.168.1.1 1-1000
Expandiendo Capacidades: Escaneo Básico de UDP
El escaneo de puertos UDP es inherentemente más complejo que el TCP. Con UDP, no hay un establecimiento de conexión formal. Un paquete UDP enviado a un puerto cerrado generalmente resulta en un mensaje ICMP "Port Unreachable". Sin embargo, un puerto UDP abierto simplemente puede ignorar el paquete o responder con su propio paquete UDP. Diagnosticar esto es más sutil y propenso a falsos positivos/negativos.
Un método básico para verificar UDP implica enviar un paquete "vacío" (o un paquete con una carga útil mínima esperada por un servicio específico, si se conoce) y esperar una respuesta. Un tiempo de espera sin respuesta no garantiza que el puerto esté cerrado, y una respuesta ICMP "Port Unreachable" sí lo confirma.
Debido a su naturaleza menos confiable y la necesidad de manejar respuestas ICMP, el escaneo UDP es más complicado y a menudo se deja para herramientas más sofisticadas como Nmap. Sin embargo, aquí hay un fragmento conceptual de cómo podrías intentar un escaneo UDP básico, centrándote en detectar el mensaje "Port Unreachable" (código 3):
import socket
import sys
import os
import struct
def scan_udp_port(target_host, port):
"""Intenta detectar si un puerto UDP está abierto o cerrado de forma básica."""
try:
# Usamos AF_INET para IPv4 y SOCK_DGRAM para UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Un tiempo de espera corto es esencial
sock.settimeout(0.5)
# Enviamos un paquete vacío. Para servicios específicos,
# podrías necesitar enviar una consulta conocida.
dummy_packet = b''
sock.sendto(dummy_packet, (target_host, port))
# Intentamos recibir una respuesta
data, addr = sock.recvfrom(1024)
# Si recibimos datos, el puerto probablemente está abierto o respondiendo.
print(f"Puerto {port}: Abierto (o respondiendo)")
except socket.timeout:
# Si hay un timeout, el puerto podría estar filtrado o cerrado sin enviar ICMP.
# No podemos estar seguros solo con esto.
# Opcional: print(f"Puerto {port}: Filtrado o Cerrado (Timeout)")
pass
except OSError as e:
# Código de error 1 (Operation not permitted) o 3 (No route to host)
# si el puerto está cerrado y el sistema operativo recibió un ICMP Port Unreachable.
# El código de error 101 (Network is unreachable) puede ocurrir si el host no existe.
if e.errno == 1 or e.errno == 3:
# print(f"Puerto {port}: Cerrado (ICMP Port Unreachable)")
pass # Opcional: comentar para ver puertos cerrados
else:
print(f"Error de OS en puerto {port}: {e}")
except Exception as e:
print(f"Error inesperado en puerto {port}: {e}")
finally:
sock.close()
# Nota: Para un escaneo UDP robusto, se requeriría el uso de librerías
# que manejen paquetes ICMP de forma más granular, como Scapy.
# Este código es conceptual y debe integrarse en la función main()
# de manera que se pueda elegir entre escaneo TCP y UDP o usar un rango específico para cada uno.
Para un análisis de red serio, herramientas como Nmap siguen siendo el estándar de facto, ya que implementan lógicas complejas para el escaneo UDP, incluyendo el envío de varias sondas y el análisis de diferentes tipos genéricos de paquetes hasta para servicios específicos. Si buscas dominar estas técnicas, una certificación como la OSCP te dará la profundidad necesaria.
Optimizando el Rendimiento: Concurrencia para la Velocidad
Un escáner secuencial puede ser muy lento, especialmente al escanear un gran rango de puertos o múltiples hosts. Aquí es donde la concurrencia entra en juego. Podemos usar hilos (`threading`) o procesos (`multiprocessing`) para escanear varios puertos simultáneamente.
Usar hilos es generalmente más sencillo y consume menos recursos que los procesos para tareas I/O-bound como el escaneo de red. Aquí tienes un ejemplo simplificado usando `threading`:
import socket
import sys
from datetime import datetime
import threading
# ... (mantener la función scan_tcp_port de arriba, o adaptarla ligeramente) ...
def scan_tcp_port_threaded(target_ip, port, open_ports):
"""Función de escaneo para ser ejecutada en un hilo."""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(0.5) # Timeout reducido para escalabilidad
result = sock.connect_ex((target_ip, port))
if result == 0:
# Usamos una lista compartida para almacenar puertos abiertos
open_ports.append(port)
sock.close()
except socket.error:
pass # Ignorar errores para no detener todos los hilos
except KeyboardInterrupt:
pass # Ignorar
def main_threaded():
"""Función principal para ejecutar el escáner con hilos."""
if len(sys.argv) != 3:
print("\nUso: python port_scanner_threaded.py <host> <rango-puertos>")
print("Ejemplo: python port_scanner_threaded.py 192.168.1.1 1-1000\n")
sys.exit()
try:
target = sys.argv[1]
ports_range = sys.argv[2]
target_ip = socket.gethostbyname(target)
print("-" * 60)
print(f"Escaneando Host: {target} ({target_ip})")
print(f"Escaneando en Rango de Puertos: {ports_range}")
print(f"Hora de Inicio: {datetime.now()}")
print("-" * 60)
start_port, end_port = map(int, ports_range.split('-'))
# Lista para almacenar los puertos abiertos encontrados
open_ports = []
threads = []
for port in range(start_port, end_port + 1):
# Creamos un hilo para cada puerto
thread = threading.Thread(target=scan_tcp_port_threaded, args=(target_ip, port, open_ports))
threads.append(thread)
thread.start()
# Esperamos a que todos los hilos terminen
for thread in threads:
thread.join()
print("\n--- Puertos Abiertos ---")
if open_ports:
# Ordenamos la lista de puertos abiertos para una mejor presentación
open_ports.sort()
for port in open_ports:
print(f"Puerto {port}: Abierto")
else:
print("No se encontraron puertos abiertos en el rango especificado.")
print("-" * 60)
print(f"Hora de Finalización: {datetime.now()}")
print("-" * 60)
except ValueError:
print("Error: Formato de rango de puertos inválido. Use 'inicio-fin'.")
sys.exit()
except KeyboardInterrupt:
print("\n[!] Escaneo interrumpido por el usuario.")
sys.exit()
except Exception as e:
print(f"Ocurrió un error inesperado: {e}")
sys.exit()
if __name__ == "__main__":
main_threaded()
Al ejecutar este script, notarás una mejora drástica en la velocidad. Sin embargo, ten cuidado con el número de hilos que lanzas. Demasiados hilos pueden sobrecargar tu propia máquina o la red, y podrían activar sistemas de detección de intrusos (IDS/IPS).
Para un control más fino y una escalabilidad mayor, podrías explorar el módulo `concurrent.futures` de Python, que ofrece una interfaz de alto nivel para ejecutar tareas asíncronamente usando hilos o procesos.
Veredicto del Ingeniero: ¿Vale la Pena Construirlo?
Construir tu propio escáner de puertos con Python es una experiencia educativa invaluable. Te fuerza a entender los protocolos de red a un nivel profundo y a manejar la complejidad del I/O de red. Para un profesional de la seguridad, este tipo de ejercicios son fundamentales para:
- Comprender los Fundamentos: Las herramientas comerciales abstractan mucha complejidad. Construir desde cero te da una perspectiva que pocas otras actividades pueden igualar.
- Personalización: Una vez que dominas la base, puedes adaptar el escáner para necesidades específicas: integración con bases de datos de vulnerabilidades, escaneo de servicios específicos, o integración en flujos de trabajo de automatización.
- Desarrollo de Habilidades: Escribir y depurar código de red es una habilidad crítica. Este proyecto es un excelente punto de partida.
Sin embargo, seamos claros: para operaciones de pentesting profesionales y avanzadas, Nmap sigue siendo el rey indiscutible. Su velocidad, sus capacidades de detección de versiones de servicios, el scripting NSE (Nmap Scripting Engine) y el manejo de diferentes tipos de escaneo lo hacen insustituible. Por lo tanto, mi veredicto es:
Construir tu propio escáner es esencial para el aprendizaje y la demostración de conocimiento. Adoptarlo como tu herramienta principal de pentesting profesional, sin embargo, sería un error, a menos que tengas requisitos de personalización extremadamente específicos que Nmap no pueda satisfacer.
Si buscas mejorar tus habilidades de escaneo y análisis de red a nivel profesional, considera invertir en formación y certificaciones. Plataformas como Udemy ofrecen cursos sobre programación de redes con Python, y la ya mencionada OSCP o certificaciones de Cisco te darán la credibilidad y las habilidades que los empleadores buscan.
Preguntas Frecuentes
¿Por qué mi escáner TCP no detecta algunos puertos como abiertos cuando Nmap sí lo hace?
Esto puede deberse a varios factores: el tiempo de espera (`timeout`) configurado en tu script puede ser demasiado corto, la red puede tener latencia alta, o el servicio en ese puerto puede estar configurado para responder de manera inusual. Además, los firewalls o sistemas IDS/IPS podrían estar bloqueando o detectando tu escaneo.
¿Es legal usar un escáner de puertos?
Es legal si lo usas en sistemas que posees o para los que tienes permiso explícito para escanear. Escanear sistemas sin autorización es ilegal y puede tener graves consecuencias. Siempre asegúrate de tener el consentimiento adecuado antes de ejecutar cualquier herramienta de escaneo.
¿Qué puertos son los más importantes para escanear?
Los puertos más comúnmente escaneados, y a menudo los más interesantes desde una perspectiva de seguridad, incluyen:
- 21 - FTP
- 22 - SSH
- 23 - Telnet
- 25 - SMTP
- 53 - DNS
- 80 - HTTP
- 110 - POP3
- 143 - IMAP
- 443 - HTTPS
- 3389 - RDP
- 445 - SMB
¿Cómo puedo mejorar la velocidad de mi escáner de puertos sin usar hilos?
Puedes usar técnicas de I/O no bloqueante con `select` o `selectors` en Python, o explorar el módulo `asyncio` para programación asíncrona. Estas técnicas permiten manejar múltiples conexiones eficientemente sin la sobrecarga de los hilos o procesos.
El Contrato: Tu Primer Reconocimiento
Has construido tu herramienta. Has desarmado el proceso. Ahora, la prueba de fuego. El contrato es simple: tu misión es escanear un host en tu red de laboratorio (un sistema virtualizado, como Metasploitable, o cualquier máquina que hayas configurado para este fin) y documentar:
- La dirección IP del objetivo.
- Un rango de puertos (ej: 1-1024).
- Todos los puertos TCP abiertos que tu script identifique.
- Si utilizaste la versión con hilos, el tiempo total que tardó el escaneo.
Guarda la salida de tu script. Si descubres algún servicio interesante (como un servidor web en el puerto 80 o 443, o SSH sin autenticación fuerte), anota esa información. Este es solo el primer paso.
Ahora, la pregunta crítica: ¿Tu escáner reveló algo que no esperabas? ¿Encontraste puertos abiertos que no deberían estar ahí? Comparte tus hallazgos y el tiempo de escaneo en los comentarios. Demuestra que has roto el código.