Full Report
The premise of the post is having an arbitrary file write in Ruby on Rails. The twist was that the Dockerfile had the application run as a non-root user with only some directories being owned by the executing user. The goal was to get an RCE in this restricted environment. Given the situation, the natural thing to do is to recreate the environment yourself to see what's possible. One of the directories that could be written to was /tmp. Ruby has a framework called Bootsnap that allows for loading Ruby/Rails Apps faster via caching. Much of the configuration and cache for Bootsnap is stored within /tmp/cache/bootsnap. Upon reviewing the contents, they noticed that load-path-cache contained gem file paths in to the MessagePack format. Additionally, comiple-cache-* contained compiled Ruby, JSON and YAML. From there, they decided to review the source code of Bootsnap to get an idea of what made sense to corrupt. The Bootsnap startup went as follows: Bootsnap is loaded from config/boot.rb. Load path caching. For every require, Bootsnap checks the cache first. Compile the cache. Bootsnap caches the compiled Ruby code from the previous steps and stores them in a directory containing a hash of the file. The object of this attack was to overwrite one of the cached compiled Ruby binaries then trigger an application restart. The bulk of the cache file contains information about the version and where it should be loaded this way. Nartually, this can be spoofed and set to an arbitrary value using the originally vulnerable. So, RCE is achieved! How do we restart the server though to load our corrupted cache file? The Puma server will automatically restart if anything is written to /tmp/restart.txt. The arbitrary file write can be used to write to this file a second time to trigger the RCE bug. I really enjoyed this blog post! Taking a library and explaining how to abuse its quirks was an awesome use of time. I bet many people will use this in the future for their endeavors.
Analysis Summary
# Vulnerability: RCE via Bootsnap Cache Poisoning in Restricted Rails Environments
## CVE Details
- **CVE ID:** Not specifically assigned (Exploitation technique for Arbitrary File Write vulnerabilities).
- **CVSS Score:** N/A (Dependent on the underlying Arbitrary File Write vulnerability, typically 9.8 Critical if RCE is achieved).
- **CWE:** CWE-94 (Improper Control of Generation of Code), CWE-22 (Path Traversal).
## Affected Systems
- **Products:** Ruby on Rails Applications.
- **Versions:** Rails 5.2 and later (when Bootsnap is enabled); Rails 7.1+ (default Docker configurations).
- **Configurations:**
- Applications using the **Bootsnap** library for boot optimization.
- Environments using the **Puma** web server with the `tmp_restart` plugin enabled.
- Dockerized environments where the app runs as a non-root user but has write access to the `/tmp` or `/rails/tmp` directories.
## Vulnerability Description
This is a post-exploitation technique that upgrades an **Arbitrary File Write** to **Remote Code Execution (RCE)**. In modern Rails deployments, security hardening often restricts file writes to specific directories like `/tmp`.
**Bootsnap**, a library used to speed up boot times, caches compiled Ruby code (ISEQ), JSON, and YAML in `tmp/cache/bootsnap`. Because these cache files are owned by the application user, an attacker with an arbitrary file write primitive can:
1. **Poison the Cache:** Overwrite a cached `.rb` file's compiled binary with malicious Ruby code.
2. **Bypass Integrity:** The cache files contain a header with versioning and file path info; however, this can be spoofed by the attacker to match the expected metadata.
3. **Trigger Execution:** By writing to `tmp/restart.txt`, the attacker triggers the Puma server to restart, forcing the application to reload and execute the poisoned cache file.
## Exploitation
- **Status:** PoC described; technique validated in research environments.
- **Complexity:** Medium (Requires understanding of Bootsnap's cache format/MessagePack).
- **Attack Vector:** Local (via an existing web-based Arbitrary File Write vulnerability).
## Impact
- **Confidentiality:** High (Full access to application data and environment variables).
- **Integrity:** High (Ability to modify application logic and data).
- **Availability:** High (Ability to crash the server or disrupt service).
## Remediation
### Patches
- There is no specific patch for Bootsnap, as it is functioning as designed. Remediation must focus on the primary Arbitrary File Write vulnerability in the application code.
### Workarounds
- **Disable Bootsnap in Production:** Remove `require 'bootsnap/setup'` from `config/boot.rb`.
- **Read-Only Cache:** Configure the Docker container to mount the Bootsnap cache directory as read-only after the initial pre-compilation step.
- **Strict File Permissions:** Ensure the application user cannot write to `tmp/restart.txt` or the `tmp/cache` directory if dynamic caching is not required at runtime.
## Detection
- **Indicators of Compromise:**
- Unexpected modifications to files within `tmp/cache/bootsnap/`.
- Rapid successive writes to `tmp/restart.txt`.
- Presence of binary/MessagePack data in unexpected file upload directories (indicating path traversal attempts).
- **Detection Methods:**
- Monitor file integrity for `tmp/cache/bootsnap`.
- Audit logs for the `FileUtils.mkdir_p` or `File.open` calls involving path traversal sequences (`../`).
## References
- hxxps://blog.convisoappsec[.]com/from-arbitrary-file-write-to-rce-in-restricted-rails-apps/
- hxxps://github[.]com/Shopify/bootsnap
- hxxps://rubyonrails[.]org/2023/10/5/Rails-7-1-0-has-been-released
- hxxps://www.sonarsource[.]com/blog/why-code-security-matters-even-in-hardened-environments/