// 2 CRITICAL · 2 ZERO-DAY · 8 CVE · 6 EXPLOIT IN THE LAST 24H
A critical Ruby on Rails vulnerability enables arbitrary file read and unauthenticated RCE through Active Storage when using libvips. A Metasploit module is available.

Ruby on Rails published advisory GHSA-xr9x-r78c-5hrm on July 29, 2026, documenting CVE-2026-66066: a critical vulnerability in Active Storage that turns routine image processing into unauthenticated remote code execution. The attack chain, dubbed KindaRails2Shell by Rapid7 researchers, is fully reproduced and automated in an official Metasploit module. The combination of an insecure default — libvips as the image processor since Rails 7.0 — and a collision between two parsers reading the same file in incompatible ways makes the attack surface exceptionally broad.

Key Takeaways
  • CVE-2026-66066 enables unauthenticated arbitrary file read and full RCE in Rails applications using Active Storage with libvips configured as the image processor.
  • The chain exploits a dual trust failure: Rails trusts the content_type in the database, libvips trusts its own sniffer on the first 10 bytes, while libmatio reads bytes 124-125 and opens an external HDF5 file.
  • Rapid7 validated the exploit against Rails 6.0.6.1, 6.1.7.10, 7.2.3.1, 8.0.5, and 8.1.3; patched versions are 7.2.3.2, 8.0.5.1, and 8.1.3.1.
  • The Metasploit module exploit/multi/http/rails_activestorage_vips_rce implements the complete file-read-to-RCE chain, with automatic retrieval of SECRET_KEY_BASE from /proc/self/environ.

How the Boundary Breaks: From content_type to Arbitrary File

The attack starts at the Active Storage direct upload endpoint. The controller ActiveStorage::DirectUploadsController#create accepts a client-supplied content_type parameter without verifying the actual file bytes. The blob is created in the database with an arbitrary MIME type, and this decision — not the real data — governs everything that follows.

When a second endpoint requests an image variant, Rails verifies signed_blob_id and variation_key separately, without binding the second value to the first. An attacker can therefore replay a legitimate variation_key against a different blob with a spoofed content_type. This is where the first trust boundary breaks: Rails decides a file is an image based on a database row, not its contents.

The second break occurs in the processor. Libvips selects the matload loader when it detects the MATLAB 5.0 prefix in the first ten bytes. But the check is superficial: vips__mat_ismat uses only vips_isprefix('MATLAB 5.0', buf) and fails to detect that the file is actually a MAT 7.3, an HDF5-based format. Libmatio, called by libvips, instead reads bytes 124-125 where it finds MAT_FT_MAT73 = 0x0200 and opens the file as HDF5. This dissonance — two parsers looking at different parts of the same header and drawing opposite conclusions — is the technical heart of the vulnerability.

"The published chain contains two separate trust failures. Rails decides that a blob is an image from a database value, while libvips decides what parser to use from the bytes on disk." — Rapid7

From Pixels to Shell: The RCE Chain in Three Steps

The HDF5 file opened this way can contain an External File List: a reference to another file on the filesystem whose contents are returned as if they were pixels. This allows arbitrary read of any file readable by the Rails process user. Significantly, as Rapid7 notes, this phase "does not require secret_key_base or forged tokens": it is a pure, unauthenticated file read reachable with a direct request.

The third step escalates the file read to full RCE. The Metasploit module targets /proc/self/environ directly to extract the SECRET_KEY_BASE variable, with a read limit of 65,536 bytes. With this key, the attacker forges signed messages using Active Support's JSON serializer — Rails' default — and constructs a malicious variation containing arrays and hashes that, through the ImageProcessing method chain, reach Kernel#spawn or Kernel#eval.

Rapid7 documented this path with the :json serializer, demonstrating that simple types — Hash, Array, String — are sufficient for escalation, with no need for YAML or Marshal. The Metasploit module, developed by jburgess-r7 and merged into the official framework, implements "the concrete representation-based chain documented by the Rails forensic repository." Validation logs show key retrieval followed by a shell with uid rails.

The Default That Amplifies Everything: Why Vips Changes the Game

Rails 7.0 made libvips the default image processor for Active Storage, replacing ImageMagick/MiniMagick. This choice, technically motivated by performance and memory, created a massive attack surface: every Rails 7+ application with image uploads is potentially exposed without developers having actively configured anything. Rails 6.x is affected only if Vips was explicitly enabled, a less common but not rare configuration in optimized deployments.

The scale of the problem is underscored by the CVSSv4 score of 9.5 assigned by Rapid7, with CWE-1188 classification: Initialization of Resource with Insecure Default. The default choice — libvips without restrictions on untrusted operations — is what transforms a parsing vulnerability into a systemic security crisis.

The Rails fix, released in versions 7.2.3.2, 8.0.5.1, and 8.1.3.1, disables untrusted operations in Active Storage initialization. But there is a tight constraint: it requires libvips >= 8.13. Systems with older libvips versions receive no protection from the Rails update alone; the boot fails if the requirement is not met. This creates a critical dependency between two components with independent release cycles.

Immediate Actions

  • Patch Active Storage immediately to versions 7.2.3.2, 8.0.5.1, or 8.1.3.1, verifying compatibility with libvips >= 8.13 on the target system before deployment.
  • Apply the temporary workaround by setting the environment variable VIPS_BLOCK_UNTRUSTED or calling Vips.block_untrusted(true) with ruby-vips >= 2.2.1, if patching is not immediately feasible.
  • Rotate all Rails secrets, especially SECRET_KEY_BASE: the file-read phase may have exposed these values even without confirmed RCE, and compromised message signing allows persistent actions.
  • Verify processor configuration: if libvips is active in production, priority is maximum; with ImageMagick/MiniMagick this specific vector does not apply, but verification must be explicit.

The Incomplete Precedent and Regression Risk

The fix pull request reveals that CVE-2025-24293 — a previous vulnerability in the same area — was only partially resolved. The patch protected only the ImageMagick transformer, not the Vips one. This leaves a clear fingerprint: Active Storage's complexity, with its two parallel processors and their different parsing surfaces, has made security controls fragmented. The CWE-1188 class, insecure default, is literal here: the system boots with a configuration that does not distinguish between trusted and untrusted operations in the image processor.

The Metasploit module, with its two exfiltration layouts — 256x256 raw (32,512 bytes per request) for Rails 7/8 and 100x100 sharpened (4,900 bytes) for Rails 6.x — demonstrates that the chain is robust and adapted to multiple versions. The presence of this public tool, live-tested on five Rails versions, drastically lowers the barrier to replicable attacks.

When Two Honest Parsers Build a Weapon

The KindaRails2Shell case is not a buffer overflow or a flawed authorization logic. It is something more subtle and more concerning: two components that, taken individually, behave as documented, but whose composition creates an attack channel. Rails trusts the database, libvips trusts its own sniffer, libmatio trusts its own HDF5 parser. None of these is a bug in the strict sense. Their overlap is.

This pattern — the collision of parsers with different trust models — is not new, but its expression in such a widespread image upload pipeline makes it particularly dangerous. The fact that the chain passed through a previous fix without being caught suggests that security controls on composite components require explicit verification of boundary states, not just individual behavior.

The disclosure, moved up to July 29 from the original August 28 date, accelerated the availability of attack tools. Security teams must now operate on the assumption that the proof-of-concept is in active circulation, even though Rapid7 had no evidence of exploitation in the wild as of July 30. The time between advisory and Metasploit module was a matter of days: the reaction window is narrow, and the dependency on libvips >= 8.13 adds a non-trivial operational hurdle.

Sources

Information verified against cited sources and current as of publication.

Sources


Sources and references
  1. rapid7.com
  2. github.com