
Laboratorio basato su Docker per riprodurre CVE-2026-46645, un bypass di autorizzazione nell'endpoint ajax_lookup di SQLAdmin. Include target vulnerabili e patchati, script PoC e passaggi di riproduzione manuale con curl per ricerca e formazione sulla sicurezza.
Questo repository contiene un laboratorio Docker locale per riprodurre la CVE-2026-46645, una vulnerabilità di bypass dell'autorizzazione che interessa l'endpoint ajax_lookup di SQLAdmin.
SQLAdmin è un'interfaccia di amministrazione per i modelli SQLAlchemy nelle applicazioni Starlette e FastAPI. Il comportamento vulnerabile si verifica quando un'applicazione limita una ModelView con is_accessible(request), ma la rotta ajax_lookup di SQLAdmin non applica la stessa decisione di controllo degli accessi prima di restituire i risultati della ricerca.
Questo laboratorio confronta due versioni di SQLAdmin:
| Service | SQLAdmin version | Purpose | URL |
|---|---|---|---|
vuln | 0.25.0 | Target vulnerabile | http://127.0.0.1:8001 |
patched | 0.25.1 | Target di confronto corretto | http://127.0.0.1:8002 |
La catena di vulnerabilità dimostrata è:```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
Il laboratorio utilizza intenzionalmente un semplice modello di dati `Report` / `SecretProject` per rendere facile da comprendere il bypass dell'autorizzazione. Questi nomi di modello non sono la causa principale della vulnerabilità. Sono usati solo per creare una condizione di riproduzione controllata.
Questo laboratorio è progettato solo per ricerca locale controllata, comprensione a livello di codice sorgente e dimostrazione per portfolio.
## Fatti verificati
| Affermazione | Evidenza | Come verificarlo in questo laboratorio |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| L'endpoint `ajax_lookup` di SQLAdmin è il componente interessato. | L'avviso pubblico descrive il formato dell'endpoint interessato come `GET /{identity}/ajax/lookup?name=<field>&term=<query>`. | Esegui il PoC e osserva le richieste a `/admin/report/ajax/lookup?name=project&term=Secret`. |
| SQLAdmin `0.25.0` è usato come target di confronto vulnerabile. | Il laboratorio installa `sqladmin==0.25.0` nel container `vuln`. | Esegui `docker compose exec -T vuln python -m pip show sqladmin`. |
| SQLAdmin `0.25.1` è usato come target di confronto corretto. | L'avviso pubblico e le note di rilascio identificano `0.25.1` come versione corretta. | Esegui `docker compose exec -T patched python -m pip show sqladmin`. |
| La causa principale è nella route `Admin.ajax_lookup()` dell'upstream di SQLAdmin. | La patch aggiunge l'autenticazione mancante e l'applicazione di `is_accessible(request)` a `ajax_lookup()`. | Ispeziona `Admin.ajax_lookup()` all'interno di entrambi i container con i comandi presenti in questo README. |
| Il laboratorio crea una `ModelView` ristretta. | `ReportAdmin.is_accessible(request)` restituisce intenzionalmente `False`. | Ispeziona `app/main.py`. |
| Il PoC utilizza una sessione autenticata. | Il PoC accede prima a `/admin/login`, mantiene il cookie di sessione e poi richiede `ajax_lookup`. | Esegui `python3 poc/poc.py --base-url http://127.0.0.1:8001`. |
| Il segnale vulnerabile è l'esposizione dei dati. | SQLAdmin `0.25.0` restituisce HTTP 200 e risultati di ricerca JSON da una vista ristretta. | Il target vulnerabile dovrebbe restituire `Secret Project Alpha` e `Secret Project Beta`. |
| Il segnale della versione corretta è la negazione dell'accesso. | SQLAdmin `0.25.1` restituisce HTTP 403 per la stessa richiesta autenticata. | Il target corretto dovrebbe restituire `403 Forbidden`. |
## Assunzioni e incognite
Questo laboratorio usa `sqladmin==0.25.0` come baseline vulnerabile e `sqladmin==0.25.1` come baseline corretta.
Il laboratorio si concentra sulla condizione di bypass dell'autorizzazione in cui:```text
A user is authenticated,
the target ModelView is not accessible,
but ajax_lookup is requested directly.
Il lab non tenta di riprodurre ogni possibile modello di distribuzione di SQLAdmin. Crea intenzionalmente una piccola applicazione Starlette con una vista admin limitata, così che la differenza di comportamento tra versioni vulnerabili e corrette sia facile da verificare.
I modelli Report e SecretProject sono oggetti solo del lab. Non fanno parte di SQLAdmin stesso.
Il PoC non tenta escalation dei privilegi, modifica dei dati, furto di sessione, callback esterni, persistenza o attacchi contro sistemi non del lab.
La causa principale si trova nella route Admin.ajax_lookup() a monte di SQLAdmin, non nel codice applicativo di questo lab.
SQLAdmin consente agli sviluppatori di limitare l'accesso alle viste admin sovrascrivendo:```python ModelView.is_accessible(request)
Le altre route di amministrazione sono tenute ad applicare questa decisione di controllo accessi prima di consentire alla richiesta di proseguire. Ad esempio, route come `list`, `create`, `details`, `delete`, `edit` ed `export` verificano se la richiesta corrente è autorizzata ad accedere alla `ModelView` di destinazione.
La route vulnerabile `ajax_lookup` non applicava la stessa decisione di controllo accessi.
L'endpoint `ajax_lookup` è utilizzato dalla funzionalità `form_ajax_refs` di SQLAdmin per caricare dinamicamente i valori delle relazioni. Il suo formato endpoint è:```text
/admin/<identity>/ajax/lookup?name=<field>&term=<query>
Nella versione vulnerabile, ajax_lookup() risolve il ModelView di destinazione, legge il nome del campo di lookup e il termine di ricerca dalla query string, quindi chiama il loader AJAX e restituisce risultati JSON. Il passo di sicurezza mancante è che non verifica prima se la richiesta corrente è autorizzata ad accedere a quel ModelView.
L'impatto sulla sicurezza è che un utente autenticato potrebbe essere bloccato dall'accesso a una vista admin ristretta attraverso le normali rotte UI, ma può comunque richiedere direttamente l'endpoint di lookup AJAX per quella vista e ricevere dati di relazione.
SQLAdmin 0.25.1 corregge questo problema imponendo il controllo degli accessi all'interno di ajax_lookup(). La rotta corretta controlla model_view.is_accessible(request) e restituisce HTTP 403 quando la vista di destinazione non è accessibile.
Questo laboratorio definisce ReportAdmin.is_accessible(request) per restituire False solo per riprodurre la condizione vulnerabile. Il codice del laboratorio non è la causa principale. È un banco di prova controllato che dimostra se la rotta ajax_lookup() a monte di SQLAdmin rispetta la decisione di controllo degli accessi.
Differenza di comportamento prevista:```text sqladmin 0.25.0 -> HTTP 200 with JSON lookup results sqladmin 0.25.1 -> HTTP 403 Forbidden
## Riepilogo della patch sorgente
La patch significativa a monte è l'aggiunta dell'applicazione dell'autenticazione e dell'autorizzazione a `Admin.ajax_lookup()`.
Il comportamento corretto è equivalente a:```python
@login_required
async def ajax_lookup(self, request):
identity = request.path_params["identity"]
model_view = self._find_model_view(identity)
if not model_view.is_accessible(request):
raise HTTPException(status_code=403)