criticalCVE-2026-66066Jul 30, 2026

CVE-2026-66066: Active Storage Arbitrary File Read via libvips Unfuzzed Loaders

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A default Rails app that accepts image uploads and uses libvips for variant processing lets an unauthenticated attacker read any file the app process can access, including secrets that can escalate…

Packageactivestorage
Ecosystemrubygems
Affected< 7.2.3.2
Fixed in7.2.3.2
CVE-2026-66066: Active Storage Arbitrary File Read via libvips Unfuzzed Loaders

The problem

Active Storage (Rails 7.0+ default) passes uploaded files directly to libvips for variant generation without disabling libvips operations flagged as 'unfuzzed', meaning they were never hardened against hostile input.

An attacker uploads a crafted file that targets one of these unfuzzed loaders. When Active Storage triggers variant processing, libvips reads the file through the dangerous loader and can be made to embed the contents of arbitrary server-side files into the output image.

The process environment, which typically contains SECRET_KEY_BASE and cloud credentials, is readable this way. Leaking SECRET_KEY_BASE enables an attacker to forge signed cookies and Active Storage URLs, which can chain into remote code execution via Marshal deserialization.

Proof of concept

A working proof-of-concept for CVE-2026-66066 in activestorage, with the exact payload below.

bash
# Step 1: craft a MATLAB 5.0 / HDF5 file that libvips routes through matload
# matload is an unfuzzed libvips operation present on stock Debian libvips builds
# The crafted .mat header causes libvips to read an external dataset whose
# path points to /proc/self/environ (or any readable file).
#
# Upload the file as an ordinary image attachment:
curl -s -F "upload[file]=@crafted.mat;type=image/jpeg" \
     http://TARGET/uploads

# Step 2: request a variant (thumbnail) of the uploaded blob.
# Active Storage hands the stored file to Vips::Image.new_from_file,
# which sniffs the MATLAB 5.0 magic bytes and dispatches matload.
# The returned PNG output contains the embedded external dataset bytes.
GET /rails/active_storage/representations/redirect/<signed_id>/<filename>?variant[resize_to_limit][]=100&variant[resize_to_limit][]=100

# The response body is a PNG whose pixel data encodes the contents of the
# file referenced in the .mat payload (e.g., /proc/self/environ).
# SECRET_KEY_BASE is then read from that output to sign a Marshal payload.

libvips routes input files through a loader chosen by sniffing magic bytes, not by the declared content-type. The MATLAB 5.0 (.mat) format is handled by matload, which libvips marks 'unfuzzed' because it was never fuzz-tested for hostile input and supports external dataset references that resolve to arbitrary filesystem paths.

Active Storage called Vips::Image.new_from_file without any restriction on which loaders could be invoked, so a file with a valid MATLAB 5.0 header header bypassed all content-type checks and reached matload.

The patch (commit 1c01bb5 / 349e7a5 / d79b7f4) adds a single call, Vips.block_untrusted(true), during Active Storage boot. This tells libvips to refuse any load or save operation tagged VIPS_OPERATION_UNTRUSTED, which covers matload and other dangerous handlers.

A minimum of libvips 8.13 and ruby-vips 2.2.1 is required because earlier versions lack the blocking API entirely. The CWE is CWE-1188: the resource (libvips) was initialized with an insecure default that left unsafe operations enabled.

The fix

Upgrade activestorage to 7.2.3.2, 8.0.5.1, or 8.1.3.1. Also upgrade libvips to >= 8.13 and ruby-vips to >= 2.2.1 on the host, because the patch's blocking call requires those minimum versions and Active Storage will refuse to boot without them. If an immediate Rails upgrade is not possible and libvips >= 8.13 is installed, set the VIPS_BLOCK_UNTRUSTED environment variable or call Vips.block_untrusted(true) from an initializer (requires ruby-vips >= 2.2.1).

After patching, rotate every secret the application process can read: SECRET_KEY_BASE, RAILS_MASTER_KEY, config/credentials.yml.enc contents, database credentials, S3/GCS/Azure keys, and all third-party API tokens.

Reported by André Baptista (0xacb), Bruno Mendes (s3np41k1r1t0), Rafael Castilho (castilho) — Ethiack; RyotaK — GMO Flatt Security.

References: [1][2][3][4][5][6]

Related research