
Laboratorio basado en Docker para reproducir CVE-2026-46645, una omisión de autorización en el endpoint ajax_lookup de SQLAdmin. Incluye objetivos vulnerable y parcheado, script PoC y pasos de reproducción manual con curl para investigación y educación en seguridad.
Este repositorio contiene un laboratorio Docker local para reproducir CVE-2026-46645, una vulnerabilidad de bypass de autorización que afecta al endpoint ajax_lookup de SQLAdmin.
SQLAdmin es una interfaz de administración para modelos de SQLAlchemy en aplicaciones Starlette y FastAPI. El comportamiento vulnerable ocurre cuando una aplicación restringe un ModelView con is_accessible(request), pero la ruta ajax_lookup de SQLAdmin no aplica la misma decisión de control de acceso antes de devolver los resultados de la búsqueda.
Este laboratorio compara dos versiones de SQLAdmin:
| Servicio | Versión de SQLAdmin | Propósito | URL |
|---|---|---|---|
vuln | 0.25.0 | Objetivo vulnerable | http://127.0.0.1:8001 |
patched | 0.25.1 | Objetivo de comparación parcheado | http://127.0.0.1:8002 |
La cadena de vulnerabilidad demostrada es:```text Authenticated low-privileged user → restricted SQLAdmin ModelView → ModelView.is_accessible(request) returns False → user directly requests the ajax_lookup endpoint → SQLAdmin 0.25.0 returns relationship lookup data → SQLAdmin 0.25.1 blocks the same request with HTTP 403
El laboratorio usa intencionalmente un modelo de datos simple de `Report` / `SecretProject` para que la omisión de autorización sea fácil de entender. Estos nombres de modelos no son la causa raíz de la vulnerabilidad. Solo se utilizan para crear una condición de reproducción controlada.
Este laboratorio está diseñado únicamente para investigación local controlada, comprensión a nivel de código fuente y demostración de portafolio.
## Datos verificados
| Afirmación | Evidencia | Cómo verificar en este laboratorio |
| ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
| El endpoint `ajax_lookup` de SQLAdmin es el componente afectado. | El aviso público describe el formato del endpoint afectado como `GET /{identity}/ajax/lookup?name=<field>&term=<query>`. | Ejecute el PoC y observe las solicitudes a `/admin/report/ajax/lookup?name=project&term=Secret`. |
| SQLAdmin `0.25.0` se utiliza como el objetivo vulnerable de comparación. | El laboratorio instala `sqladmin==0.25.0` en el contenedor `vuln`. | Ejecute `docker compose exec -T vuln python -m pip show sqladmin`. |
| SQLAdmin `0.25.1` se utiliza como el objetivo parcheado de comparación. | El aviso público y las notas de publicación identifican `0.25.1` como la versión corregida. | Ejecute `docker compose exec -T patched python -m pip show sqladmin`. |
| La causa raíz está en la ruta `Admin.ajax_lookup()` de SQLAdmin. | El parche agrega autenticación faltante y aplicación de `is_accessible(request)` a `ajax_lookup()`. | Inspeccione `Admin.ajax_lookup()` dentro de ambos contenedores con los comandos en este README. |
| El laboratorio crea un `ModelView` restringido. | `ReportAdmin.is_accessible(request)` devuelve intencionalmente `False`. | Inspeccione `app/main.py`. |
| El PoC utiliza una sesión autenticada. | El PoC primero inicia sesión en `/admin/login`, mantiene la cookie de sesión y luego solicita `ajax_lookup`. | Ejecute `python3 poc/poc.py --base-url http://127.0.0.1:8001`. |
| La señal vulnerable es la exposición de datos. | SQLAdmin `0.25.0` devuelve HTTP 200 y resultados de búsqueda JSON desde una vista restringida. | El objetivo vulnerable debería devolver `Secret Project Alpha` y `Secret Project Beta`. |
| La señal parcheada es la denegación de acceso. | SQLAdmin `0.25.1` devuelve HTTP 403 para la misma solicitud autenticada. | El objetivo parcheado debería devolver `403 Forbidden`. |
## Supuestos y Desconocidos
Este laboratorio utiliza `sqladmin==0.25.0` como línea base vulnerable y `sqladmin==0.25.1` como línea base parcheada.
El laboratorio se centra en la condición de omisión de autorización donde:```text
A user is authenticated,
the target ModelView is not accessible,
but ajax_lookup is requested directly.
El laboratorio no intenta reproducir todos los patrones de implementación posibles de SQLAdmin. Crea intencionalmente una pequeña aplicación Starlette con una vista de administración restringida para que la diferencia de comportamiento entre las versiones vulnerables y parcheadas sea fácil de verificar.
Los modelos Report y SecretProject son objetos solo del laboratorio. No forman parte de SQLAdmin en sí.
El PoC no intenta escalada de privilegios, modificación de datos, robo de sesiones, callbacks externos, persistencia o ataques contra sistemas que no sean del laboratorio.
La causa raíz está en la ruta Admin.ajax_lookup() de SQLAdmin ascendente, no en el código de la aplicación de este laboratorio.
SQLAdmin permite a los desarrolladores restringir el acceso a las vistas de administración sobrescribiendo:```python ModelView.is_accessible(request)
Se espera que otras rutas de administración apliquen esta decisión de control de acceso antes de permitir que la solicitud continúe. Por ejemplo, rutas como list, create, details, delete, edit y export verifican si la solicitud actual puede acceder al destino `ModelView`.
La ruta vulnerable `ajax_lookup` no aplicó la misma decisión de control de acceso.
El endpoint `ajax_lookup` es utilizado por la función `form_ajax_refs` de SQLAdmin para cargar dinámicamente valores de relaciones. Su formato de endpoint es:
``````text
/admin/<identity>/ajax/lookup?name=<field>&term=<query>
En la versión vulnerable, ajax_lookup() resuelve el ModelView objetivo, lee el nombre del campo de búsqueda y el término de búsqueda de la cadena de consulta, luego llama al cargador AJAX y devuelve resultados JSON. El paso de seguridad faltante es que no verifica primero si la solicitud actual tiene permiso para acceder a ese ModelView.
El impacto de seguridad es que un usuario autenticado puede tener bloqueado el acceso a una vista de administración restringida a través de rutas de UI normales, pero aún puede solicitar directamente el endpoint de búsqueda AJAX para esa vista y recibir datos de relaciones.
SQLAdmin 0.25.1 corrige esto aplicando control de acceso dentro de ajax_lookup(). La ruta parcheada verifica model_view.is_accessible(request) y devuelve HTTP 403 cuando la vista objetivo no es accesible.