
# Laboratório baseado em Docker para reproduzir o CVE-2026-46645, uma bypass de autorização no endpoint ajax_lookup do SQLAdmin. Inclui alvos vulneráveis e corrigidos, script de PoC e etapas manuais de reprodução com curl para pesquisa e educação em segurança.
Este repositório contém um laboratório Docker local para reproduzir a CVE-2026-46645, uma vulnerabilidade de bypass de autorização que afeta o endpoint ajax_lookup do SQLAdmin.
SQLAdmin é uma interface administrativa para modelos SQLAlchemy em aplicações Starlette e FastAPI. O comportamento vulnerável ocorre quando uma aplicação restringe um ModelView com is_accessible(request), mas a rota ajax_lookup do SQLAdmin não aplica a mesma decisão de controle de acesso antes de retornar os resultados da consulta.
Este laboratório compara duas versões do SQLAdmin:
| Serviço | Versão do SQLAdmin | Finalidade | URL |
|---|---|---|---|
vuln | 0.25.0 | Alvo vulnerável | http://127.0.0.1:8001 |
patched | 0.25.1 | Alvo de comparação corrigido | http://127.0.0.1:8002 |
A cadeia de vulnerabilidade demonstrada é:```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
O laboratório usa intencionalmente um modelo de dados simples `Report` / `SecretProject` para tornar o bypass de autorização fácil de entender. Esses nomes de modelos não são a causa raiz da vulnerabilidade. Eles são usados apenas para criar uma condição controlada de reprodução.
Este laboratório foi projetado apenas para pesquisa local controlada, compreensão no nível do código-fonte e demonstração de portfólio.
## Fatos Verificados
| Afirmação | Evidência | Como verificar neste laboratório |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| O endpoint `ajax_lookup` do SQLAdmin é o componente afetado. | O advisory público descreve o formato do endpoint afetado como `GET /{identity}/ajax/lookup?name=<field>&term=<query>`. | Execute o PoC e observe as requisições para `/admin/report/ajax/lookup?name=project&term=Secret`. |
| SQLAdmin `0.25.0` é usado como alvo vulnerável de comparação. | O laboratório instala `sqladmin==0.25.0` no contêiner `vuln`. | Execute `docker compose exec -T vuln python -m pip show sqladmin`. |
| SQLAdmin `0.25.1` é usado como alvo corrigido de comparação. | O advisory público e as notas de versão identificam `0.25.1` como a versão corrigida. | Execute `docker compose exec -T patched python -m pip show sqladmin`. |
| A causa raiz está na rota `Admin.ajax_lookup()` do upstream do SQLAdmin. | O patch adiciona autenticação ausente e aplicação de `is_accessible(request)` em `ajax_lookup()`. | Inspecione `Admin.ajax_lookup()` dentro de ambos os contêineres com os comandos neste README. |
| O laboratório cria uma `ModelView` restrita. | `ReportAdmin.is_accessible(request)` retorna `False` intencionalmente. | Inspecione `app/main.py`. |
| O PoC usa uma sessão autenticada. | O PoC primeiro faz login em `/admin/login`, mantém o cookie da sessão e então faz a requisição a `ajax_lookup`. | Execute `python3 poc/poc.py --base-url http://127.0.0.1:8001`. |
| O sinal vulnerável é a exposição de dados. | O SQLAdmin `0.25.0` retorna HTTP 200 e resultados de lookup JSON de uma view restrita. | O alvo vulnerável deve retornar `Secret Project Alpha` e `Secret Project Beta`. |
| O sinal corrigido é a negação de acesso. | O SQLAdmin `0.25.1` retorna HTTP 403 para a mesma requisição autenticada. | O alvo corrigido deve retornar `403 Forbidden`. |
## Premissas e Incógnitas
Este laboratório usa `sqladmin==0.25.0` como baseline vulnerável e `sqladmin==0.25.1` como baseline corrigido.
O laboratório concentra-se na condição de bypass de autorização em que:```text
A user is authenticated,
the target ModelView is not accessible,
but ajax_lookup is requested directly.
O laboratório não tenta reproduzir todos os padrões de implantação possíveis do SQLAdmin. Ele cria intencionalmente uma pequena aplicação Starlette com uma visão administrativa restrita para que a diferença de comportamento entre versões vulneráveis e corrigidas seja fácil de verificar.
Os modelos Report e SecretProject são objetos apenas do laboratório. Eles não fazem parte do próprio SQLAdmin.
O PoC não tenta escalonamento de privilégios, modificação de dados, roubo de sessão, callbacks externos, persistência ou ataques contra sistemas fora do laboratório.
A causa raiz está na rota Admin.ajax_lookup() do SQLAdmin a montante, não no código da aplicação deste laboratório.
O SQLAdmin permite que desenvolvedores restrinjam o acesso às visões administrativas sobrescrevendo:```python ModelView.is_accessible(request)
Espera-se que outras rotas administrativas apliquem essa decisão de controle de acesso antes de permitir que a solicitação continue. Por exemplo, rotas como list, create, details, delete, edit e export verificam se a solicitação atual tem permissão para acessar o `ModelView` de destino.
A rota vulnerável `ajax_lookup` não aplicava a mesma decisão de controle de acesso.
O endpoint `ajax_lookup` é usado pelo recurso `form_ajax_refs` do SQLAdmin para carregar dinamicamente valores de relacionamentos. Seu formato de endpoint é:```text
/admin/<identity>/ajax/lookup?name=<field>&term=<query>
Na versão vulnerável, ajax_lookup() resolve o ModelView alvo, lê o nome do campo de busca e o termo de pesquisa da query string, então chama o carregador AJAX e retorna resultados JSON. A etapa de segurança ausente é que ele não verifica primeiro se a requisição atual tem permissão para acessar esse ModelView.
O impacto de segurança é que um usuário autenticado pode ser bloqueado de acessar uma view administrativa restrita pelas rotas normais da interface, mas ainda pode solicitar diretamente o endpoint de busca AJAX para essa view e receber dados de busca de relacionamento.
O SQLAdmin 0.25.1 corrige isso aplicando controle de acesso dentro de ajax_lookup(). A rota corrigida verifica model_view.is_accessible(request) e retorna HTTP 403 quando a view alvo não está acessível.
Este laboratório define ReportAdmin.is_accessible(request) para retornar False apenas para reproduzir a condição vulnerável. O código do laboratório não é a causa raiz. É um ambiente de teste controlado que comprova se a rota ajax_lookup() upstream do SQLAdmin respeita a decisão de controle de acesso.
Diferença de comportamento esperada:```text sqladmin 0.25.0 -> HTTP 200 with JSON lookup results sqladmin 0.25.1 -> HTTP 403 Forbidden
## Resumo do Patch de Origem
O patch upstream significativo é a adição de aplicação de autenticação e autorização em `Admin.ajax_lookup()`.
O comportamento corrigido é 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)