Jewel
A public GitWeb instance leaks the full source of a Ruby on Rails blog, revealing Rails 5.2.2.1 — vulnerable to CVE-2020-8165. The app caches your username in Redis and deserializes it with Marshal.load, so a crafted serialized object stored as a username executes a shell command on the next page load, giving code execution as bill and the user flag.
Overview
Jewel is a medium-difficulty Linux machine centred on source-code review of a Ruby on Rails application. A GitWeb instance on port 8000 serves the full git repository of the blog, and the leaked Gemfile pins Rails 5.2.2.1 — a version vulnerable to CVE-2020-8165, an untrusted-deserialization flaw in RedisCacheStore/MemCacheStore. The app caches each user’s username in Redis and later runs Marshal.load on it (even with raw: true), so storing a serialized Ruby gadget as our username yields remote code execution as bill. This post covers recon through the user flag.
Recon
| Port | Service | Notes |
|---|---|---|
| 22 | OpenSSH | publickey-only (password auth disabled) |
| 8000 | GitWeb | Perl CGI git browser — full repo exposed |
| 8080 | Phusion Passenger | the Ruby on Rails blog |
1
nmap -sC -sV 10.129.10.75
Two web surfaces stood out: a GitWeb git browser on 8000 and the actual Rails app on 8080.
Enumeration
GitWeb exposes the entire git repository. Rather than click through the web UI, the snapshot action dumps the whole repo as a tarball for offline review:
1
2
3
curl -s "http://10.129.10.75:8000/gitweb/" | grep -oE 'p=[^;"&]+\.git'
wget "http://10.129.10.75:8000/gitweb/?p=.git;a=snapshot;h=HEAD;sf=tgz" -O blog.tgz
tar xzf blog.tgz
Reading the source, the Gemfile.lock shows Rails 5.2.2.1. More importantly, the users controller caches the username in Redis and reads it back with RedisCacheStore:
1
2
cache = ActiveSupport::Cache::RedisCacheStore.new(url: "redis://127.0.0.1:6379/0")
@current_username = cache.fetch("username_#{session[:user_id]}", raw: true) { ... }
That combination — vulnerable Rails version + a user-controlled value flowing through the cache — is CVE-2020-8165.
Foothold
The cache stores whatever we set as our username, and on the next read Rails calls Marshal.load on it. We build a serialized ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy wrapping an ERB object whose source is a shell command. (Key detail: the gadget must include the @deprecator instance variable — without it the target’s method_missing raises before the command runs.)
The flow is fully scriptable: register an account, set the username to the serialized payload via the profile update (the binary value makes the DB write fail with a 500, which leaves the object parked in the cache), then load any page to trigger deserialization.
1
2
3
# register + login at /signup, then PATCH /users/<id> with username=<marshal payload>
# the update 500s (object cached); GET /articles triggers Marshal.load -> RCE
python3 exploit.py
The command we executed wrote our SSH public key into bill’s authorized_keys. Since SSH is publickey-only, that gives a clean, stable shell:
1
2
ssh -i jewel_key [email protected]
id # uid=1000(bill)
User flag
1
cat /home/bill/user.txt # HTB{...}
Code execution as bill achieved via the Rails deserialization bug, and the user flag captured.
Foothold complete. Privilege escalation is left as an exercise — this post stops at user.