Add content from: Marshal madness: A brief history of Ruby deserialization exp...

This commit is contained in:
HackTricks News Bot 2025-08-20 06:36:55 +00:00
parent 22bea233ef
commit a919fe6dc4

View File

@ -1082,7 +1082,87 @@ This payload is compiled into binary Ruby code and concatenated with a carefully
Using the arbitrary file write vulnerability, the attacker writes the crafted cache file to the computed location. Next, they trigger a server restart (by writing to tmp/restart.txt, which is monitored by Puma). During restart, when Rails requires the targeted file, the malicious cache file is loaded, resulting in remote code execution (RCE). Using the arbitrary file write vulnerability, the attacker writes the crafted cache file to the computed location. Next, they trigger a server restart (by writing to tmp/restart.txt, which is monitored by Puma). During restart, when Rails requires the targeted file, the malicious cache file is loaded, resulting in remote code execution (RCE).
{{#include ../../banners/hacktricks-training.md}}
### Ruby Marshal exploitation in practice (updated)
Treat any path where untrusted bytes reach `Marshal.load`/`marshal_load` as an RCE sink. Marshal reconstructs arbitrary object graphs and triggers library/gem callbacks during materialization.
- Minimal vulnerable Rails code path:
```ruby
class UserRestoreController < ApplicationController
def show
user_data = params[:data]
if user_data.present?
deserialized_user = Marshal.load(Base64.decode64(user_data))
render plain: "OK: #{deserialized_user.inspect}"
else
render plain: "No data", status: :bad_request
end
end
end
```
- Common gadget classes seen in real chains: `Gem::SpecFetcher`, `Gem::Version`, `Gem::RequestSet::Lockfile`, `Gem::Resolver::GitSpecification`, `Gem::Source::Git`.
- Typical side-effect marker embedded in payloads (executed during unmarshal):
```
*-TmTT="$(id>/tmp/marshal-poc)"any.zip
```
Where it surfaces in real apps:
- Rails cache stores and session stores historically using Marshal
- Background job backends and file-backed object stores
- Any custom persistence or transport of binary object blobs
Industrialized gadget discovery:
- Grep for constructors, `hash`, `_load`, `init_with`, or side-effectful methods invoked during unmarshal
- Use CodeQLs Ruby unsafe deserialization queries to trace sources → sinks and surface gadgets
- Validate with public multi-format PoCs (JSON/XML/YAML/Marshal)
Detection (SAST):
- Semgrep rules:
- rails-cache-store-marshal: https://github.com/trailofbits/semgrep-rules/blob/main/ruby/rails-cache-store-marshal.yaml
- marshal-load-method: https://github.com/trailofbits/semgrep-rules/blob/main/ruby/marshal-load-method.yaml
- json-create-deserialization: https://github.com/trailofbits/semgrep-rules/blob/main/ruby/json-create-deserialization.yaml
- yaml-unsafe-load: https://github.com/trailofbits/semgrep-rules/blob/main/ruby/yaml-unsafe-load.yaml
- CodeQL:
- Query help: https://codeql.github.com/codeql-query-help/ruby/rb-unsafe-deserialization/
- Payload PoCs: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization
Mitigations (what to do):
- Never pass attacker-controlled bytes to `Marshal.load`/`marshal_load`
- Prefer safe formats and APIs:
- YAML.safe_load with strict `permitted_classes`
- JSON with manual object construction
- Typed DB columns instead of opaque blobs
- Ecosystem hardening proposal:
- Introduce `Marshal.safe_load` (primitive-only by default, with `permitted_classes`)
- Warn on `Marshal.load`, switch defaults to safe behavior, and gate legacy behavior behind `Marshal.unsafe_load`
Notes on recent timeline (selected):
- 20182022: Universal gadget chains across Ruby 2.x3.x (elttam, Bowling) and patches through Ruby 3.1/3.2
- 2019: Rails 5.2 insecure deserialization (CVE-2019-5420)
- 2024: Include Security shows gadget discovery via grep; GitHub Security Lab ships CodeQL rules + multi-format PoCs
- 2024-12: Ruby 3.4.0-rc1 near-miss in `rubygems` code path patched before GA (PR #12444)
- 2024-11/12: New Ruby 3.4 Marshal chains and SafeMarshal escape published and subsequently patched
## References
- Trail of Bits Marshal madness: A brief history of Ruby deserialization exploits: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
- elttam Ruby 2.x Universal RCE Deserialization Gadget Chain: https://www.elttam.com/blog/ruby-deserialization/
- Phrack #69 Rails 3/4 Marshal chain: https://phrack.org/issues/69/12.html
- CVE-2019-5420 (Rails 5.2 insecure deserialization): https://nvd.nist.gov/vuln/detail/CVE-2019-5420
- ZDI RCE via Ruby on Rails Active Storage insecure deserialization: https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
- Include Security Discovering gadget chains in Rubyland: https://blog.includesecurity.com/2024/03/discovering-deserialization-gadget-chains-in-rubyland/
- GitHub Security Lab Ruby unsafe deserialization (query help): https://codeql.github.com/codeql-query-help/ruby/rb-unsafe-deserialization/
- GitHub Security Lab PoCs repo: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization
- Doyensec PR Ruby 3.4 gadget: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization/pull/1
- Luke Jahnke Ruby 3.4 universal chain: https://nastystereo.com/security/ruby-3.4-deserialization.html
- Luke Jahnke Gem::SafeMarshal escape: https://nastystereo.com/security/ruby-safe-marshal-escape.html
- Ruby 3.4.0-rc1 release: https://github.com/ruby/ruby/releases/tag/v3_4_0_rc1
- Ruby fix PR #12444: https://github.com/ruby/ruby/pull/12444
- Trail of Bits Auditing RubyGems.org (Marshal findings): https://blog.trailofbits.com/2024/12/11/auditing-the-ruby-ecosystems-central-package-repository/
{{#include ../../banners/hacktricks-training.md}}