Volver al blog

mitmproxy para desarrolladores: interceptación y análisis de tráfico HTTPS con ejemplos de código en Python

mitmproxy es una herramienta poderosa para interceptar y analizar tráfico HTTPS. Abordamos la instalación, configuración de certificados SSL, escritura de scripts y escenarios de aplicación reales.

📅5 de agosto de 2026
```html

¿Estás depurando una aplicación móvil y no entiendes qué solicitudes está enviando al servidor? ¿Necesitas probar el comportamiento de la API bajo diferentes condiciones o interceptar la respuesta y modificar los datos sobre la marcha? mitmproxy resuelve todas estas tareas: es una herramienta gratuita de código abierto que permite controlar completamente el tráfico HTTP y HTTPS entre el cliente y el servidor.

Qué es mitmproxy y para qué lo necesita un desarrollador

mitmproxy es un proxy MITM (Man-In-The-Middle Proxy) interactivo de código abierto, escrito en Python. Funciona como intermediario entre tu aplicación y el servidor: intercepta todas las solicitudes y respuestas, permitiendo visualizarlas, modificarlas, reproducirlas y guardarlas.

La principal diferencia entre mitmproxy y los proxies convencionales es la capacidad de trabajar con tráfico HTTPS cifrado. La herramienta genera dinámicamente certificados SSL para cada dominio, lo que permite descifrar el tráfico sobre la marcha sin interrumpir el funcionamiento de la aplicación.

Aquí hay tareas típicas que los desarrolladores resuelven con mitmproxy:

  • Depuración de API — ves las solicitudes y respuestas exactas, incluyendo encabezados, cuerpo, códigos de estado.
  • Ingeniería inversa — analizas cómo funcionan aplicaciones y servicios de terceros.
  • Pruebas — sustituyes respuestas del servidor para verificar casos límite.
  • Automatización — escribes scripts para modificar el tráfico según condiciones.
  • Grabación y reproducción — guardas la sesión y la reproduces sin un servidor real.
  • Análisis de seguridad — verificas que la aplicación no envíe datos innecesarios.

Es importante entender

mitmproxy es una herramienta para pruebas y desarrollo legales. Úsala solo para analizar el tráfico de aplicaciones que desarrollas o tienes permiso para probar. Interceptar el tráfico de otros sin autorización viola la legislación.

La herramienta se presenta en tres variantes: interfaz interactiva de consola mitmproxy, interfaz web mitmweb y utilidad de línea de comandos mitmdump. Las tres utilizan un núcleo común y admiten scripts de Python.

Instalación de mitmproxy en Windows, macOS y Linux

mitmproxy se puede instalar de varias maneras. Se recomienda usar pip — esto garantiza la versión más reciente y una fácil actualización.

Instalación a través de pip (método universal)

Se requiere Python 3.9 o superior. Verifica la versión de Python:

python --version
# o
python3 --version

Instalamos mitmproxy:

pip install mitmproxy

Después de la instalación, verificamos:

mitmproxy --version
# Debe mostrar: mitmproxy 10.x.x

Instalación a través de gestores de paquetes

macOS (Homebrew):

brew install mitmproxy

Linux (Ubuntu/Debian):

sudo apt install mitmproxy
# o a través de snap para la versión más reciente:
sudo snap install mitmproxy

Windows: Descarga el instalador desde el sitio oficial mitmproxy.org o usa pip en PowerShell con privilegios de administrador.

Ejecutar y verificar

Por defecto, mitmproxy escucha en el puerto 8080. Iniciamos la interfaz web para comenzar a trabajar:

# Iniciar la interfaz web en el puerto 8080
mitmweb

# Iniciar en otro puerto
mitmweb --listen-port 9090

# Interfaz de consola
mitmproxy

Después de iniciar mitmweb, abre el navegador en la dirección http://127.0.0.1:8081 — esta es la interfaz web para ver el tráfico. El proxy en sí funciona en el puerto 8080.

Configuración de certificados SSL para interceptar HTTPS

Interceptar tráfico HTTPS requiere instalar el certificado raíz de mitmproxy en el sistema o navegador. Sin este paso, el navegador mostrará una advertencia sobre la conexión no segura, y muchas aplicaciones se negarán a funcionar.

Cómo funciona técnicamente

Al iniciar mitmproxy por primera vez, se crea automáticamente un certificado CA raíz y se guarda en el directorio ~/.mitmproxy/. Cuando un cliente se conecta a un sitio HTTPS a través del proxy, mitmproxy genera sobre la marcha un certificado para ese dominio, firmándolo con su CA. El cliente confía en este certificado si la CA está añadida a las de confianza, y el descifrado ocurre de manera transparente.

Instalación del certificado en el sistema

Los certificados se encuentran en ~/.mitmproxy/:

  • mitmproxy-ca-cert.pem — para Linux/macOS
  • mitmproxy-ca-cert.cer — para Windows
  • mitmproxy-ca-cert.p12 — para iOS

macOS:

sudo security add-trusted-cert -d -r trustRoot \
  -k /Library/Keychains/System.keychain \
  ~/.mitmproxy/mitmproxy-ca-cert.pem

Linux (Ubuntu/Debian):

sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem \
  /usr/local/share/ca-certificates/mitmproxy.crt
sudo update-ca-certificates

Windows: Haz doble clic en el archivo mitmproxy-ca-cert.cer → «Instalar certificado» → «Equipo local» → «Centros de certificación raíz de confianza».

Instalación en el navegador Firefox

Firefox utiliza su propio almacén de certificados. Ve a: Configuración → Privacidad y seguridad → Certificados → Ver certificados → Autoridades → Importar. Selecciona el archivo mitmproxy-ca-cert.pem y marca «Confiar en la identificación de sitios web».

Verificación del funcionamiento

Configura el navegador para usar el proxy 127.0.0.1:8080 y abre cualquier sitio HTTPS. En la interfaz de mitmweb deberías ver el tráfico descifrado. Alternativamente, abre http://mitm.it a través del proxy configurado: mitmproxy mostrará instrucciones para instalar el certificado en tu plataforma.

Tres interfaces: mitmproxy, mitmweb y mitmdump

El paquete mitmproxy incluye tres utilidades con diferentes interfaces para diferentes escenarios de trabajo. Comprender las diferencias ayudará a elegir la herramienta adecuada para cada tarea.

Utilidad Interfaz Cuándo usar Características
mitmproxy TUI de consola Depuración interactiva en la terminal Requiere terminal con soporte para colores, potente filtro
mitmweb Navegador web Análisis visual del tráfico Interfaz fácil de usar, soporte de filtros, exportación
mitmdump CLI (stdout) Scripts, CI/CD, automatización Sin interactividad, salida a archivo o pipe

Banderas útiles de inicio

# Grabar tráfico en un archivo
mitmdump -w traffic.dump

# Reproducir tráfico grabado
mitmdump -r traffic.dump

# Filtrado: solo solicitudes a un dominio específico
mitmproxy --filter "~d api.example.com"

# Ejecutar en modo proxy transparente
mitmproxy --mode transparent

# Ejecutar como proxy ascendente (cadena de proxies)
mitmproxy --mode upstream:http://upstream-proxy:8080

# Especificar un puerto específico
mitmweb --listen-port 9090 --web-port 9091

# Ejecutar con un script
mitmproxy -s my_script.py

Sintaxis de filtros de mitmproxy

mitmproxy admite un potente lenguaje de filtros para seleccionar las solicitudes necesarias:

# ~d — filtro por dominio
~d api.example.com

# ~u — filtro por URL (regex)
~u /api/v2/users

# ~m — filtro por método HTTP
~m POST

# ~s — solo respuestas
~s ~c 404

# ~c — filtro por código de estado
~c 500

# Combinación (Y)
~d api.example.com & ~m POST

# Combinación (O)
~c 404 | ~c 500

# NO
!~d static.example.com

Escritura de scripts en Python: interceptación y modificación de tráfico

Los scripts son el principal superpoder de mitmproxy. Con ellos puedes modificar automáticamente solicitudes y respuestas, registrar datos en el formato deseado, simular errores del servidor y mucho más. Los scripts se escriben en Python y utilizan un modelo basado en eventos.

Eventos principales (hooks)

Hook Cuándo se llama Objeto
request Se recibe una solicitud del cliente flow.request
response Se recibe una respuesta del servidor flow.response
error Error de conexión flow.error
tls_start_client Inicio del apretón de manos TLS con el cliente tls_start

Ejemplo 1: registro de solicitudes en un archivo

# logger.py
import mitmproxy.http
import json
from datetime import datetime

def request(flow: mitmproxy.http.HTTPFlow) -> None:
    """Registramos cada solicitud en un archivo JSON."""
    log_entry = {
        "timestamp": datetime.now().isoformat(),
        "method": flow.request.method,
        "url": flow.request.pretty_url,
        "headers": dict(flow.request.headers),
        "body": flow.request.text if flow.request.text else None
    }
    with open("requests.log", "a") as f:
        f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")

def response(flow: mitmproxy.http.HTTPFlow) -> None:
    """Registramos respuestas con código de error."""
    if flow.response.status_code >= 400:
        print(f"[ERROR] {flow.request.method} {flow.request.pretty_url} "
              f"-> {flow.response.status_code}")

Ejecutar el script:

mitmproxy -s logger.py

Ejemplo 2: modificación de solicitudes — sustitución de encabezados

# modify_headers.py
from mitmproxy import http

def request(flow: http.HTTPFlow) -> None:
    """Sustituimos User-Agent y añadimos un encabezado personalizado."""
    if "api.example.com" in flow.request.pretty_host:
        # Sustitución de User-Agent
        flow.request.headers["User-Agent"] = (
            "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) "
            "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"
        )
        # Adición de encabezado de autorización
        flow.request.headers["X-Custom-Token"] = "test-token-12345"
        # Eliminación de encabezado
        if "X-Debug-Info" in flow.request.headers:
            del flow.request.headers["X-Debug-Info"]

Ejemplo 3: sustitución de la respuesta del servidor (mock)

# mock_response.py
from mitmproxy import http
import json

def request(flow: http.HTTPFlow) -> None:
    """Interceptamos la solicitud y devolvemos una respuesta mock, sin contactar al servidor."""
    if flow.request.pretty_url.endswith("/api/v1/user/profile"):
        # Creamos una respuesta mock
        mock_data = {
            "id": 42,
            "name": "Test User",
            "email": "[email protected]",
            "premium": True  # Probamos la funcionalidad premium
        }
        flow.response = http.Response.make(
            200,  # Código de estado
            json.dumps(mock_data),  # Cuerpo de la respuesta
            {"Content-Type": "application/json"}  # Encabezados
        )

def response(flow: http.HTTPFlow) -> None:
    """Modificamos la respuesta real del servidor."""
    if "/api/v1/products" in flow.request.pretty_url:
        try:
            data = json.loads(flow.response.text)
            # Añadimos un campo a cada producto
            for product in data.get("items", []):
                product["debug_info"] = "intercepted"
            flow.response.text = json.dumps(data)
        except (json.JSONDecodeError, KeyError):
            pass

Ejemplo 4: simulación de conexión lenta y errores

# chaos_testing.py
from mitmproxy import http
import time
import random

def response(flow: http.HTTPFlow) -> None:
    """Ingeniería del caos: retrasos y errores aleatorios para pruebas."""
    # Añadimos un retraso aleatorio de 0 a 2 segundos
    if "api.example.com" in flow.request.pretty_host:
        delay = random.uniform(0, 2.0)
        time.sleep(delay)

    # 10% de probabilidad de respuesta 503
    if random.random() < 0.1:
        flow.response = http.Response.make(
            503,
            json.dumps({"error": "Servicio no disponible"}),
            {"Content-Type": "application/json"}
        )

Interceptación de tráfico de aplicaciones móviles

Analizar el tráfico de aplicaciones móviles es una de las tareas más comunes al usar mitmproxy. Esto es especialmente útil al hacer ingeniería inversa de APIs de aplicaciones móviles o al probar tu propia aplicación en un dispositivo real.

Configuración en Android

Paso 1. Asegúrate de que el teléfono y la computadora estén en la misma red Wi-Fi.

Paso 2. Inicia mitmproxy en la computadora:

mitmweb --listen-host 0.0.0.0 --listen-port 8080

Paso 3. En Android: Configuración → Wi-Fi → mantén presionada la red → Modificar red → Avanzado → Proxy → Manual. Ingresa la IP de la computadora y el puerto 8080.

Paso 4. Instalación del certificado en Android: abre el navegador en el dispositivo, ve a http://mitm.it y descarga el certificado para Android. Luego: Configuración → Seguridad → Instalar certificado → Certificado CA.

Android 7+ y Certificate Pinning

A partir de Android 7.0, las aplicaciones por defecto no confían en certificados CA personalizados. Para interceptar el tráfico de tales aplicaciones se requerirá acceso root o modificar network_security_config.xml en el APK. Para aplicaciones con SSL Pinning, usa Frida o Xposed Framework para eludir la verificación del certificado.

Configuración en iOS

Paso 1. Configura el proxy de manera similar a Android: Configuración → Wi-Fi → toca (i) junto a la red → Configurar proxy → Manual.

Paso 2. Abre Safari y ve a http://mitm.it — descarga el certificado para iOS.

Paso 3. Instala el perfil: Configuración → Perfil descargado → Instalar.

Paso 4. Activa la confianza en el certificado: Configuración → General → Acerca del dispositivo → Confianza en certificados — activa el interruptor para mitmproxy.

Interceptación de tráfico de una aplicación específica a través de Python

# mobile_app_analyzer.py
from mitmproxy import http
import json
import re

# Dominios de la aplicación de interés
TARGET_DOMAINS = ["api.myapp.com", "cdn.myapp.com"]

def response(flow: http.HTTPFlow) -> None:
    """Analizamos el tráfico de la aplicación móvil."""
    host = flow.request.pretty_host

    if not any(domain in host for domain in TARGET_DOMAINS):
        return

    # Extraemos respuestas JSON
    content_type = flow.response.headers.get("content-type", "")
    if "application/json" in content_type:
        try:
            data = json.loads(flow.response.text)
            print(f"\n{'='*60}")
            print(f"URL: {flow.request.pretty_url}")
            print(f"Status: {flow.response.status_code}")
            print(f"Response: {json.dumps(data, indent=2, ensure_ascii=False)}")
        except json.JSONDecodeError:
            pass

    # Buscamos tokens en los encabezados de la solicitud
    auth_header = flow.request.headers.get("authorization", "")
    if auth_header:
        print(f"[AUTH] Token encontrado: {auth_header[:50]}...")

Cadena de proxies: mitmproxy + proxy ascendente

Uno de los escenarios más poderosos es usar mitmproxy junto con un servidor proxy externo. Esto permite interceptar y analizar tráfico (a través de mitmproxy) y dirigirlo a través de una dirección IP externa (a través de un proxy ascendente). Este esquema se utiliza al probar APIs geodependientes o al desarrollar aplicaciones que deben funcionar a través de un proxy.

Modo proxy ascendente

# Dirigir todo el tráfico a través de un proxy HTTP ascendente
mitmproxy --mode upstream:http://proxy-host:port

# Proxy SOCKS5 ascendente
mitmproxy --mode upstream:socks5://proxy-host:port

# Con autenticación
mitmproxy --mode upstream:http://user:password@proxy-host:port

# A través de mitmweb con upstream
mitmweb --mode upstream:http://proxy-host:port

En este modo, mitmproxy acepta solicitudes localmente, descifra HTTPS, te permite analizarlas y modificarlas, y luego las envía a través de un proxy externo. Esto es especialmente útil al probar APIs que solo están disponibles desde ciertas regiones.

Para tales tareas, los proxies residenciales son una buena opción: tienen direcciones IP reales de usuarios domésticos de los países necesarios, lo que permite probar correctamente las respuestas geodependientes de la API.

Selección dinámica de proxy ascendente en el script

# dynamic_upstream.py
from mitmproxy import http
from mitmproxy.net.server_spec import ServerSpec

# Lista de proxies para rotación
PROXY_LIST = [
    "http://proxy1.example.com:8080",
    "http://proxy2.example.com:8080",
    "http://proxy3.example.com:8080",
]

proxy_index = 0

def request(flow: http.HTTPFlow) -> None:
    """Rotación de proxies ascendentes para cada solicitud."""
    global proxy_index

    # Dirigimos solicitudes a la API a través de diferentes proxies
    if "api.target.com" in flow.request.pretty_host:
        proxy_url = PROXY_LIST[proxy_index % len(PROXY_LIST)]
        proxy_index += 1
        flow.live.change_upstream_proxy_server(
            ServerSpec.from_url(proxy_url)
        )
        print(f"Usando proxy: {proxy_url} para {flow.request.pretty_url}")

Proxy transparente (modo transparente)

En modo transparente, la aplicación no sabe que su tráfico está siendo interceptado — no es necesario configurar el proxy en la configuración. Esto requiere la configuración de iptables/pf a nivel del sistema operativo:

# Ejecutar en modo transparente
mitmproxy --mode transparent --listen-port 8080

# Configuración de iptables para redirigir tráfico (Linux)
sudo iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port 8080
sudo iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to-port 8080

Escenarios prácticos de aplicación mitmproxy

Consideremos tareas específicas que los desarrolladores resuelven con mitmproxy en proyectos reales.

Escenario 1: Pruebas de API sin modificar el servidor

Imagina: necesitas verificar cómo el frontend maneja una respuesta con un array de datos vacío o un error de autorización 401, pero reproducir esto en el servidor de pruebas es complicado. mitmproxy permite sustituir la respuesta sobre la marcha:

# test_edge_cases.py
from mitmproxy import http
import json

def response(flow: http.HTTPFlow) -> None:
    url = flow.request.pretty_url

    # Prueba: lista de productos vacía
    if "/api/products" in url and "test_empty=1" in url:
        flow.response.text = json.dumps({"items": [], "total": 0})

    # Prueba: token expirado
    if "/api/user" in url and "test_auth=1" in url:
        flow.response = http.Response.make(
            401,
            json.dumps({"error": "Token expirado", "code": "AUTH_001"}),
            {"Content-Type": "application/json"}
        )

    # Prueba: superación del límite de solicitudes
    if "/api/" in url and "test_rate=1" in url:
        flow.response = http.Response.make(
            429,
            json.dumps({"error": "Demasiadas solicitudes", "retry_after": 60}),
            {"Content-Type": "application/json",
             "Retry-After": "60"}
        )

Escenario 2: Grabación y reproducción de sesión

Útil para crear fixtures de prueba o demostrar funcionalidades sin un servidor real:

# Grabar sesión en un archivo
mitmdump -w session.dump --filter "~d api.example.com"

# Reproducir sesión grabada (offline)
mitmdump -r session.dump

# Convertir a formato HAR para análisis
mitmdump -r session.dump --flow-detail 3 > session.txt

Escenario 3: Documentación automática de API

# api_documenter.py
from mitmproxy import http
import json
from collections import defaultdict

# Diccionario para acumular información sobre endpoints
endpoints = defaultdict(lambda: {"methods": set(), "status_codes": set(),
                                  "request_fields": set(), "response_fields": set()})

def _extract_fields(data, prefix=""):
    """Extraemos campos de JSON de manera recursiva."""
    fields = set()
    if isinstance(data, dict):
        for key, value in data.items():
            full_key = f"{prefix}.{key}" if prefix else key
            fields.add(full_key)
            fields.update(_extract_fields(value, full_key))
    elif isinstance(data, list) and data:
        fields.update(_extract_fields(data[0], prefix))
    return fields

def response(flow: http.HTTPFlow) -> None:
    if "api.example.com" not in flow.request.pretty_host:
        return

    # Normalizamos la URL (eliminamos ID)
    import re
    path = re.sub(r'/\d+', '/{id}', flow.request.path)
    endpoint = f"{flow.request.method} {path}"

    ep = endpoints[endpoint]
    ep["methods"].add(flow.request.method)
    ep["status_codes"].add(flow.response.status_code)

    # Extraemos campos de la solicitud
    if flow.request.text:
        try:
            req_data = json.loads(flow.request.text)
            ep["request_fields"].update(_extract_fields(req_data))
        except json.JSONDecodeError:
            pass

    # Extraemos campos de la respuesta
    if flow.response.text:
        try:
            resp_data = json.loads(flow.response.text)
            ep["response_fields"].update(_extract_fields(resp_data))
        except json.JSONDecodeError:
            pass

def done():
    """Mostramos la documentación al finalizar."""
    print("\n=== DOCUMENTACIÓN DE API ===\n")
    for endpoint, info in sorted(endpoints.items()):
        print(f"Endpoint: {endpoint}")
        print(f"  Códigos de estado: {sorted(info['status_codes'])}")
        if info["request_fields"]:
            print(f"  Campos de solicitud: {sorted(info['request_fields'])}")
        if info["response_fields"]:
            print(f"  Campos de respuesta: {sorted(info['response_fields'])}")
        print()

Escenario 4: Pruebas de respuestas geodependientes

Al desarrollar aplicaciones con contenido regional, es importante verificar cómo responde la API a las solicitudes de diferentes países. Para esto, mitmproxy se ejecuta en modo ascendente con proxies de centros de datos de las regiones necesarias — es una forma rápida y confiable de simular solicitudes desde países específicos.

# geo_test.py
from mitmproxy import http

def response(flow: http.HTTPFlow) -> None:
    """Registramos encabezados y datos geodependientes."""
    # Verificamos qué contenido devuelve el servidor
    geo_headers = ["cf-ipcountry", "x-country", "x-geo-country"]
    for header in geo_headers:
        value = flow.response.headers.get(header)
        if value:
            print(f"[GEO] {header}: {value} | URL: {flow.request.pretty_url}")

    # Buscamos menciones de monedas y locales en la respuesta
    if flow.response.text:
        import re
        currencies = re.findall(r'"currency":\s*"([A-Z]{3})"', flow.response.text)
        locales = re.findall(r'"locale":\s*"([a-z]{2}-[A-Z]{2})"', flow.response.text)
        if currencies:
            print(f"[CURRENCY] {currencies}")
        if locales:
            print(f"[LOCALE] {locales}")

Escenario 5: Uso de mitmproxy en CI/CD

mitmdump es ideal para pruebas de integración en el pipeline de CI/CD — se ejecuta como un proceso en segundo plano, graba tráfico y finaliza junto con las pruebas:

#!/bin/bash
# ci_test.sh

# Iniciamos mitmdump en segundo plano
mitmdump -w test_traffic.dump -s ci_assertions.py &
MITM_PID=$!

# Damos tiempo al proxy para iniciarse
sleep 1

# Ejecutamos pruebas con el proxy
export HTTP_PROXY=http://127.0.0.1:8080
export HTTPS_PROXY=http://127.0.0.1:8080
pytest tests/integration/ -v

# Detenemos mitmdump
kill $MITM_PID

# Analizamos el tráfico grabado
mitmdump -r test_traffic.dump -s analyze_traffic.py

El script ci_assertions.py puede verificar que la aplicación no realice solicitudes innecesarias, no envíe datos sensibles en texto claro y cumpla con los contratos de API.

Escenario 6: Análisis de tráfico de un parser

Al desarrollar parsers, mitmproxy ayuda a entender qué solicitudes realiza el navegador al cargar una página — incluyendo solicitudes XHR/fetch a la API que no son visibles en el código fuente HTML. Esto permite acceder directamente a la API del sitio en lugar de parsear HTML. Al desarrollar tales soluciones, a menudo se utilizan proxies residenciales para rotar IP y evitar bloqueos al recopilar datos.

Conclusión

mitmproxy es una de las herramientas más poderosas en el arsenal de un desarrollador para trabajar con tráfico HTTP/HTTPS. Combina funciones de depurador, entorno de pruebas, documentación de API y herramienta de análisis de seguridad. Las tres interfaces — consola, web y CLI — cubren todos los escenarios: desde la depuración interactiva hasta la automatización en CI/CD.

```