Python para pentesting: scripting ofensivo desde cero
· 12 min de lectura
Aprende a usar Python para automatizar tareas de pentesting: construye escáneres de puertos con sockets y threading, haz fuzzing web con requests, crea paquetes con scapy, automatiza herramientas con subprocess y da el primer paso con pwntools.
Las herramientas de pentesting que usas a diario (Nmap, Burp Suite, sqlmap, Metasploit) resuelven problemas comunes. Pero en un compromiso real siempre llega un momento en el que necesitas algo que no existe: un script que recorra una API con lógica propia, un escáner adaptado a un protocolo interno, un generador de payloads que encadene tres pasos. Para eso necesitas saber programar, y el lenguaje que domina el pentesting desde hace más de una década es Python.
Python no es la única opción, pero tiene tres ventajas difíciles de igualar: una biblioteca estándar enorme que cubre sockets, HTTP y procesos sin instalar nada extra; un ecosistema de paquetes ofensivos maduros como scapy, pwntools e impacket; y una sintaxis tan legible que un script escrito a las tres de la mañana sigue siendo comprensible al día siguiente. La mayoría de pruebas de concepto publicadas junto a CVEs están escritas en Python, y la mayoría de herramientas de automatización de seguridad usan Python como pegamento.
En esta lección vas a ver qué módulos forman el kit básico de un pentester, cómo construir un escáner de puertos con hilos, cómo automatizar peticiones web para hacer fuzzing, cómo lanzar herramientas externas desde un script y cómo dar los primeros pasos con pwntools y scapy.
Ejecuta tus scripts solo contra sistemas propios o en entornos autorizados, como los laboratorios de whoami-labs. Usar herramientas ofensivas contra infraestructuras ajenas sin permiso es ilegal.
Qué es Python para pentesting#
Cuando se habla de «Python para pentesting» no se trata de una versión especial del lenguaje ni de un framework concreto. Se trata de usar los módulos de la biblioteca estándar y algunos paquetes externos para resolver las tareas que aparecen en cada fase de una prueba de penetración: reconocimiento, explotación y post-explotación.

El flujo típico es este: encuentras un problema que una herramienta genérica no resuelve (o resuelve demasiado lento), abres un editor, escribes un script de entre 30 y 200 líneas que combina dos o tres módulos, lo pruebas contra el laboratorio y lo descartas o lo guardas en tu carpeta de herramientas. No necesitas un proyecto complejo ni tests unitarios: necesitas algo que funcione, que sea legible y que puedas adaptar rápido.
Python 3 es el estándar actual. Python 2 llegó a su fin de vida en enero de 2020 y muchas bibliotecas ya no lo soportan. Si encuentras un exploit antiguo escrito para Python 2, la primera tarea es portarlo, y suele bastar con ajustar los print, las cadenas de bytes y algún xrange.
Por qué Python domina el pentesting#
| Razón | Detalle |
|---|---|
| Biblioteca estándar completa | socket, http, subprocess, threading, json, base64, hashlib sin instalar nada |
| Ecosistema ofensivo maduro | scapy, pwntools, impacket, paramiko, requests, beautifulsoup |
| Prototipado rápido | Escribir un PoC en 50 líneas para probar una idea en minutos |
| Comunidad enorme | La mayoría de exploits públicos y herramientas de CTF están en Python |
| Multiplataforma | Funciona igual en Kali, macOS y Windows |
| Legibilidad | Un script se entiende meses después sin comentarios extra |
Otros lenguajes tienen su lugar: Go para herramientas compiladas que se despliegan en un binario, Bash para one-liners rápidos en la terminal, C para exploits de bajo nivel. Pero Python es el lenguaje por defecto para el 90 % de las tareas de automatización en seguridad ofensiva.
Módulos esenciales#
| Módulo | Tipo | Para qué sirve |
|---|---|---|
| socket | Estándar | Conexiones TCP y UDP a bajo nivel, escáneres de puertos, banner grabbing |
| threading | Estándar | Ejecutar tareas en paralelo para acelerar escaneos |
| subprocess | Estándar | Lanzar herramientas externas (nmap, gobuster, hashcat) desde un script |
| argparse | Estándar | Crear interfaces de línea de comandos con opciones y ayuda |
| requests | Externo | Peticiones HTTP con sesión, cookies, redirecciones y formularios |
| scapy | Externo | Construir, enviar y analizar paquetes de red a nivel de capa 2 y 3 |
| pwntools | Externo | Desarrollo de exploits, conexiones remotas, manejo de binarios |
No necesitas dominar todos desde el primer día. El camino natural es empezar con socket y threading para entender la red, pasar a requests para automatizar tareas web, aprender subprocess para integrar herramientas y después explorar scapy y pwntools cuando los problemas lo pidan.
Cuándo se usa cada módulo#
- socket + threading: cuando necesitas hablar directamente con un puerto, leer un banner, enviar datos crudos o construir un escáner rápido sin depender de Nmap.
- requests: cuando el objetivo es una aplicación web y necesitas automatizar login, fuzzing de directorios, extracción de datos o pruebas de inyección.
- subprocess: cuando ya existe una herramienta que hace el trabajo (Nmap, Gobuster, John) y quieres integrar su salida en un flujo mayor.
- scapy: cuando necesitas manipular paquetes a bajo nivel: ARP spoofing, crafting de paquetes TCP con flags personalizados, sniffing selectivo.
- pwntools: cuando trabajas con explotación binaria, necesitas interactuar con un servicio remoto enviando payloads binarios o resolver retos de CTF.
- argparse: cuando quieres convertir un script rápido en una herramienta reutilizable con opciones claras.
Cómo se construye un escáner de puertos#
El escáner de puertos es el «hola mundo» del pentesting con Python. Permite entender sockets, manejo de errores y paralelismo. Vamos a construirlo paso a paso.
Paso 1: conexión a un solo puerto#
El bloque más básico es intentar una conexión TCP y ver si el puerto responde.
import socket
def escanear_puerto(ip, puerto):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
resultado = s.connect_ex((ip, puerto))
s.close()
return resultado == 0
if escanear_puerto("IP_DEL_LAB", 80):
print("Puerto 80 abierto")
connect_ex devuelve 0 si la conexión se establece y un código de error si no. El timeout de un segundo evita esperar demasiado en puertos filtrados.
Paso 2: recorrer un rango de puertos#
import socket
def escanear_rango(ip, inicio, fin):
abiertos = []
for puerto in range(inicio, fin + 1):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.5)
if s.connect_ex((ip, puerto)) == 0:
abiertos.append(puerto)
s.close()
return abiertos
encontrados = escanear_rango("IP_DEL_LAB", 1, 1024)
print(f"Puertos abiertos: {encontrados}")
Esto funciona, pero es lento: cada puerto espera hasta 0,5 segundos si no responde. Escanear 65.535 puertos así llevaría horas.
Paso 3: acelerar con threading#
import socket
import threading
abiertos = []
lock = threading.Lock()
def escanear_puerto(ip, puerto):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.5)
if s.connect_ex((ip, puerto)) == 0:
with lock:
abiertos.append(puerto)
s.close()
def escanear_con_hilos(ip, puertos, max_hilos=100):
hilos = []
for puerto in puertos:
t = threading.Thread(target=escanear_puerto, args=(ip, puerto))
hilos.append(t)
t.start()
if len(hilos) >= max_hilos:
for h in hilos:
h.join()
hilos.clear()
for h in hilos:
h.join()
escanear_con_hilos("IP_DEL_LAB", range(1, 1025))
abiertos.sort()
print(f"Puertos abiertos: {abiertos}")
Con 100 hilos, el escaneo de los primeros 1.024 puertos pasa de minutos a pocos segundos. El Lock protege la lista compartida para evitar condiciones de carrera.
Paso 4: añadir banner grabbing#
Una vez detectado un puerto abierto, puedes leer el banner que envía el servicio para identificar qué software y versión corren.
import socket
def obtener_banner(ip, puerto):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(2)
s.connect((ip, puerto))
s.send(b"HEAD / HTTP/1.0\r\n\r\n")
banner = s.recv(1024).decode(errors="ignore").strip()
s.close()
return banner
except Exception:
return None
banner = obtener_banner("IP_DEL_LAB", 80)
if banner:
print(banner)
No todos los servicios envían banner espontáneamente. Para HTTP puedes enviar una petición HEAD; para otros servicios como FTP o SSH, basta conectar y leer lo primero que mandan.
Automatización web con requests#
La biblioteca requests simplifica enormemente el trabajo con aplicaciones web. Instálala con pip install requests si no la tienes.
Fuzzing de directorios#
import requests
url_base = "http://IP_DEL_LAB"
wordlist = "/usr/share/wordlists/dirb/common.txt"
with open(wordlist) as f:
for linea in f:
ruta = linea.strip()
r = requests.get(f"{url_base}/{ruta}", timeout=3)
if r.status_code != 404:
print(f"[{r.status_code}] /{ruta}")
Fuerza bruta contra un formulario de login#
import requests
url = "http://IP_DEL_LAB/login"
usuarios = ["admin", "root", "test"]
passwords = open("/usr/share/wordlists/rockyou.txt", errors="ignore")
for usuario in usuarios:
passwords.seek(0)
for pwd in passwords:
pwd = pwd.strip()
r = requests.post(url, data={"user": usuario, "pass": pwd})
if "Invalid" not in r.text:
print(f"Credenciales encontradas: {usuario}:{pwd}")
break
Siempre filtra la respuesta para identificar un login exitoso: busca cambios en el texto, la URL de redirección, el código de estado o las cookies.
Automatización de herramientas con subprocess#
A veces no necesitas reimplementar una herramienta: solo necesitas orquestar varias.
import subprocess
import json
def ejecutar_nmap(ip):
resultado = subprocess.run(
["nmap", "-p-", "--min-rate", "2000", "-T4", "-oX", "-", ip],
capture_output=True, text=True
)
return resultado.stdout
def ejecutar_gobuster(ip, puerto):
resultado = subprocess.run(
["gobuster", "dir", "-u", f"http://{ip}:{puerto}",
"-w", "/usr/share/wordlists/dirb/common.txt", "-q"],
capture_output=True, text=True
)
return resultado.stdout
salida_nmap = ejecutar_nmap("IP_DEL_LAB")
print(salida_nmap)
La opción capture_output=True captura stdout y stderr. Con -oX - le pides a Nmap que escriba la salida XML en stdout, lo que facilita el parseo posterior.
Primeros pasos con scapy#
Scapy te permite construir paquetes a medida. Instálalo con pip install scapy.
from scapy.all import IP, TCP, sr1, conf
conf.verb = 0
# SYN scan de un puerto
paquete = IP(dst="IP_DEL_LAB") / TCP(dport=80, flags="S")
respuesta = sr1(paquete, timeout=2)
if respuesta and respuesta.haslayer(TCP):
if respuesta[TCP].flags == "SA":
print("Puerto 80 abierto (SYN/ACK)")
elif respuesta[TCP].flags == "RA":
print("Puerto 80 cerrado (RST/ACK)")
else:
print("Puerto 80 filtrado (sin respuesta)")
Con scapy puedes también hacer ARP scanning, traceroute personalizado o construir paquetes DNS malformados para pruebas de seguridad.
Primeros pasos con pwntools#
Pwntools es la navaja suiza de la explotación binaria y los CTF. Instálalo con pip install pwntools.
from pwn import remote
# Conectar a un servicio remoto
conn = remote("IP_DEL_LAB", 9999)
conn.recvuntil(b"Username: ")
conn.sendline(b"admin")
conn.recvuntil(b"Password: ")
conn.sendline(b"admin123")
respuesta = conn.recvline()
print(respuesta.decode())
conn.close()
Pwntools maneja conexiones TCP y UDP con una interfaz mucho más cómoda que sockets directos, y además incluye utilidades para generar shellcode, calcular offsets de buffer overflow y trabajar con formatos binarios como ELF.
Crear herramientas CLI con argparse#
Un script sin argumentos sirve una vez. Con argparse, sirve siempre.
import argparse
import socket
import threading
def main():
parser = argparse.ArgumentParser(description="Escáner de puertos TCP")
parser.add_argument("target", help="IP o hostname del objetivo")
parser.add_argument("-p", "--ports", default="1-1024",
help="Rango de puertos (ej: 1-1024)")
parser.add_argument("-t", "--threads", type=int, default=100,
help="Número de hilos")
args = parser.parse_args()
inicio, fin = map(int, args.ports.split("-"))
print(f"Escaneando {args.target} puertos {inicio}-{fin}")
# Aquí iría la lógica de escaneo con threading
# ...
if __name__ == "__main__":
main()
python3 scanner.py IP_DEL_LAB -p 1-65535 -t 200
La estructura if __name__ == "__main__" permite importar las funciones del script desde otro archivo sin que se ejecute automáticamente.
Qué consigues con esto#
| Capacidad | Sin Python | Con Python |
|---|---|---|
| Escaneo personalizado | Depender de las opciones de Nmap | Lógica propia adaptada al escenario |
| Fuzzing web | Herramientas genéricas | Scripts que entienden la lógica de la aplicación |
| Cadena de herramientas | Ejecutar una por una a mano | Un script que encadena Nmap, Gobuster y Nikto |
| Explotación | Buscar un exploit público que funcione | Escribir o adaptar el tuyo |
| Reportes | Copiar y pegar resultados | Generar informes automáticos en JSON o HTML |
| Repetibilidad | Recordar cada paso | Un script que repite el proceso exacto |
Buenas prácticas de scripting ofensivo#

- Usa siempre timeouts. Una conexión sin timeout puede bloquear tu script indefinidamente. Pon
settimeout()en sockets ytimeout=en requests. - Maneja excepciones. Los servicios caen, los firewalls cortan conexiones, los DNS fallan. Usa
try/exceptalrededor de toda operación de red. - No hardcodees IPs ni rutas. Usa argparse o al menos variables al inicio del script. Un script con la IP escrita a fuego no sirve para el siguiente laboratorio.
- Limita la concurrencia. Lanzar 10.000 hilos simultáneos puede tumbar el objetivo o tu propia máquina. Usa un
ThreadPoolExecutorcon un máximo razonable. - Registra la salida. Escribe los resultados en un archivo además de mostrarlos en pantalla. Un hallazgo perdido es un hallazgo inútil.
- Comenta el propósito, no el código. Mejor un comentario que diga «Detectar si el FTP acepta anónimo» que uno que diga «Enviar USER anonymous».
- Versiona tus scripts. Un repositorio Git personal con tus herramientas crece con tu carrera. Organízalo por categoría: recon, web, privesc, utils.
- Respeta el alcance. Tu script solo debe tocar los objetivos autorizados. Añade una confirmación al inicio si el script puede ser destructivo.
Errores comunes#
- Olvidar cerrar sockets. Cada socket abierto consume un descriptor de archivo. Si no lo cierras, el sistema operativo te bloquea al llegar al límite. Usa
witho llama a.close()explícitamente. - No manejar la codificación. Los datos de red son bytes, no strings. Usa
b"texto"para enviar y.decode(errors="ignore")para recibir. - Escanear sin permiso. Que el script sea tuyo no significa que puedas apuntarlo a cualquier IP. Asegúrate de tener autorización escrita.
- Ignorar las respuestas de error. Un
status_code 403o un timeout no significan que no haya nada: significan que hay un firewall o un WAF. Anótalos. - Reinventar herramientas que ya existen. Si Nmap ya hace lo que necesitas, úsalo con subprocess. Escribe código propio cuando la herramienta existente no cubra tu caso.
- Escribir scripts de un solo uso. Diez minutos añadiendo argparse y guardando resultados en archivo convierten un descarte en una herramienta permanente.
Preguntas frecuentes#
¿Necesito saber programar antes de empezar?#
No necesitas ser desarrollador, pero sí entender los fundamentos: variables, funciones, bucles, condicionales y manejo de archivos. Con eso puedes escribir scripts útiles desde el primer día e ir aprendiendo el resto sobre la marcha.
¿Python 2 o Python 3?#
Python 3, sin excepción. Python 2 dejó de recibir actualizaciones de seguridad en enero de 2020. Si encuentras un exploit en Python 2, pártalo a Python 3 antes de usarlo.
¿Requests o urllib?#
Usa requests. La biblioteca estándar urllib funciona, pero su API es mucho más verbosa y difícil de usar. Requests maneja cookies, sesiones, redirecciones y encodings de forma transparente.
¿pwntools funciona en Windows?#
Oficialmente pwntools solo soporta Linux (y parcialmente macOS). En Windows puedes usar WSL (Windows Subsystem for Linux) para ejecutarlo sin problemas. Para tareas que no requieren pwntools, el resto de módulos funcionan en cualquier sistema.
¿Cuándo conviene usar Go en lugar de Python?#
Cuando necesitas distribuir una herramienta como un binario estático sin dependencias, cuando el rendimiento de red es crítico (miles de conexiones por segundo) o cuando trabajas con herramientas que ya están en Go como Gobuster o Nuclei. Para scripting rápido y prototipos, Python sigue siendo más productivo.
¿Cómo organizo mis scripts?#
Crea una carpeta con subcarpetas por fase: recon/, web/, exploit/, privesc/, utils/. Cada script con su argparse, un bloque if __name__ y un comentario de una línea al inicio que explique qué hace.
Resumen#
- Python es el lenguaje dominante en pentesting por su biblioteca estándar, su ecosistema ofensivo y su velocidad de prototipado.
- Los módulos clave son socket para conexiones de red, threading para paralelismo, requests para HTTP, subprocess para orquestar herramientas, scapy para paquetes a bajo nivel y pwntools para explotación binaria.
- Un escáner de puertos con threading se construye en menos de 30 líneas y enseña los fundamentos de red y concurrencia.
- argparse convierte un script desechable en una herramienta reutilizable con opciones claras.
- Siempre usa timeouts, excepciones y autorización antes de ejecutar cualquier script ofensivo.
- Empieza simple, resuelve un problema real y amplía desde ahí.