Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/pentesting-web/file-upload/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<sup>[[1]](#references)</sup>


### 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**.<sup>[[24]](#references)</sup>

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.<sup>[[24]](#references)[[25]](#references)</sup>

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.<sup>[[24]](#references)</sup>

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.<sup>[[24]](#references)[[26]](#references)[[27]](#references)</sup>

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)
Expand All @@ -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}}