diff --git a/src/pentesting-web/file-upload/README.md b/src/pentesting-web/file-upload/README.md index db16b737a4d..873bd987f64 100644 --- a/src/pentesting-web/file-upload/README.md +++ b/src/pentesting-web/file-upload/README.md @@ -623,6 +623,52 @@ Content-Type: application/json Backend copies `file.filepath`, so the response returns that path’s content. Common chain: read `/proc/self/environ` to learn `$HOME`, then `$HOME/.n8n/config` for keys and `$HOME/.n8n/database.sqlite` for user identifiers.[[1]](#references) + +### Ruby on Rails Active Storage + libvips parser confusion → arbitrary file read + +A useful upload pattern to test is: **the application stores an attacker-controlled MIME type before inspecting bytes, then a later image-processing step trusts that metadata and lets a backend parser sniff the real format**.[[24]](#references) + +In **Rails Active Storage direct uploads**, the blob record can be created with attacker-controlled `content_type` **before** the file is inspected. If later code only checks whether the stored type is inside the image allowlist (for example `image/png`), a non-image file can still reach the variant/analyzer pipeline.[[24]](#references)[[25]](#references) + +Useful checks: + +- **Pre-upload metadata trust:** Can you create the upload/blob/database record first and persist `content_type=image/png` (or another transformable type) without server-side MIME re-identification? +- **Independently signed processing tokens:** If the app verifies a blob/object identifier and a processing token separately, test whether a legitimate token from one object can be replayed against a malicious upload. +- **Backend parser disagreement:** Compare the outer file sniffer with the deeper parser. A short magic-byte check in layer 1 plus version/offset-based parsing in layer 2 is a strong **polyglot/parser-confusion** candidate. + +One practical Rails chain abused `image_processing` + `vips` leaving loader selection to libvips. libvips `matload` only checked bytes `0-9` for `MATLAB 5.0`, while libmatio selected **MAT 7.3** from bytes `124-125`. A crafted file could therefore: + +- start with `MATLAB 5.0` to satisfy libvips, +- set bytes `124-127` to a valid MAT 7.3 version/endian marker, +- place a real HDF5 superblock after a **512-byte userblock**, +- and use **HDF5 external dataset storage** to read bytes from an attacker-chosen path and offset.[[24]](#references) + +This turns the image processor into an **arbitrary file-read oracle**. Good targets are `/proc/self/environ`, `config/master.key`, encrypted credentials, or other files readable by the web worker. If the application returns the generated image, file bytes may come back as pixels; if not, analyzer metadata can still become a lower-bandwidth exfil channel. + +Related pages: +- [Ruby Tricks](../../network-services-pentesting/pentesting-web/ruby-tricks.md) +- [ImageMagick Security](../../network-services-pentesting/pentesting-web/imagemagick-security.md) + +### From file read to signed-operation abuse + +Once you can read application secrets, validate candidate signing material against a genuine signed value from the app (for Rails, a real signed blob/token is ideal). If the image-processing pipeline later applies attacker-influenced transformation names with dynamic dispatch such as `public_send(name, *argument)`, recovered signing authority may become RCE.[[24]](#references)[[26]](#references)[[27]](#references) + +Example JSON-compatible transformation shapes: + +```json +{"send":["spawn","/bin/sh","-c","id"]} +{"send":["eval","File.write('/tmp/kr2s', %x{id})"]} +``` + +Even if the HTTP response returns `500`, the payload may already have executed while the processing chain was being built. + +### Detection / hardening notes + +- Flag files whose first bytes claim `MATLAB 5.0` but whose bytes `124-127` carry a **MAT 7.3** version/endian tag. +- Re-identify MIME types **server-side after upload**, not only from pre-upload metadata. +- Bind signed processing options to the specific blob/object they are meant for. +- For libvips-backed processing of untrusted uploads, block operations marked untrusted (for example via `Vips.block_untrusted(true)`). + ## References - [1] [n8n form upload Content-Type confusion → arbitrary file read PoC](https://github.com/Chocapikk/CVE-2026-21858) @@ -648,5 +694,9 @@ Backend copies `file.filepath`, so the response returns that path’s content. C - [21] [HackerOne report 3712279 – Burp Suite Professional browser-powered crawler file input path traversal leading to arbitrary file write and delayed code execution](https://hackerone.com/reports/3712279) - [22] [Persistent PHP payloads in PNGs: How to inject PHP code in an image – and keep it there!](https://www.synacktiv.com/publications/persistent-php-payloads-in-pngs-how-to-inject-php-code-in-an-image-and-keep-it-there.html) - [23] [Compressed file upload and command execution](https://blog.silentsignal.eu/2014/01/31/file-upload-unzip/) +- [24] [Rapid7 Analysis: KindaRails2Shell (CVE-2026-66066)](https://rapid7.com/blog/post/ra-kindarails2shell-technical-analysis-cve-2026-66066) +- [25] [Rails security advisory GHSA-xr9x-r78c-5hrm](https://github.com/rails/rails/security/advisories/GHSA-xr9x-r78c-5hrm) +- [26] [rails/rails#56995 – Active Storage Vips transformation validation discussion](https://github.com/rails/rails/pull/56995) +- [27] [Rapid7 Metasploit module: rails_activestorage_vips_rce](https://github.com/rapid7/metasploit-framework/pull/21733) {{#include ../../banners/hacktricks-training.md}}