← All hunts high TLP:CLEAR

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

  1. Scope to vulnerable hosts

    Query · scoping

    Identify host UIDs where the vulnerability scanner has flagged CVE-2026-66066 for remediation.

    reads hb_vulnerability_findingsql
    SELECT 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.

  2. Direct upload requests

    Query · triage

    Identify unauthenticated POST requests to the Rails direct-upload endpoint.

    reads hb_http_activitysql
    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')

    What a hit looks like. A POST request indicating the initial upload of a crafted payload.

  3. Variation key replay requests

    Query · triage

    Identify requests to the representation endpoint which trigger libvips to process the uploaded payload.

    reads hb_http_activitysql
    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')

    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.

  4. Triage early-stage leads

    Agent triage

    Correlate upload and representation requests from the same source IP on the same host.

  5. Sensitive file access by Rails process

    Query · detection candidate

    Detect if the Rails process accessed secrets or system files, indicating a successful file disclosure oracle.

    reads hb_file_activitysql
    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')

    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.

  6. Rare child processes spawned by Rails

    Query · baseline

    Stack-count child processes of Rails across the fleet to find RCE payloads that spawn shells or external tools.

    reads hb_process_activitysql
    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

    What 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.

  7. Assess total chain impact

    Agent triage

    Connect the early-stage HTTP leads with observed file disclosure or anomalous process execution.

  8. Route on verdict

    Decision

    Direct response actions based on the agent's confidence in the exploitation chain.

  9. Isolate host

    Response action

    Contain the breach by isolating the compromised Rails server.

  10. Analyst review

    Analyst task

    Perform manual review of the correlated evidence to confirm the scope of the file read or RCE.

  11. Close out

    Analyst task

    Verify patching and document the negative result.

Coverage

Scenario coverage

StageCoveredHow, 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

ParameterTypeDefaultWhat it is
lookback_daysnumber14Days of history to examine.
scope_hostslist[host]Optional list of hostnames to focus the hunt; leave empty to scan the full estate.
sensitive_fileslist[path]/etc/passwd, config/master.key, config/credentials.yml.enc, .envPaths to sensitive files that a Rails application should not typically read after startup.

Telemetry

SourceCategoryTelemetry
Endpoint telemetry (hb_ surfaces)endpointendpoint
Web server / proxy logssiemnetwork

Source

Download hunt.md Definition (JSON) An open hunt.md file; it runs anywhere that reads the format.
---
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.