
Reproduction bénigne et autonome de CVE-2026-77521 (exécution de commandes par injection de prompt dans MaxKB)
Un laboratoire autonome et bénin qui reproduit
GHSA-f36j-f34j-h3rx
/ CVE-2026-77521 : dans MaxKB (une
plateforme open-source d'assistant IA d'entreprise / RAG), tout assistant doté d'un outil,
d'un outil MCP, d'une compétence ou d'une sous-application reçoit un outil shell execute
auto-injecté qui n'est pas protégé par une validation humaine — ainsi une injection de prompt (depuis le chat
ou un document ingéré) atteint un shell. Un bac à sable était censé le contenir,
mais une chaîne shell=True laissait les métacaractères s'en échapper.
| Avis | GHSA-f36j-f34j-h3rx |
| CVE | CVE-2026-77521 |
| Projet | 1Panel-dev/MaxKB |
| Affecté | < 2.10.5-lts |
| Corrigé | 2.10.5-lts (commit 594f50f) — partiel, voir ci-dessous |
| CVSS | 10.0 CRITIQUE (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) |
| Faiblesse | CWE-78 (injection de commande OS) · CWE-250 (privilèges inutiles) · CWE-749 (méthode dangereuse exposée) |
Ceci reproduit une vulnérabilité corrigée et divulguée publiquement à des fins éducatives et défensives. Il s'exécute entièrement en local et de manière bénigne :
id, l'écriture d'un fichier marqueur
que le PoC supprime). Aucun vrai gosu/bac à sable/root n'est impliqué — l'enveloppe d'abandon de privilèges
est remplacée par un marqueur env inerte afin que la démo s'exécute partout.Un assistant MaxKB doté d'une capacité quelconque est construit sur deepagents avec
un SandboxShellBackend. deepagents injecte automatiquement un outil shell execute (ainsi que
read_file/write_file/…), et MaxKB n'utilise aucun excluded_tools, donc execute est
toujours présent. La configuration interrupt_on — qui décide de ce qui nécessite une validation
humaine — est {"write_file": False, "read_file": False, "edit_file": False} :
execute n'y figure pas, il s'exécute donc sans aucune validation. Tout texte capable d'orienter
le modèle (un message de chat, ou un document empoisonné que le pipeline RAG ingère) peut
donc amener l'agent à exécuter des commandes shell. Sur les installations depuis les sources, SANDBOX
est désactivé par défaut, donc les commandes s'exécutent en tant qu'utilisateur de l'application ; dans le conteneur
officiel (root), l'enveloppe du bac à sable était une chaîne shell=True, donc ;/>/$() s'exécutaient en dehors
de gosu, en tant que root.
interrupt_on de MaxKB, un appel execute injecté s'exécute sans validation ; ajouter
"execute": True le protège. → poc/01_missing_hitl_gate.py;+redirection s'échappe du
préfixe gosu en chaîne unique ; le re-quoting de _build_sandbox_command() dans la 2.10.5-lts
le neutralise. → poc/02_sandbox_escape.pyexecute avec la commande de l'attaquant (et ne le fait pas sur un document
propre). → poc/03_real_llm.pyLe correctif 2.10.5-lts livré (commit 594f50f)
ne durcit que l'évasion du bac à sable — il ajoute _build_sandbox_command(), qui
découpe selon les opérateurs shell, ré-échappe chaque jeton avec shlex.quote, et enveloppe
chaque commande simple dans le préfixe gosu. Vérifié par rapport à la
source v2.10.5-lts,
le cœur de l'agence est inchangé : interrupt_on omet toujours execute, le
backend est toujours SandboxShellBackend, et SANDBOX vaut toujours 0 par défaut. Ainsi,
après le correctif, un execute injecté par prompt s'exécute toujours sans approbation humaine —
le rempart interne a été renforcé tandis que la porte est restée ouverte. Le PoC 1 démontre
l'atténuation recommandée (filtrer execute), que le fournisseur n'a pas livrée.
Aucune dépendance pour les PoC 1/2 (Python 3.9+) :
./run.sh # all three (PoC 3 skips without an endpoint)
python3 poc/01_missing_hitl_gate.py # missing approval gate
python3 poc/02_sandbox_escape.py # metacharacter sandbox escape
Vous présentez cela ?
DEMO.mdest un runbook etdemo.shle pilote une touche à la fois.
Tout endpoint compatible OpenAI (auto-hébergé ou modèle ouvert hébergé). Exemple (Groq) :
export OPENAI_BASE_URL=https://api.groq.com/openai/v1
export OPENAI_API_KEY=$GROQ_API_KEY # read from env only; never logged
MODEL="openai/gpt-oss-20b" python3 poc/03_real_llm.py
Derrière un proxy d'inspection TLS, il réessaie sans vérification avec un avertissement
(INSECURE_SSL=1 pour le forcer). Il s'arrête proprement si aucun endpoint n'est défini.
| Chemin | Ce que c'est |
|---|---|
sandbox_shell.py | Modèle bénin de la construction de commande execute() de MaxKB — chaîne vulnérable vs _build_sandbox_command() corrigée |
agent_sim.py | Modèle minimal du portail d'approbation humaine interrupt_on de deepagents |
payloads/poisoned-document.md | Document de base de connaissances empoisonné par l'attaquant (injection indirecte) |
poc/01_missing_hitl_gate.py | L'execute injecté s'exécute sans approbation ; le portail le bloque |
poc/02_sandbox_escape.py | Les métacaractères échappent au préfixe gosu ; le parseur 2.10.5 les contient |
poc/03_real_llm.py | Un modèle réel appelle execute depuis le document empoisonné (propre vs empoisonné) |
DEMO.md / demo.sh | Guide d'exécution du présentateur et pilote de démo rythmé |
assets/attack-flow.{svg,png} | Diagramme du flux d'attaque (visuel principal du blog) |
assets/poc3-groq-sample.txt | Sortie enregistrée de la PoC 3 (solution de repli pour la démo) |