Active Storage libvips Image Processing Exploitation
An attacker is exploiting CVE-2026-66066 by uploading a MAT/HDF5 payload disguised as an image through Rails direct-upload and replaying a variation key to trigger an unauthenticated arbitrary file read or RCE via libvips.
Based on research by Rapid7 2026-09-20 12 steps · 5 queries T1190
Brief
Why now
We are tracking a critical vulnerability in Ruby on Rails' Active Storage framework, identified as CVE-2026-66066. As detailed in the KindaRails2Shell technical analysis by Rapid7 (https://www.rapid7.com/blog/post/ra-kindarails2shell-technical-analysis-cve-2026-66066/), attackers use crafted image payloads to trigger arbitrary file reads or remote code execution through the libvips library. This hunt allows practitioners to verify impact on vulnerable systems.
How the hunt flows
The first step scopes the hunt to devices where vulnerability scanners have flagged CVE-2026-66066 for remediation. This ensures the hunt focuses on known exposures while reducing noise from patched systems. The hunt then searches HTTP telemetry for unauthenticated POST requests to the direct_uploads endpoint and GET requests to the representations endpoint. An analyst or agent correlates these leads by source IP and host to identify the specific sequence required to trigger libvips processing. Following the web leads, the hunt pivots to endpoint telemetry to detect impact. It looks for the Rails process reading sensitive configuration files, such as the application master.key, credentials.yml.enc, or /etc/passwd. In parallel, the hunt stack-counts child processes spawned by Rails to identify rare shells or system utilities that indicate a transition from file read to remote code execution. The final phase connects the early-stage HTTP leads with the observed file disclosure or anomalous process execution. This creates a per-host verdict to confirm whether the libvips vulnerability was successfully exploited.
What the hunt cannot see
This hunt requires HTTP telemetry to identify the initial unauthenticated web requests. Without these logs, the hunt cannot distinguish malicious file access from legitimate application behavior with the same level of confidence. Furthermore, standard telemetry does not show internal library function calls like the libvips matload function. We must infer the use of this function from the presence of a MAT file and subsequent process behavior.
Steps
-
Scope to vulnerable hosts
Query · scopingIdentify host UIDs where the vulnerability scanner has flagged CVE-2026-66066 for remediation.
reads hb_vulnerability_findingsqlSELECT DISTINCT device_uid, resource_uid, severity, status FROM hb_vulnerability_finding WHERE cve_uid = 'CVE-2026-66066' AND status != 'suppressed'What a hit looks like. A list of vulnerable device UIDs. Silence means no known exposure is recorded in the vulnerability inventory.
-
Direct upload requests
Query · triageIdentify unauthenticated POST requests to the Rails direct-upload endpoint.
reads hb_http_activitysqlSELECT device_hostname, src_endpoint_ip, url_path, time FROM hb_http_activity WHERE (LOWER(url_path) LIKE '%/rails/active_storage/direct_uploads%') AND http_method = 'POST' AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. A POST request indicating the initial upload of a crafted payload.
-
Variation key replay requests
Query · triageIdentify requests to the representation endpoint which trigger libvips to process the uploaded payload.
reads hb_http_activitysqlSELECT device_hostname, src_endpoint_ip, url_path, url_query, time FROM hb_http_activity WHERE (LOWER(url_path) LIKE '%/rails/active_storage/representations%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. A request that triggers processing. Silence does not prove absence if the application uses proxying that masks these internal routes.
-
Triage early-stage leads
Agent triageCorrelate upload and representation requests from the same source IP on the same host.
-
Sensitive file access by Rails process
Query · detection candidateDetect if the Rails process accessed secrets or system files, indicating a successful file disclosure oracle.
reads hb_file_activitysqlSELECT device_hostname, process_name, file_path, time FROM hb_file_activity WHERE (LOWER(process_name) LIKE '%ruby%' OR LOWER(process_name) LIKE '%rails%') AND (instr(',' || '{{sensitive_files}}' || ',', ',' || LOWER(file_path) || ',') > 0 OR LOWER(file_path) LIKE '%/etc/passwd') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. A row showing the ruby process reading master.key or /etc/passwd. Silence proves absence of observed file access to these paths.
-
Rare child processes spawned by Rails
Query · baselineStack-count child processes of Rails across the fleet to find RCE payloads that spawn shells or external tools.
reads hb_process_activitysqlSELECT LOWER(process_name) AS child_process, COUNT(DISTINCT device_hostname) AS host_count, MIN(time) AS first_seen FROM hb_process_activity WHERE (LOWER(parent_process_name) LIKE '%ruby%' OR LOWER(parent_process_name) LIKE '%rails%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY child_process HAVING host_count <= 2What a hit looks like. Rare shell or system utility processes spawned by the Rails application. Benign child processes like 'sh -c exit' for health checks may appear but will usually be common.
-
Assess total chain impact
Agent triageConnect the early-stage HTTP leads with observed file disclosure or anomalous process execution.
-
Route on verdict
DecisionDirect response actions based on the agent's confidence in the exploitation chain.
-
Isolate host
Response actionContain the breach by isolating the compromised Rails server.
-
Analyst review
Analyst taskPerform manual review of the correlated evidence to confirm the scope of the file read or RCE.
-
Close out
Analyst taskVerify patching and document the negative result.
Coverage
Scenario coverage
| Stage | Covered | How, or why not |
|---|---|---|
| Direct-upload content-type spoofing T1190 |
Yes | direct-upload-spoofing, early-stage-agent |
| Variation key replay T1190 |
Yes | variation-key-replay, early-stage-agent |
| Unsafe libvips loader execution T1190 |
Not visible | Standard telemetry does not provide visibility into internal library function calls within the Rails process; impact is inferred from file and process activity. |
| Arbitrary file read via HDF5 T1190 |
Yes | sensitive-file-reads, follow-on-agent |
| Remote code execution via Kernel spawn T1190 |
Yes | rare-child-processes, follow-on-agent |
Blind spots
- Needs hb_http_activity. On hosts without HTTP logging, we cannot distinguish malicious file access from legitimate application behavior without relying solely on the rarity of the file access pattern. It would answer What were the unauthenticated web requests preceding the file access?.
- Needs Module load tracing or library call logging. Standard process telemetry only shows the 'ruby' binary; we must infer the use of matload from the presence of a MAT file in the direct-upload storage path. It would answer Did the Rails process specifically execute the libvips matload function?.
Parameters & data
Parameters
| Parameter | Type | Default | What it is |
|---|---|---|---|
lookback_days | number | 14 | Days of history to examine. |
scope_hosts | list[host] | — | Optional list of hostnames to focus the hunt; leave empty to scan the full estate. |
sensitive_files | list[path] | /etc/passwd, config/master.key, config/credentials.yml.enc, .env | Paths to sensitive files that a Rails application should not typically read after startup. |
Telemetry
| Source | Category | Telemetry |
|---|---|---|
| Endpoint telemetry (hb_ surfaces) | endpoint | endpoint |
| Web server / proxy logs | siem | network |
Source
---
analysis: A standard detection rule might alert on any /etc/passwd read, but this
hunt correlates unauthenticated web traffic with rare process behavior and file
access across three surfaces, allowing it to identify the specific Rails exploitation
chain while filtering out false positives.
blind_spots:
- id: no-http-telemetry
question: What were the unauthenticated web requests preceding the file access?
requires: hb_http_activity
risk: On hosts without HTTP logging, we cannot distinguish malicious file access
from legitimate application behavior without relying solely on the rarity of the
file access pattern.
stage: direct-upload-type-spoofing
- id: internal-libvips-calls
question: Did the Rails process specifically execute the libvips matload function?
requires: Module load tracing or library call logging
risk: Standard process telemetry only shows the 'ruby' binary; we must infer the
use of matload from the presence of a MAT file in the direct-upload storage path.
stage: libvips-matload-execution
coverage:
- stage: direct-upload-type-spoofing
status: covered
steps:
- direct-upload-spoofing
- early-stage-agent
- stage: variation-key-replay
status: covered
steps:
- variation-key-replay
- early-stage-agent
- blind_spot: internal-libvips-calls
reason: Standard telemetry does not provide visibility into internal library function
calls within the Rails process; impact is inferred from file and process activity.
stage: libvips-matload-execution
status: not_visible
- stage: arbitrary-file-disclosure
status: covered
steps:
- sensitive-file-reads
- follow-on-agent
- stage: rce-payload-execution
status: covered
steps:
- rare-child-processes
- follow-on-agent
guardrails:
claims: no_unsupported
evidence: citation_required
missing_data: not_benign
telemetry: untrusted
hunt:
applicability: campaign-specific
handoff: promote-to-detection
justification: CVE-2026-66066 allows unauthenticated attackers to read arbitrary
files, which can disclose secret keys and lead to full remote code execution.
Verifying that production servers are not currently being exploited is a critical
security requirement.
methodology: model-assisted
trigger: intel-report
hypothesis: An attacker is exploiting CVE-2026-66066 by uploading a MAT/HDF5 payload
disguised as an image through Rails direct-upload and replaying a variation key
to trigger an unauthenticated arbitrary file read or RCE via libvips.
labels:
- hunt
- attack.t1190
name: Active Storage libvips Image Processing Exploitation
parameters:
lookback_days:
default: '14'
description: Days of history to examine.
type: number
scope_hosts:
default: []
description: Optional list of hostnames to focus the hunt; leave empty to scan
the full estate.
type: list[host]
sensitive_files:
default:
- /etc/passwd
- config/master.key
- config/credentials.yml.enc
- .env
description: Paths to sensitive files that a Rails application should not typically
read after startup.
type: list[path]
provenance:
authors:
- name: Huntbase hunt generation
org: huntbase.io
generated:
by: huntbase-hunt-generation
from: https://www.rapid7.com/blog/post/ra-kindarails2shell-technical-analysis-cve-2026-66066/
gates:
- dry-run
- lint
- critic
model: hb_google/gemini-3-flash-preview
rationale: Focus on servers identified with CVE-2026-66066. If scanning is incomplete,
widen the scope to all hosts where the Rails process ('ruby') is observed.
references:
- name: "Rapid7 \u2014 KindaRails2Shell technical analysis (CVE-2026-66066)"
url: https://www.rapid7.com/blog/post/ra-kindarails2shell-technical-analysis-cve-2026-66066/
related:
- hunt: rails-token-forgery-investigation
reason: If master.key disclosure is confirmed, a separate hunt for session token
forgery and administrative takeover is required.
relation: follows
scenario:
stages:
- name: Direct-upload content-type spoofing
observables:
- POST /rails/active_storage/direct_uploads
- 'content_type: image/png'
- 'params.expect(blob: [:filename, :byte_size, :checksum, :content_type, metadata:
{}])'
- ActiveStorage::DirectUploadsController#create
slug: direct-upload-type-spoofing
tactic: initial-access
techniques:
- T1190
- name: Variation key replay
observables:
- GET /rails/active_storage/representations/proxy/
- params[:variation_key]
- params[:signed_blob_id]
- ActiveStorage::Variation.wrap
slug: variation-key-replay
tactic: defense-evasion
techniques:
- T1190
- name: Unsafe libvips loader execution
observables:
- MATLAB 5.0
- libvips
- matload
- VipsForeignLoadMatClass
- VIPS_OPERATION_UNTRUSTED
- '0x0200'
- MAT_FT_MAT73
slug: libvips-matload-execution
tactic: execution
techniques:
- T1190
- name: Arbitrary file read via HDF5
observables:
- /etc/passwd
- config/master.key
- HDF5 external storage
- libmatio
slug: arbitrary-file-disclosure
tactic: collection
techniques:
- T1190
- name: Remote code execution via Kernel spawn
observables:
- Kernel#spawn
- Kernel#eval
- ImageProcessing
- config.active_support.message_serializer = :json
slug: rce-payload-execution
tactic: execution
techniques:
- T1190
summary: Unauthenticated attackers can exploit CVE-2026-66066 to read arbitrary
files or achieve remote code execution in Ruby on Rails applications using libvips
for Active Storage. The attack involves uploading a crafted MAT/HDF5 file through
direct-upload with a spoofed content type and replaying a valid variation key
to trigger unsafe libvips loaders.
severity: high
targets:
analyst:
name: Tier-2 analyst
role: analyst
endpoint:
category: endpoint
name: Endpoint telemetry (hb_ surfaces)
telemetry:
- endpoint
hunter:
agent: true
name: Hunt agent
web:
category: siem
name: Web server / proxy logs
telemetry:
- network
tlp: clear
type: investigation
---
# Active Storage libvips Image Processing Exploitation
This hunt identifies the multi-stage exploitation of Ruby on Rails' Active Storage framework. It begins by identifying vulnerable hosts and correlating unauthenticated HTTP requests for direct-upload and image representation. The hunt then pivots to the endpoint to detect the impact: the Rails process reading sensitive configuration material or spawning rare child processes, which indicates a successful transition from file disclosure to remote code execution.
## vulnerable-scoping
<!-- Scope to vulnerable hosts -->
Identify host UIDs where the vulnerability scanner has flagged CVE-2026-66066 for remediation.
```sqlite target=endpoint role=scoping
~~~yaml
expected: A list of vulnerable device UIDs. Silence means no known exposure is recorded
in the vulnerability inventory.
reads:
- device_uid
- resource_uid
- severity
- status
silence: not_evidence_of_absence
source: hb_vulnerability_finding
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT DISTINCT device_uid, resource_uid, severity, status FROM hb_vulnerability_finding WHERE cve_uid = 'CVE-2026-66066' AND status != 'suppressed'
```
## early-stage-leads
<!-- Hunt for early-stage exploitation leads -->
parallel:
- → direct-upload-spoofing
- → variation-key-replay
join: → early-stage-agent
## direct-upload-spoofing
<!-- Direct upload requests -->
Identify unauthenticated POST requests to the Rails direct-upload endpoint.
```sqlite target=web role=triage params=(lookback_days=lookback_days, scope_hosts=scope_hosts)
~~~yaml
expected: A POST request indicating the initial upload of a crafted payload.
reads:
- device_hostname
- src_endpoint_ip
- url_path
- time
silence: not_evidence_of_absence
source: hb_http_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT device_hostname, src_endpoint_ip, url_path, time FROM hb_http_activity WHERE (LOWER(url_path) LIKE '%/rails/active_storage/direct_uploads%') AND http_method = 'POST' AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```
## variation-key-replay
<!-- Variation key replay requests -->
Identify requests to the representation endpoint which trigger libvips to process the uploaded payload.
```sqlite target=web role=triage params=(lookback_days=lookback_days, scope_hosts=scope_hosts)
~~~yaml
expected: A request that triggers processing. Silence does not prove absence if the
application uses proxying that masks these internal routes.
reads:
- device_hostname
- src_endpoint_ip
- url_path
- url_query
- time
silence: not_evidence_of_absence
source: hb_http_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT device_hostname, src_endpoint_ip, url_path, url_query, time FROM hb_http_activity WHERE (LOWER(url_path) LIKE '%/rails/active_storage/representations%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```
## early-stage-agent
<!-- Triage early-stage leads -->
```agent target=hunter
cite: required
context:
- direct-upload-spoofing
- variation-key-replay
max_iterations: 3
objective: Determine if any source IP performed both a direct-upload and a representation
request on a vulnerable host within a 1-hour window.
success_criteria: Identification of source IPs and target hostnames for follow-on
hunting.
tools:
- endpoint
- web
```
## follow-on-impact
<!-- Hunt for follow-on impact -->
parallel:
- → sensitive-file-reads
- → rare-child-processes
join: → follow-on-agent
## sensitive-file-reads
<!-- Sensitive file access by Rails process -->
Detect if the Rails process accessed secrets or system files, indicating a successful file disclosure oracle.
```sqlite target=endpoint role=detection-candidate params=(lookback_days=lookback_days, sensitive_files=sensitive_files, scope_hosts=scope_hosts)
~~~yaml
expected: A row showing the ruby process reading master.key or /etc/passwd. Silence
proves absence of observed file access to these paths.
reads:
- device_hostname
- process_name
- file_path
- time
silence: evidence_of_absence
source: hb_file_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT device_hostname, process_name, file_path, time FROM hb_file_activity WHERE (LOWER(process_name) LIKE '%ruby%' OR LOWER(process_name) LIKE '%rails%') AND (instr(',' || '{{sensitive_files}}' || ',', ',' || LOWER(file_path) || ',') > 0 OR LOWER(file_path) LIKE '%/etc/passwd') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```
## rare-child-processes
<!-- Rare child processes spawned by Rails -->
Stack-count child processes of Rails across the fleet to find RCE payloads that spawn shells or external tools.
```sqlite target=endpoint role=baseline params=(lookback_days=lookback_days, scope_hosts=scope_hosts)
~~~yaml
baseline:
compare: first_seen
window: '{{lookback_days}}d'
expected: Rare shell or system utility processes spawned by the Rails application.
Benign child processes like 'sh -c exit' for health checks may appear but will usually
be common.
prevalence:
by: device_hostname
key:
- child_process
rare_below: 3
reads:
- device_hostname
- process_name
- parent_process_name
- time
silence: not_evidence_of_absence
source: hb_process_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT LOWER(process_name) AS child_process, COUNT(DISTINCT device_hostname) AS host_count, MIN(time) AS first_seen FROM hb_process_activity WHERE (LOWER(parent_process_name) LIKE '%ruby%' OR LOWER(parent_process_name) LIKE '%rails%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY child_process HAVING host_count <= 2
```
## follow-on-agent
<!-- Assess total chain impact -->
```agent target=hunter
cite: required
context:
- early-stage-agent
- sensitive-file-reads
- rare-child-processes
max_iterations: 5
objective: Evaluate whether the unauthenticated web requests observed in the early
stage resulted in the Rails process reading sensitive material or spawning rare
shells, constituting a successful breach.
success_criteria: A per-host verdict of malicious (confirmed chain), suspicious (partial
chain), or benign.
tools:
- endpoint
- web
```
## route-on-verdict
<!-- Route on verdict -->
if~: "the follow-on-agent verdict is malicious for at least one host" (confidence: high, judge=hunter)
then: → isolate-host
indeterminate: → analyst-review
unavailable: → analyst-review (blind_spot: no-http-telemetry)
else: → close-out
## isolate-host
<!-- Isolate host -->
```action target=endpoint
~~~yaml
approval: required
~~~
Isolate the host immediately. Revoke the Rails master.key and rotate all credentials stored in the application environment.
```
→ analyst-review
## analyst-review
<!-- Analyst review -->
```manual target=analyst
Analyze the HTTP headers if available to confirm the image/png spoofing. Verify if any files were downloaded by the attacker and check for persistence in the rare child processes.
```
→ end
## close-out
<!-- Close out -->
```manual target=analyst
Confirm that all hosts identified as vulnerable in the scoping step have been patched to at least Rails 7.2.3.2, 8.0.5.1, or 8.1.3.1.
```
→ end
Run it
Take this hunt into your environment.
Open it in Huntbase to run every step against your own connections, with Scout weighing the evidence and your analysts in command. Or take the open hunt.md file anywhere that reads the format.
Machine-drafted by huntbase-hunt-generation using hb_google/gemini-3-flash-preview, gated by dry-run, lint, critic, then reviewed by a person.