Creuto is now an OpenAI Select Partner Read More

Custom Software Development

KindaRails2Shell: a fake MATLAB header reads secrets

KindaRails2Shell turns an avatar upload into arbitrary file read. CVE-2026-66066 depends on your image processor, not your Rails version — vips is affected.

KindaRails2Shell: a fake MATLAB header reads secrets

KindaRails2Shell is the name given to CVE-2026-66066, and the chain is worth understanding precisely because the thing that decides your exposure is not your Rails version. It is which image processor Active Storage uses. Applications on vips are affected; applications on ImageMagick are not affected by this vector. Since load_defaults 7.0 made vips the default and no later default changed it, most Rails 7 and 8 apps that accept image uploads are in scope.

The flaw is rated Critical at CVSS 9.5 and patched in Active Storage 7.2.3.2, 8.0.5.1 and 8.1.3.1. We have not reproduced it and do not work in Rails; everything below is read off the Rails advisory and the researchers' write-ups, and we say where they disagree.

The KindaRails2Shell chain, in five steps

From Ethiack's technical write-up, which is the source for the mechanism.

1. A header that lies. libvips includes a matload loader backed by libmatio, which libvips itself flags as untrusted. Its sniffer accepts a file whose header begins with the literal string MATLAB 5.0 — and that string is mandatory for the vector, because it is what routes the file to libmatio in the first place.

2. A body that is something else. libmatio then dispatches on a 2-byte version word at offset 124: 0x0100 means MAT v5, 0x0200 means the HDF5 backend. The crafted file carries the MATLAB 5.0 text header and an HDF5 body with the version word set to 0x0200, so libvips routes it one way and libmatio reads it another.

3. HDF5 external storage becomes a file read. HDF5 supports datasets whose bytes live in a separate file, via H5Pset_external. libmatio's mat73.c never calls H5Pget_external_count(), so it does not notice. Point a dataset at /etc/passwd and H5Dread reads it. That is the primitive: arbitrary file read as the user running the worker.

4. Rails hands it the file. This is the part that makes it reachable from the internet. Blob#variable? checks the declared content_type against an allowlist, and create_before_direct_upload! stores the client-supplied content type without verifying it. So an attacker POSTs to /rails/active_storage/direct_uploads declaring image/png, PUTs the .mat bytes, and Rails treats it as a PNG. libvips then sniffs the actual bytes.

5. Getting the bytes back out. The representations route accepts a variation key that signs only the transform — something like {"resize_to_limit":[1024,768]} under the purpose variation — and not the blob. Ethiack describes those keys as "blob-independent and replayable", harvestable from any public response. The read is then done as 1x1 images, one byte per request, byte-exact.

Why a file read becomes remote code execution

The escalation is the part that moves this from serious to critical, and it runs through your application's own secrets.

The same arbitrary read reaches config/master.key and config/credentials.yml.enc, or SECRET_KEY_BASE and RAILS_MASTER_KEY from /proc/self/environ. With secret_key_base, an attacker can forge their own variation key — Ethiack gives the derivation as PBKDF2-HMAC-SHA256 with the salt ActiveStorage, 1000 iterations, 64 bytes — so they no longer need to harvest one.

From there, Ethiack describes this as an incomplete fix of the earlier CVE-2025-24293: the vips transformer did not enforce ActiveStorage.supported_image_processing_methods, which was only enforced on the ImageMagick path, so a forged transform could reach instance_eval with Ruby string arguments. Their write-up states their proof of concept went "from arbitrary file read, to secret disclosure, to remote code execution".

Are you exposed? The sources disagree on 6.x

The Rails advisory sets two conditions: the app "Uses libvips for Active Storage image processing" — that is config.active_storage.variant_processor = :vips — and "Allows image uploads from untrusted users". It adds that "Generating variants is not a separate requirement", which is easy to miss and matters: you do not have to be producing thumbnails to be affected.

VersionRails advisoryEthiack
6.0.0–6.1.7.10Covered by "< 7.2.3.2", no carve-outAffected only with customised Active Storage
7.0.0–7.2.3.1AffectedAffected on default config
8.0.0–8.0.5AffectedAffected
8.1.0–8.1.3AffectedAffected

If you are on 6.x, that disagreement is yours to resolve rather than ours. The advisory's literal range sweeps you in; the researchers say you are only affected with a non-default configuration. The safe reading is the advisory's, and the cheap way to settle it is to check your own variant_processor setting rather than reason from version numbers.

There is a further precondition the advisory does not mention and Ethiack does: libvips must be built against libmatio. They note that Debian and Ubuntu's libvips42 links libmatio13, and that the official Rails Docker image qualifies — so "we did not install libmatio" is not a defence if you use a distro package or that image.

What is confirmed, and what is not

Being careful here, because this one attracted claims that outran the evidence.

Confirmed: patched versions 7.2.3.2, 8.0.5.1 and 8.1.3.1; CVSS 9.5 Critical; CWE-1188, "Initialization of a Resource with an Insecure Default"; disclosure 29 July 2026. Ethiack's timeline has the initial working proof of concept on 21 July, reported to Rails on 22 July, acknowledged on 23 July and fixed on 29 July — a seven-day turnaround. Rails also published forensic tooling. Ethiack notes a separate researcher, bl0rph, reached the same matload primitive independently on 18 July.

Not confirmed: CVE-2026-66066 is not in CISA's Known Exploited Vulnerabilities catalogue as of 23 October 2026 — we checked, and the three Rails entries there are from 2014, 2016 and 2019. A claim circulating that the patch leaves an RCE path open on servers comes from an article we could not retrieve, so we are not repeating it. Ethiack says only that "Some PoCs are already landing."

What to do

Upgrade Active Storage to 7.2.3.2, 8.0.5.1 or 8.1.3.1. Then rotate, because an arbitrary file read that reaches your master key has already taken it if it was used — the advisory is explicit that upgrading "does not undo an exfiltrated secret", and rotating what the read could have reached is part of the fix rather than a precaution.

If you cannot upgrade Rails immediately there are two ways to close the loader without touching your Rails version, both requiring libvips 8.13 or newer, and both with side effects on which image formats still work. That, and the formats that break, is its own subject.

If you are weighing whether to keep an image pipeline in-house at all after this, note that the attack surface here was three dependencies deep and none of it was Rails code. That calculus belongs in how you scope a web application build rather than in a post-incident review.

The transferable lesson has nothing to do with Ruby. A content type the client supplied was trusted to decide what a file was; the library then decided from the bytes. Any stack with an upload endpoint and an image pipeline has that seam, which is why a decoder bug in Node's bundled Brotli looks structurally identical three months later: untrusted bytes reaching a parser nobody audited, in a dependency nobody chose directly. The same fortnight also produced php2shell, where the parser in question was PHP itself.

Frequently asked questions

KindaRails2Shell is the nickname for CVE-2026-66066, a Critical flaw rated CVSS 9.5 in Rails Active Storage. A crafted file with a MATLAB 5.0 header and an HDF5 body reaches libvips and then libmatio, producing an arbitrary file read that can escalate to remote code execution.

The advisory lists Active Storage below 7.2.3.2, from 8.0.0 below 8.0.5.1, and from 8.1.0 below 8.1.3.1. Rails 6.x falls inside the advisory's literal range, though the researchers say 6.x is affected only with a customised Active Storage configuration.

No. Ethiack states that only the vips image processor is affected and applications using Magick are not affected by this vector. The exposure condition is config.active_storage.variant_processor set to vips, which load_defaults 7.0 established as the default.

No. The Rails advisory states explicitly that generating variants is not a separate requirement. The conditions are using libvips for Active Storage image processing and allowing image uploads from untrusted users, which includes direct uploads.

No. As of 23 October 2026 it is not listed in CISA's Known Exploited Vulnerabilities catalogue, whose only Rails entries date from 2014, 2016 and 2019. Ethiack reports that proof-of-concept code is appearing, but confirmed in-the-wild exploitation has not been established.

The read reaches config/master.key, credentials.yml.enc or environment variables holding secret_key_base. With that secret an attacker can forge an Active Storage variation key, and because the vips transformer did not enforce the supported image processing methods allowlist, a forged transform could reach instance_eval.

Written by

Akash Mohapatra

Akash Mohapatra

Co Founder & Director

23 Oct 2026

·

6 min read

Share

LET'S CONNECT

Connect with Creuto!

Ready to take the first step towards unlocking opportunities, realizing goals, and embracing innovation? We're here and eager to connect.