Nota de origen: cheatsheet basado en un script y hallazgo REALES de un engagement autorizado (plataforma de cobranza con agentes de voz IA integrados vía un proveedor externo). Anonimizado.
El hallazgo real
El endpoint POST /api/v1/webhooks/<proveedor> recibía eventos de fin de llamada de un proveedor de
voz/IA externo (transcript, metadata, resultado del análisis de la conversación) y los procesaba sin
verificar el header de firma HMAC que el proveedor sí envía (X-<Proveedor>-Signature). El propio
research automatizado de Websec Argus sobre este proveedor (ver
[[../Investigación/Hallazgos/2026-08-07-hallazgo-webhook-de-elevenlabs-sin-validacion-de-firma-exploit-public|hallazgo relacionado]])
confirmó que el SDK oficial SÍ ofrece verificación de firma lista para usar — el gap no está en el
proveedor, está en que la implementación del CLIENTE nunca llamó a esa verificación.
Por qué importa más que "falta un header"
No es un simple hallazgo de "falta autenticación" — el CONTENIDO del evento falsificado altera
datos de negocio con impacto financiero directo: en una plataforma de cobranza, el campo
analysis.evaluation_criteria_results de un evento de llamada alimenta directamente si se registra
una "promesa de pago" del deudor. Un evento falsificado puede:
- Crear promesas de pago falsas (o borrar promesas reales sobreescribiendo el registro real con uno
falso para el mismo
conversation_id). - Inundar la cola de procesamiento de eventos (
--count 500+) — DoS del pipeline de webhooks.
Script real (patrón generalizado)
EVENT = {
"conversation_id": "<ID-REAL-DE-UNA-LLAMADA-EN-CURSO>",
"status": "completed",
"transcript": [
{"role": "agent", "message": "..."},
{"role": "user", "message": "Sí, voy a pagar <MONTO> el <FECHA>"},
],
"analysis": {
"evaluation_criteria_results": [{"name": "payment_promise", "result": True}],
},
}
def send(api, conversation_id):
ev = dict(EVENT); ev["conversation_id"] = conversation_id
r = urllib.request.Request(f"{api}/webhooks/<proveedor>",
data=json.dumps(ev).encode(),
headers={"Content-Type": "application/json"}) # sin firma — ese es el punto
with urllib.request.urlopen(r, timeout=15) as resp:
return resp.status, resp.read().decode()
Disciplina del PoC real: el conversation_id usado en el PoC fue un identificador
CLARAMENTE marcado como sintético ("PENTEST-FAKE-CALL-2026"), NUNCA el ID de una llamada real de
un cliente/deudor real — para demostrar el impacto (el evento se acepta y se "bufferea") sin
contaminar datos de negocio reales durante la prueba. Solo se corrió contra STAGING por defecto;
PROD requería flag explícito.
Cómo probarlo en cualquier engagement con webhooks entrantes
- Identificar todos los endpoints que reciben webhooks de proveedores externos (Stripe, Twilio, ElevenLabs, GitHub, etc. — cada uno documenta su propio mecanismo de firma).
- Enviar el mismo payload que un evento legítimo, PERO sin el header de firma o con un valor inválido, usando un identificador de prueba claramente sintético.
- Si el endpoint procesa el evento igual (HTTP 200 y efecto observable en el sistema), es un hallazgo de "falta de verificación de autenticidad de webhook" — impacto según qué datos de negocio dependen de ese evento (no todos los webhooks tienen el mismo impacto; el de cobranza es alto porque toca dinero).
Mitigación real a recomendar: usar el SDK/librería oficial del proveedor para verificar firma + timestamp (ventana de replay, ej. 30 min) ANTES de cualquier procesamiento del payload — nunca implementar la verificación HMAC a mano si el proveedor ya ofrece un método probado.
Ver también: [[cheatsheet-de-dos-por-abuso-de-limites-ausentes-creacion-masiva-e-importacion-sin-limite|DoS por límites ausentes]] (mismo cliente/engagement), [[cheatsheet-de-ataques-a-jwt-algoritmos-y-forjado-de-tokens|cheatsheet de forjado de tokens]], [[indice-de-casos-reales-de-brechas-publicas-por-vector|índice de casos reales de brechas públicas]].