
PoC pour CVE-2026-18953 — écriture arbitraire de fichier (CWE-22) dans l'outil get_resource de awslabs.aws-transform-mcp-server via le paramètre savePath
| CVE | CVE-2026-18953 |
| CWE | CWE-22 — Limitation inappropriée d'un nom de chemin à un répertoire restreint |
| Versions affectées | awslabs.aws-transform-mcp-server 0.1.0 – 0.1.4 |
| Corrigé dans | 0.1.5 |
| CVSS v3.1 | 8.6 ÉLEVÉE — AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H |
| CVSS v4.0 | 6.3 MOYENNE — AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:H/SI:H/SA:H |
| Avis | GHSA-66mr-jr63-2jgw |
| Bulletin | AWS Security Bulletin 2026-075 |
| Signaleur | Drew Raines (divulgation coordonnée) |
| Publié | 2026-08-05 |
get_resource(resource="artifact" | "asset", ...) télécharge un fichier depuis une
URL S3 pré-signée et, lorsque l'appelant transmet savePath / fileName, l'enregistre
sur le disque local via :
tools/get_resource.py -> tool_utils.download_s3_content()
-> file_validation.validate_write_path()
Dans <= 0.1.4, validate_write_path() se contente de :
save_path avec os.path.realpath(os.path.expanduser(...)).~/.aws, ~/.ssh, ~/.gnupg,
~/.docker, ~/.aws-transform-mcp, /etc/shadow, /etc/passwd).file_name via os.path.basename().Il ne confine jamais le répertoire résolu à un répertoire de base/de travail, et
BLOCKED_FILENAMES (.bashrc, .zshrc, authorized_keys, id_rsa, …) n'est
appliquée que sur les lectures, pas sur les écritures. Ainsi, tout client MCP de
ce serveur — y compris un agent indirectement victime d'une injection via le prompt à
partir du contenu non fiable de jobs/tâches/messages qu'il récupère via ce même outil —
peut définir savePath sur un chemin absolu, une traversée ../.. ou un nom de
dotfile sensible, et le serveur y écrit des octets influencés par l'attaquant. C'est
une primitive d'écriture de fichier en dehors du répertoire auquel l'opérateur croit
que les téléchargements sont confinés ; l'avis note que cela « pourrait mener à une
exécution de code locale » (par exemple en écrasant un fichier de démarrage du shell).
0.1.5 corrige cela en ajoutant un répertoire de base explicitement autorisé
(_ALLOWED_WRITE_BASE, tiré de $AWS_TRANSFORM_MCP_WRITE_DIR ou du répertoire de
travail courant du serveur au démarrage) auquel tout chemin d'écriture résolu doit
appartenir, et en appliquant également BLOCKED_FILENAMES sur le chemin d'écriture
résolu final.
Diff complet de la cause racine : vendor/0.1.4-vulnerable/file_validation.py
vs. vendor/0.1.5-fixed/file_validation.py
(les deux extraits tels quels de PyPI / GitHub, Apache-2.0).
Un client MCP (Q Developer, Kiro, Claude, etc. connecté à ce serveur) émet un appel d'outil tel que :
{
"name": "get_resource",
"arguments": {
"resource": "artifact",
"workspaceId": "ws-...",
"jobId": "job-...",
"artifactId": "art-...",
"savePath": "/Users/victim/Library/LaunchAgents",
"fileName": "com.evil.persist.plist"
}
}
ou, depuis un répertoire sandbox relatif :
{ "savePath": "../../../../../../Users/victim/.bashrc", "fileName": "x" }
Comme l'outil décrit les réponses resource="task" comme des éléments que l'agent
doit lire et traiter, et que le contenu de resource="messages" provient de données
de chat/jobs, un collaborateur de l'espace de travail (ou une source amont compromise
de jobs/messages) n'a pas besoin de convaincre un humain de saisir cela — il suffit
d'orienter un agent déjà connecté pour qu'il appelle get_resource avec un savePath
hostile. Aucun identifiant AWS réel n'est requis pour démontrer la faille elle-même,
car le bug réside entièrement dans la gestion des chemins locaux, avant/après la
récupération S3.
python3 poc.py
Aucune dépendance tierce, aucun compte AWS, aucun accès réseau à AWS. Le script :
file_validation.py réel et non modifié des versions 0.1.4 et
0.1.5+ (voir vendor/).download_s3_content() avec le flux de contrôle exact de
tool_utils.py en amont (httpx remplacé par urllib de la bibliothèque standard
uniquement — zéro dépendance, même logique), et le pilote exactement comme le ferait
get_resource.savePath/fileName malveillantes — échappement par chemin
absolu, traversée relative ../.. et nom de dotfile bloqué en lecture seule —
contre le validateur vulnérable, puis contre le validateur corrigé.Tout se passe dans un répertoire temporaire jetable créé par mkdtemp() ; votre vrai
$HOME n'est jamais touché. Exemple de sortie :
=== 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): ...
Mettez à jour vers awslabs.aws-transform-mcp-server >= 0.1.5. Il n'existe aucun
contournement côté serveur pour les versions antérieures ; l'avis recommande la mise à
jour. Les opérateurs qui ne peuvent pas mettre à jour immédiatement doivent exécuter le
serveur avec son répertoire de travail courant défini sur un répertoire dédié et vide,
et considérer tout fichier qu'il peut écrire comme compromis.
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