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