# RCE which may occur due to `ActiveSupport::MessageVerifier` or `ActiveSupport::MessageEncryptor` (especially Active storage)

Rails MessageVerifier/MessageEncryptor default to Marshal serializer, so a signed ActiveStorage disk URL carrying a Marshal gadget yields RCE when secret_key_base is known.

- Payout: $1,500
- Severity: high
- Program: Ruby on Rails
- Credited: ooooooo_q
- Disclosed: 2019-03-13
- CVE: CVE-2019-5420
- Write-up quality: 7/10
- Original: https://hackerone.com/reports/473888
- Library page: https://bugbountylibrary.com/r/h1%3A473888
- Tags: domain:web impact:integrity impact:rce kind:bounty platform:hackerone sev:high tech:ruby technique:source-review vuln:deserialization vuln:rce

**Target:** ActiveStorage disk controller / ActiveSupport::MessageVerifier
**Root cause:** MessageVerifier and MessageEncryptor default @serializer to Marshal, so verified signed blobs are deserialized with Marshal.load, enabling object-injection gadget chains.
**Steps:**
1. Derive secret: `key_generator.generate_key("ActiveStorage")` from secret_key_base
2. Build gadget: ERB.allocate with @src='`touch /tmp/rce`', wrap in ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(erb, :result)
3. `verifier.generate(dump_target, purpose: :blob_key)` to get signed encoded_key
4. GET `/rails/active_storage/disk/<encoded_key>/test`
5. Marshal.load during verified() triggers ERB#result -> command execution
**How found:** Code reading: noticed Marshal is the default serializer in MessageVerifier/MessageEncryptor initializers, then traced that ActiveStorage's disk controller calls verifier.verified on a user-supplied encoded_key, making the deserialization reachable via URL.
**Exploitation:** Attacker who knows secret_key_base (trivially derivable from app name in development mode) forges a signed blob_key containing a Marshal gadget; requesting the ActiveStorage disk route deserializes it and executes the embedded command.
**Insight:** When a framework signs+deserializes user-supplied tokens, check the default serializer: signed does not mean safe if Marshal/YAML.load is used. Look for routes that call verified()/find_signed on request params even when the feature (ActiveStorage) appears unused.
