# Remote code execution on rubygems.org

RubyGems' checksum parser used unsafe YAML.load, letting a crafted gem reach Marshal.load and achieve RCE on rubygems.org via the gem push API.

- Payout: $1,500
- Severity: critical
- Program: RubyGems
- Found by: max
- Disclosed: 2017-11-09
- CVE: CVE-2017-0903
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/274990
- Library page: https://bugbountylibrary.com/r/h1%3A274990
- Tags: area:web domain:web impact:rce kind:bounty platform:hackerone sev:critical tech:ruby technique:chained technique:source-review vuln:deserialization vuln:rce

**Target:** rubygems.org /api/v1/gems gem push endpoint and Gem::Package checksum parser
**Root cause:** The app monkey-patched the spec parser to use Psych.safe_load, but Gem::Package#read_checksums calls YAML.load directly on the gem's checksum file, allowing arbitrary object deserialization.
**Steps:**
1. Craft a malicious gem whose checksum file contains a YAML payload that deserializes to a call to Marshal.load on attacker-controlled data.
2. POST the gem to the push endpoint: `cat poc.gem | curl -H 'Content-Type: application/gzip' --data-binary @- -H 'Authorization: █████' https://rubygems.org/api/v1/gems`.
3. Server calls Gem::Package.new(body).spec in app/models/pusher.rb, then read_checksums invokes YAML.load on the crafted checksum file.
4. YAML deserialization reaches Marshal.load on attacker data, executing known Marshal gadget chains for code execution (PoC runs wget to attacker server).
**How found:** Noticed the app defensively monkey-patched the spec parser to Psych.safe_load, then checked other YAML parsing paths in Gem::Package and found read_checksums still calling YAML.load directly.
**Exploitation:** Attacker with a valid API token pushes a crafted gem to /api/v1/gems; the server parses it, hits unsafe YAML.load in read_checksums, pivots to Marshal.load on attacker-controlled bytes, and executes code on the rubygems.org server (PoC performed a wget callback).
**Escalation:** Chained a YAML deserialization sink to a Marshal.load sink using classes available in the app, escalating object deserialization to full RCE.
**Insight:** When a codebase patches one unsafe deserialization sink (e.g. safe_load on spec parsing), audit sibling parsing paths in the same library for unpatched YAML.load/Marshal.load sinks reachable from the same untrusted upload.

## Proof of concept (copied exactly from the original report)

**curl POST of malicious gem to trigger RCE**

```
cat poc.gem | curl -H 'Content-Type: application/gzip' --data-binary @- -H 'Authorization: █████' https://rubygems.org/api/v1/gems
```

