{
  "hunt": {
    "meta": {
      "tlp": "clear",
      "hunt": {
        "handoff": "promote-to-detection",
        "trigger": "intel-report",
        "methodology": "model-assisted",
        "applicability": "campaign-specific",
        "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."
      },
      "name": "Active Storage libvips Image Processing Exploitation",
      "type": "investigation",
      "labels": [
        "hunt",
        "attack.t1190"
      ],
      "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"
        }
      ],
      "targets": {
        "web": {
          "name": "Web server / proxy logs",
          "category": "siem",
          "telemetry": [
            "network"
          ]
        },
        "hunter": {
          "name": "Hunt agent",
          "agent": true
        },
        "analyst": {
          "name": "Tier-2 analyst",
          "role": "analyst"
        },
        "endpoint": {
          "name": "Endpoint telemetry (hb_ surfaces)",
          "category": "endpoint",
          "telemetry": [
            "endpoint"
          ]
        }
      },
      "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.",
      "coverage": [
        {
          "stage": "direct-upload-type-spoofing",
          "steps": [
            "direct-upload-spoofing",
            "early-stage-agent"
          ],
          "status": "covered"
        },
        {
          "stage": "variation-key-replay",
          "steps": [
            "variation-key-replay",
            "early-stage-agent"
          ],
          "status": "covered"
        },
        {
          "stage": "libvips-matload-execution",
          "reason": "Standard telemetry does not provide visibility into internal library function calls within the Rails process; impact is inferred from file and process activity.",
          "status": "not_visible",
          "blind_spot": "internal-libvips-calls"
        },
        {
          "stage": "arbitrary-file-disclosure",
          "steps": [
            "sensitive-file-reads",
            "follow-on-agent"
          ],
          "status": "covered"
        },
        {
          "stage": "rce-payload-execution",
          "steps": [
            "rare-child-processes",
            "follow-on-agent"
          ],
          "status": "covered"
        }
      ],
      "scenario": {
        "stages": [
          {
            "name": "Direct-upload content-type spoofing",
            "slug": "direct-upload-type-spoofing",
            "tactic": "initial-access",
            "techniques": [
              "T1190"
            ],
            "observables": [
              "POST /rails/active_storage/direct_uploads",
              "content_type: image/png",
              "params.expect(blob: [:filename, :byte_size, :checksum, :content_type, metadata: {}])",
              "ActiveStorage::DirectUploadsController#create"
            ]
          },
          {
            "name": "Variation key replay",
            "slug": "variation-key-replay",
            "tactic": "defense-evasion",
            "techniques": [
              "T1190"
            ],
            "observables": [
              "GET /rails/active_storage/representations/proxy/",
              "params[:variation_key]",
              "params[:signed_blob_id]",
              "ActiveStorage::Variation.wrap"
            ]
          },
          {
            "name": "Unsafe libvips loader execution",
            "slug": "libvips-matload-execution",
            "tactic": "execution",
            "techniques": [
              "T1190"
            ],
            "observables": [
              "MATLAB 5.0",
              "libvips",
              "matload",
              "VipsForeignLoadMatClass",
              "VIPS_OPERATION_UNTRUSTED",
              "0x0200",
              "MAT_FT_MAT73"
            ]
          },
          {
            "name": "Arbitrary file read via HDF5",
            "slug": "arbitrary-file-disclosure",
            "tactic": "collection",
            "techniques": [
              "T1190"
            ],
            "observables": [
              "/etc/passwd",
              "config/master.key",
              "HDF5 external storage",
              "libmatio"
            ]
          },
          {
            "name": "Remote code execution via Kernel spawn",
            "slug": "rce-payload-execution",
            "tactic": "execution",
            "techniques": [
              "T1190"
            ],
            "observables": [
              "Kernel#spawn",
              "Kernel#eval",
              "ImageProcessing",
              "config.active_support.message_serializer = :json"
            ]
          }
        ],
        "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",
      "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.",
      "guardrails": {
        "claims": "no_unsupported",
        "evidence": "citation_required",
        "telemetry": "untrusted",
        "missing_data": "not_benign"
      },
      "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.",
      "parameters": {
        "scope_hosts": {
          "type": "list[host]",
          "default": [],
          "description": "Optional list of hostnames to focus the hunt; leave empty to scan the full estate."
        },
        "lookback_days": {
          "type": "number",
          "default": "14",
          "description": "Days of history to examine."
        },
        "sensitive_files": {
          "type": "list[path]",
          "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."
        }
      },
      "provenance": {
        "authors": [
          {
            "org": "huntbase.io",
            "name": "Huntbase hunt generation"
          }
        ],
        "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"
        }
      },
      "references": [
        {
          "url": "https://www.rapid7.com/blog/post/ra-kindarails2shell-technical-analysis-cve-2026-66066/",
          "name": "Rapid7 \u2014 KindaRails2Shell technical analysis (CVE-2026-66066)"
        }
      ],
      "blind_spots": [
        {
          "id": "no-http-telemetry",
          "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",
          "question": "What were the unauthenticated web requests preceding the file access?",
          "requires": "hb_http_activity"
        },
        {
          "id": "internal-libvips-calls",
          "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",
          "question": "Did the Rails process specifically execute the libvips matload function?",
          "requires": "Module load tracing or library call logging"
        }
      ]
    },
    "name": "Active Storage libvips Image Processing Exploitation",
    "description": "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."
  },
  "nodes": [
    {
      "id": "hypothesis",
      "type": "hypothesis",
      "label": "Hypothesis",
      "config": {
        "tags": [],
        "coverage": [
          {
            "stage": "direct-upload-type-spoofing",
            "steps": [
              "direct-upload-spoofing",
              "early-stage-agent"
            ],
            "status": "covered"
          },
          {
            "stage": "variation-key-replay",
            "steps": [
              "variation-key-replay",
              "early-stage-agent"
            ],
            "status": "covered"
          },
          {
            "stage": "libvips-matload-execution",
            "reason": "Standard telemetry does not provide visibility into internal library function calls within the Rails process; impact is inferred from file and process activity.",
            "status": "not_visible",
            "blind_spot": "internal-libvips-calls"
          },
          {
            "stage": "arbitrary-file-disclosure",
            "steps": [
              "sensitive-file-reads",
              "follow-on-agent"
            ],
            "status": "covered"
          },
          {
            "stage": "rce-payload-execution",
            "steps": [
              "rare-child-processes",
              "follow-on-agent"
            ],
            "status": "covered"
          }
        ],
        "rationale": "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.",
        "blind_spots": [
          {
            "id": "no-http-telemetry",
            "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",
            "question": "What were the unauthenticated web requests preceding the file access?",
            "requires": "hb_http_activity"
          },
          {
            "id": "internal-libvips-calls",
            "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",
            "question": "Did the Rails process specifically execute the libvips matload function?",
            "requires": "Module load tracing or library call logging"
          }
        ],
        "scoping_notes": "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.",
        "beyond_detection": "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."
      }
    },
    {
      "id": "vulnerable-scoping",
      "type": "query",
      "label": "Scope to vulnerable hosts",
      "config": {
        "dsl": "sqlite",
        "role": "scoping",
        "source": "endpoint",
        "content": "SELECT DISTINCT device_uid, resource_uid, severity, status FROM hb_vulnerability_finding WHERE cve_uid = 'CVE-2026-66066' AND status != 'suppressed'",
        "surface": "hb_vulnerability_finding",
        "description": "Identify host UIDs where the vulnerability scanner has flagged CVE-2026-66066 for remediation.",
        "expected_signal": "A list of vulnerable device UIDs. Silence means no known exposure is recorded in the vulnerability inventory."
      },
      "parents": [
        {
          "id": "hypothesis"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "scoping",
        "label": "Scope to vulnerable hosts",
        "reads": [
          "device_uid",
          "resource_uid",
          "severity",
          "status"
        ],
        "source": "hb_vulnerability_finding",
        "target": "endpoint",
        "content": "SELECT DISTINCT device_uid, resource_uid, severity, status FROM hb_vulnerability_finding WHERE cve_uid = 'CVE-2026-66066' AND status != 'suppressed'",
        "silence": "not_evidence_of_absence",
        "expected": "A list of vulnerable device UIDs. Silence means no known exposure is recorded in the vulnerability inventory.",
        "verified": "dry-run",
        "verified_at": "2026-09-20"
      }
    },
    {
      "id": "direct-upload-spoofing",
      "type": "query",
      "label": "Direct upload requests",
      "config": {
        "dsl": "sqlite",
        "role": "triage",
        "source": "web",
        "content": "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')",
        "surface": "hb_http_activity",
        "description": "Identify unauthenticated POST requests to the Rails direct-upload endpoint.",
        "expected_signal": "A POST request indicating the initial upload of a crafted payload."
      },
      "parents": [
        {
          "id": "vulnerable-scoping"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "triage",
        "label": "Direct upload requests",
        "reads": [
          "device_hostname",
          "src_endpoint_ip",
          "url_path",
          "time"
        ],
        "source": "hb_http_activity",
        "target": "web",
        "content": "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')",
        "silence": "not_evidence_of_absence",
        "expected": "A POST request indicating the initial upload of a crafted payload.",
        "verified": "dry-run",
        "verified_at": "2026-09-20"
      }
    },
    {
      "id": "variation-key-replay",
      "type": "query",
      "label": "Variation key replay requests",
      "config": {
        "dsl": "sqlite",
        "role": "triage",
        "source": "web",
        "content": "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')",
        "surface": "hb_http_activity",
        "description": "Identify requests to the representation endpoint which trigger libvips to process the uploaded payload.",
        "expected_signal": "A request that triggers processing. Silence does not prove absence if the application uses proxying that masks these internal routes."
      },
      "parents": [
        {
          "id": "vulnerable-scoping"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "triage",
        "label": "Variation key replay requests",
        "reads": [
          "device_hostname",
          "src_endpoint_ip",
          "url_path",
          "url_query",
          "time"
        ],
        "source": "hb_http_activity",
        "target": "web",
        "content": "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')",
        "silence": "not_evidence_of_absence",
        "expected": "A request that triggers processing. Silence does not prove absence if the application uses proxying that masks these internal routes.",
        "verified": "dry-run",
        "verified_at": "2026-09-20"
      }
    },
    {
      "id": "early-stage-agent",
      "type": "analytic",
      "label": "Triage early-stage leads",
      "config": {
        "cite": "required",
        "tools": [
          "endpoint",
          "web"
        ],
        "context": [
          "direct-upload-spoofing",
          "variation-key-replay"
        ],
        "objective": "Determine if any source IP performed both a direct-upload and a representation request on a vulnerable host within a 1-hour window.",
        "description": "Correlate upload and representation requests from the same source IP on the same host.",
        "max_iterations": 3,
        "expected_signal": "A list of source IPs suspected of triggering the Rails exploitation chain.",
        "success_criteria": "Identification of source IPs and target hostnames for follow-on hunting."
      },
      "parents": [
        {
          "id": "direct-upload-spoofing",
          "kind": "merge"
        },
        {
          "id": "variation-key-replay",
          "kind": "merge"
        }
      ]
    },
    {
      "id": "sensitive-file-reads",
      "type": "query",
      "label": "Sensitive file access by Rails process",
      "config": {
        "dsl": "sqlite",
        "role": "detection-candidate",
        "source": "endpoint",
        "content": "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')",
        "surface": "hb_file_activity",
        "description": "Detect if the Rails process accessed secrets or system files, indicating a successful file disclosure oracle.",
        "expected_signal": "A row showing the ruby process reading master.key or /etc/passwd. Silence proves absence of observed file access to these paths."
      },
      "parents": [
        {
          "id": "early-stage-agent"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "detection-candidate",
        "label": "Sensitive file access by Rails process",
        "reads": [
          "device_hostname",
          "process_name",
          "file_path",
          "time"
        ],
        "source": "hb_file_activity",
        "target": "endpoint",
        "content": "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')",
        "silence": "evidence_of_absence",
        "expected": "A row showing the ruby process reading master.key or /etc/passwd. Silence proves absence of observed file access to these paths.",
        "verified": "dry-run",
        "verified_at": "2026-09-20"
      }
    },
    {
      "id": "rare-child-processes",
      "type": "query",
      "label": "Rare child processes spawned by Rails",
      "config": {
        "dsl": "sqlite",
        "role": "baseline",
        "source": "endpoint",
        "content": "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",
        "surface": "hb_process_activity",
        "description": "Stack-count child processes of Rails across the fleet to find RCE payloads that spawn shells or external tools.",
        "expected_signal": "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."
      },
      "parents": [
        {
          "id": "early-stage-agent"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "baseline",
        "label": "Rare child processes spawned by Rails",
        "reads": [
          "device_hostname",
          "process_name",
          "parent_process_name",
          "time"
        ],
        "source": "hb_process_activity",
        "target": "endpoint",
        "content": "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",
        "silence": "not_evidence_of_absence",
        "baseline": {
          "window": "{{lookback_days}}d",
          "compare": "first_seen"
        },
        "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.",
        "verified": "dry-run",
        "prevalence": {
          "by": "device_hostname",
          "key": [
            "child_process"
          ],
          "rare_below": 3
        },
        "verified_at": "2026-09-20"
      }
    },
    {
      "id": "follow-on-agent",
      "type": "analytic",
      "label": "Assess total chain impact",
      "config": {
        "cite": "required",
        "tools": [
          "endpoint",
          "web"
        ],
        "context": [
          "early-stage-agent",
          "sensitive-file-reads",
          "rare-child-processes"
        ],
        "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.",
        "description": "Connect the early-stage HTTP leads with observed file disclosure or anomalous process execution.",
        "max_iterations": 5,
        "expected_signal": "A per-host verdict confirming whether the libvips vulnerability was successfully exploited.",
        "success_criteria": "A per-host verdict of malicious (confirmed chain), suspicious (partial chain), or benign."
      },
      "parents": [
        {
          "id": "sensitive-file-reads",
          "kind": "merge"
        },
        {
          "id": "rare-child-processes",
          "kind": "merge"
        }
      ]
    },
    {
      "id": "route-on-verdict",
      "type": "checkpoint",
      "label": "Route on verdict",
      "config": {
        "fuzzy": true,
        "judge": "hunter",
        "question": "the follow-on-agent verdict is malicious for at least one host",
        "condition": "the follow-on-agent verdict is malicious for at least one host",
        "blind_spot": "no-http-telemetry",
        "confidence": "high",
        "description": "Direct response actions based on the agent's confidence in the exploitation chain.",
        "checkpoint_type": "mandatory"
      },
      "parents": [
        {
          "id": "follow-on-agent"
        }
      ]
    },
    {
      "id": "isolate-host",
      "type": "action",
      "label": "Isolate host",
      "config": {
        "target": "endpoint",
        "description": "Contain the breach by isolating the compromised Rails server.",
        "instructions": "Isolate the host immediately. Revoke the Rails master.key and rotate all credentials stored in the application environment.",
        "action_approval": "required"
      },
      "parents": [
        {
          "id": "route-on-verdict",
          "branch": "on_supports"
        }
      ]
    },
    {
      "id": "analyst-review",
      "type": "task",
      "label": "Analyst review",
      "config": {
        "assignee": "analyst",
        "description": "Perform manual review of the correlated evidence to confirm the scope of the file read or RCE.",
        "instructions": "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."
      },
      "parents": [
        {
          "id": "route-on-verdict",
          "branch": "default"
        },
        {
          "id": "route-on-verdict",
          "branch": "on_unavailable"
        },
        {
          "id": "isolate-host"
        }
      ]
    },
    {
      "id": "close-out",
      "type": "task",
      "label": "Close out",
      "config": {
        "assignee": "analyst",
        "description": "Verify patching and document the negative result.",
        "instructions": "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."
      },
      "parents": [
        {
          "id": "route-on-verdict",
          "branch": "on_refutes"
        }
      ]
    }
  ]
}