Aprende › Fundamentos

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.

Arquitectura del toolkit Python para pentesting: módulos de la biblioteca estándar y paquetes externos conectados a las fases de un compromiso
Arquitectura del toolkit Python para pentesting: módulos de la biblioteca estándar y paquetes externos conectados a las fases de un compromiso

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ónDetalle
Biblioteca estándar completasocket, http, subprocess, threading, json, base64, hashlib sin instalar nada
Ecosistema ofensivo maduroscapy, pwntools, impacket, paramiko, requests, beautifulsoup
Prototipado rápidoEscribir un PoC en 50 líneas para probar una idea en minutos
Comunidad enormeLa mayoría de exploits públicos y herramientas de CTF están en Python
MultiplataformaFunciona igual en Kali, macOS y Windows
LegibilidadUn 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óduloTipoPara qué sirve
socketEstándarConexiones TCP y UDP a bajo nivel, escáneres de puertos, banner grabbing
threadingEstándarEjecutar tareas en paralelo para acelerar escaneos
subprocessEstándarLanzar herramientas externas (nmap, gobuster, hashcat) desde un script
argparseEstándarCrear interfaces de línea de comandos con opciones y ayuda
requestsExternoPeticiones HTTP con sesión, cookies, redirecciones y formularios
scapyExternoConstruir, enviar y analizar paquetes de red a nivel de capa 2 y 3
pwntoolsExternoDesarrollo 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#

CapacidadSin PythonCon Python
Escaneo personalizadoDepender de las opciones de NmapLógica propia adaptada al escenario
Fuzzing webHerramientas genéricasScripts que entienden la lógica de la aplicación
Cadena de herramientasEjecutar una por una a manoUn script que encadena Nmap, Gobuster y Nikto
ExplotaciónBuscar un exploit público que funcioneEscribir o adaptar el tuyo
ReportesCopiar y pegar resultadosGenerar informes automáticos en JSON o HTML
RepetibilidadRecordar cada pasoUn script que repite el proceso exacto

Buenas prácticas de scripting ofensivo#

Flujo del desarrollo de herramientas Python para pentesting: desde la idea hasta la herramienta reutilizable
Flujo del desarrollo de herramientas Python para pentesting: desde la idea hasta la herramienta reutilizable
  • Usa siempre timeouts. Una conexión sin timeout puede bloquear tu script indefinidamente. Pon settimeout() en sockets y timeout= en requests.
  • Maneja excepciones. Los servicios caen, los firewalls cortan conexiones, los DNS fallan. Usa try/except alrededor 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 ThreadPoolExecutor con 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 with o 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 403 o 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í.

Fuentes y lecturas recomendadas#

Practícalo en estos laboratorios

Ver todos los laboratorios de esta categoría