
Lab Docker locale per CVE-2026-44338, un bypass dell'autenticazione nel server API Flask legacy di PraisonAI.
Questo lab dimostra la condizione di accesso non autenticato sulle route API legacy. Usa intenzionalmente una riproduzione sicura a livello di route invece di una distribuzione PraisonAI completa, così la prova resta focalizzata sulla falla di autenticazione e non attiva reali flussi di lavoro degli agenti né chiamate LLM esterne.
CVE-2026-44338 riguarda le versioni di PraisonAI >= 2.5.6 e <= 4.6.33.
Nel server API legacy vulnerabile, l'autenticazione era disabilitata per impostazione predefinita. Di conseguenza, un chiamante non autenticato che riuscisse a raggiungere il server API poteva accedere a /agents e attivare la route del flusso di lavoro /chat senza un bearer token.
Il problema è stato corretto in PraisonAI 4.6.34 modificando il comportamento predefinito per richiedere l'autenticazione a meno che non venga esplicitamente disabilitata.
Nella versione vulnerabile, il server API legacy utilizzava impostazioni di autenticazione non sicure:
AUTH_ENABLED = False
AUTH_TOKEN = None
def check_auth():
if not AUTH_ENABLED:
return True
Poiché check_auth() restituiva True quando l'autenticazione era disabilitata, le route protette fallivano in modalità aperta.
Le route interessate includevano:
GET /agentsPOST /chatLa versione corretta modifica la postura predefinita in modo che l'autenticazione sia abilitata a meno che non venga esplicitamente disabilitata tramite configurazione.
Il problema centrale non era una primitiva di exploit complessa. Derivava da impostazioni predefinite non sicure nel server API Flask legacy.
v4.6.33In v4.6.33, l'autenticazione era disabilitata per impostazione predefinita:
AUTH_ENABLED = False
AUTH_TOKEN = None
Il controllo di autenticazione falliva quindi in modalità aperta:
def check_auth():
if not AUTH_ENABLED:
return True
Ciò significa che la richiesta veniva accettata ogni volta che l'autenticazione era disabilitata, anche se il chiamante non inviava un header Authorization.
Il flusso vulnerabile era:
AUTH_ENABLED = False
↓
check_auth() restituisce True
↓
GET /agents è consentito
POST /chat è consentito
↓
un chiamante non autenticato può accedere ai metadati degli agenti e raggiungere la route di attivazione del flusso di lavoro
La parte sensibile è che /chat non era solo un endpoint di stato. Accettava un messaggio utente e poi chiamava il runner di flusso di lavoro di PraisonAI usando agents.yaml.
v4.6.34In v4.6.34, il comportamento predefinito è stato modificato per richiedere l'autenticazione a meno che l'operatore non la disabiliti esplicitamente:
AUTH_ENABLED = os.environ.get("PRAISONAI_API_AUTH", "enabled").strip().lower() != "disabled"
AUTH_TOKEN = os.environ.get("PRAISONAI_API_TOKEN") or None
La versione corretta migliora anche la gestione del token:
secrets.compare_digest()127.0.0.1 per impostazione predefinita invece di esporsi su tutte le interfacceIl flusso corretto è:
AUTH_ENABLED = True per impostazione predefinita
↓
la richiesta deve includere un Bearer token valido
↓
token mancante o non valido restituisce 401
↓
/agents e /chat non sono più raggiungibili in modo anonimo
Questo lab rispecchia quella differenza a livello di sorgente:
vuln -> autenticazione disabilitata per impostazione predefinita, richieste non autenticate restituiscono 200
patched -> autenticazione richiesta per impostazione predefinita, richieste non autenticate restituiscono 401
Il lab contiene due servizi locali:
| Servizio | URL | Comportamento |
|---|---|---|
vuln | http://127.0.0.1:8081 | Riproduce il comportamento vulnerabile fail-open |
patched | http://127.0.0.1:8082 | Richiede l'autenticazione con bearer token |
Entrambi i servizi sono vincolati solo a 127.0.0.1.
La route /chat usa un runner fittizio invece di un vero flusso di lavoro PraisonAI. Questo fornisce una prova osservabile che la richiesta non autenticata raggiunge il percorso di attivazione del flusso di lavoro senza causare effetti collaterali esterni.
.
├── docker-compose.yml
├── vuln
│ ├── Dockerfile
│ └── start_server.py
├── patched
│ ├── Dockerfile
│ └── start_server.py
├── poc
│ └── poc.py
└── .gitignore
└── README.md
docker compose up --build -d
python3 poc/poc.py
Il servizio vulnerabile consente l'accesso non autenticato:
=== vuln ===
[unauthenticated] GET /agents
status: 200
[unauthenticated] POST /chat
status: 200
verdict: LIKELY_VULNERABLE
Il servizio corretto blocca l'accesso non autenticato:
=== patched ===
[unauthenticated] GET /agents
status: 401
[unauthenticated] POST /chat
status: 401
verdict: NOT_VULNERABLE_OR_PROTECTED
Riepilogo finale atteso:
vuln: LIKELY_VULNERABLE
patched: NOT_VULNERABLE_OR_PROTECTED
Controlla la route vulnerabile:
curl -i http://127.0.0.1:8081/agents
Risposta vulnerabile attesa:
HTTP/1.1 200 OK
Controlla la route corretta:
curl -i http://127.0.0.1:8082/agents
Risposta corretta attesa:
HTTP/1.1 401 UNAUTHORIZED
I log del server dovrebbero mostrare chiaramente la differenza:
vuln: "GET /agents HTTP/1.1" 200
patched: "GET /agents HTTP/1.1" 401
docker compose down -v
Questo lab è inteso solo per ricerca di sicurezza locale.
La PoC non:
Advisory GitHub: GHSA-6rmh-7xcm-cpxj https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-6rmh-7xcm-cpxj
NVD: CVE-2026-44338 https://nvd.nist.gov/vuln/detail/CVE-2026-44338
OSV: GHSA-6rmh-7xcm-cpxj https://osv.dev/vulnerability/GHSA-6rmh-7xcm-cpxj
Sorgente vulnerabile: PraisonAI v4.6.33 src/praisonai/api_server.py
https://raw.githubusercontent.com/MervinPraison/PraisonAI/v4.6.33/src/praisonai/api_server.py
Sorgente corretta: PraisonAI v4.6.34 src/praisonai/api_server.py
https://raw.githubusercontent.com/MervinPraison/PraisonAI/v4.6.34/src/praisonai/api_server.py