
Laboratoire basé sur Docker démontrant le contournement d'authentification CVE-2026-44338 dans l'API Flask legacy de PraisonAI. Inclut des services vulnérables et corrigés avec un script PoC pour la recherche en sécurité locale.
Laboratoire Docker local pour CVE-2026-44338, un contournement d'authentification dans l'ancien serveur API Flask de PraisonAI.
Ce laboratoire démontre la condition d'accès non authentifié sur les routes API héritées. Il utilise intentionnellement une reproduction sécurisée au niveau des routes plutôt qu'un déploiement complet de PraisonAI, afin que la preuve reste concentrée sur la faille d'authentification et ne déclenche pas de véritables workflows d'agents ou d'appels LLM externes.
CVE-2026-44338 affecte les versions de PraisonAI >= 2.5.6 et <= 4.6.33.
Dans l'ancien serveur API vulnérable, l'authentification était désactivée par défaut. Par conséquent, un appelant non authentifié capable d'atteindre le serveur API pouvait accéder à /agents et déclencher la route de workflow /chat sans jeton porteur.
Le problème a été corrigé dans PraisonAI 4.6.34 en modifiant le comportement par défaut pour exiger une authentification sauf si elle est explicitement désactivée.
Dans la version vulnérable, l'ancien serveur API utilisait des paramètres d'authentification non sécurisés :
AUTH_ENABLED = False
AUTH_TOKEN = None
def check_auth():
if not AUTH_ENABLED:
return True
Comme check_auth() renvoyait True lorsque l'authentification était désactivée, les routes protégées étaient ouvertes en échec.
Les routes affectées incluaient :
GET /agentsPOST /chatLa version corrigée modifie la posture par défaut pour que l'authentification soit activée sauf si elle est explicitement désactivée via la configuration.
Le problème central n'était pas une primitive d'exploitation complexe. Il provenait de paramètres par défaut non sécurisés dans l'ancien serveur API Flask.
v4.6.33Dans v4.6.33, l'authentification était désactivée par défaut :
AUTH_ENABLED = False
AUTH_TOKEN = None
La vérification d'authentification échouait alors en mode ouvert :
def check_auth():
if not AUTH_ENABLED:
return True
Cela signifie que la requête était acceptée chaque fois que l'authentification était désactivée, même si l'appelant n'envoyait pas d'en-tête Authorization.
Le flux vulnérable était :
AUTH_ENABLED = False
↓
check_auth() returns True
↓
GET /agents is allowed
POST /chat is allowed
↓
unauthenticated caller can access agent metadata and reach the workflow trigger route
La partie sensible est que /chat n'était pas simplement un point de terminaison de statut. Il acceptait un message utilisateur puis appelait l'exécuteur de workflow PraisonAI en utilisant agents.yaml.
v4.6.34Dans v4.6.34, le comportement par défaut a été modifié pour exiger une authentification sauf si l'opérateur la désactive explicitement :
AUTH_ENABLED = os.environ.get("PRAISONAI_API_AUTH", "enabled").strip().lower() != "disabled"
AUTH_TOKEN = os.environ.get("PRAISONAI_API_TOKEN") or None
La version corrigée améliore également le comportement de gestion des jetons :
secrets.compare_digest()127.0.0.1 par défaut au lieu de s'exposer sur toutes les interfacesLe flux corrigé est :
AUTH_ENABLED = True by default
↓
request must include a valid Bearer token
↓
missing or invalid token returns 401
↓
/agents and /chat are no longer reachable anonymously
Ce laboratoire reflète cette différence au niveau source :
vuln -> auth disabled by default, unauthenticated requests return 200
patched -> auth required by default, unauthenticated requests return 401
Le laboratoire contient deux services locaux :
| Service | URL | Comportement |
|---|---|---|
vuln | http://127.0.0.1:8081 | Reproduit le comportement d'authentification vulnérable en mode ouvert |
patched | http://127.0.0.1:8082 | Nécessite une authentification par jeton porteur |
Les deux services sont liés à 127.0.0.1 uniquement.
La route /chat utilise un exécuteur factice au lieu d'un véritable workflow PraisonAI. Cela fournit une preuve observable que la requête non authentifiée atteint le chemin de déclenchement du workflow sans provoquer d'effets secondaires externes.
.
├── 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
Le service vulnérable autorise l'accès non authentifié :
=== vuln ===
[unauthenticated] GET /agents
status: 200
[unauthenticated] POST /chat
status: 200
verdict: LIKELY_VULNERABLE
Le service corrigé bloque l'accès non authentifié :
=== patched ===
[unauthenticated] GET /agents
status: 401
[unauthenticated] POST /chat
status: 401
verdict: NOT_VULNERABLE_OR_PROTECTED
Résumé final attendu :
vuln: LIKELY_VULNERABLE
patched: NOT_VULNERABLE_OR_PROTECTED
Vérifiez la route vulnérable :
curl -i http://127.0.0.1:8081/agents
Réponse vulnérable attendue :
HTTP/1.1 200 OK
Vérifiez la route corrigée :
curl -i http://127.0.0.1:8082/agents
Réponse corrigée attendue :
HTTP/1.1 401 UNAUTHORIZED
Les journaux du serveur devraient montrer clairement la différence :
vuln: "GET /agents HTTP/1.1" 200
patched: "GET /agents HTTP/1.1" 401
docker compose down -v
Ce laboratoire est destiné uniquement à la recherche en sécurité locale.
La preuve de concept ne :
Avis 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
Source vulnérable : PraisonAI v4.6.33 src/praisonai/api_server.py
https://raw.githubusercontent.com/MervinPraison/PraisonAI/v4.6.33/src/praisonai/api_server.py
Source corrigée : PraisonAI v4.6.34 src/praisonai/api_server.py
https://raw.githubusercontent.com/MervinPraison/PraisonAI/v4.6.34/src/praisonai/api_server.py