Reddish
Box details | |
---|---|
OS | Linux |
Difficulty | Insane |
Status | Retired |
Release | July 2018 |
Completed | September 2025 |
Enumeration
Started by enumerating the target with Nmap to discover open ports and running services. Only a single open TCP port was discovered:
Scanned the target for common open UDP ports, but found none.
Attempting to navigate to the web server on port 1880 only returns an error message:
The error message refers to a GET request. Changing the request type to POST returns a JSON object:
The path
in the JSON object hints about a possible URL: http://10.10.10.94:1880/red/caa395f52cdd8ec38369d4b50d25fe6a
. Navigating to this URL reveals a Node-RED UI:
Foothold
Node-RED is a framework for visual programming. Through the web UI, functional blocks, called nodes, can be connected together to build application flows that accept external input, execute functions and deliver output.
Among the nodes available in the UI, there is an exec
node that allows users to run system commands. The description reads:
Runs a system command and returns its output.
The node can be configured to either wait until the command completes, or to send its output as the command generates it.
The command that is run can be configured in the node or provided by the received message.
This is an obvious candidate for running arbitrary commands on the target. A reverse shell for Node-RED can be found here. Importing it into the web UI produces the following flow:
Stood up a Netcat listener, deployed the flow and got a call back as root
:
Although this is a root shell, it spawned inside a Docker container:
The output from mount
shows a few configuration files mounted from the host file system:
The container is also dual-homed with network interfaces on both 172.18.0.0/16
and 172.19.0.0/16
:
To futher enumerate the container and the network, a better reverse shell is needed. The intitial shell can be upgraded by standing up a new Netcat listener and spawning a Bash shell inside it like so:
Network Enumeration
Over in the new reverse Bash shell, a quick way to enumerate the two networks is by running a ping sweep:
Not counting the gateway and IP addresses belonging to the current container, two more hosts were discovered on the 172.19.0.0/16
network: 172.19.0.2
and 172.19.0.4
.
Without access to a proper network scanner like Nmap, enumerating open ports on the other containers requires a more basic approach. One way of doing it is by writing directly to TCP sockets using Bash. If a port is open, the write will succeed, if not, it will return an error. For instance:
This can be automated with a simple Bash script:
Running the script on the target reveals two open ports, one on each of the two containers:
TCP/6379 is the default port for Redis. Given that the second container runs what is likely a web server on port 80, it makes sense that the container on 172.19.0.2
could be the database for it.
As the Node-RED container doesn't have any tools for interacting with the services on the other containers, this has to be done from the attack host through pivoting.
Pivot to the www
Container
The web server container (www
) is a good place to start. The first step is to decide on a pivoting tool and get it stood up on the attack host and target.
One such pivoting tool is Ligolo-ng. Unlike most pivoting tools, Ligolo-ng works more like a C2 with a central server hosted on the attack host with one or more agents deployed on the pivot hosts. It's also intuitive and relatively easy to manage through both a CLI and web UI.
The limited environment in the container also means that the agent needs to be transferred using only what's available in Bash. Again, using a raw TCP socket, this can be achieved by directing a file to a Netcat listner on the attack host:
- On the attack host:
- On the target:
Note
While simple to set up, there is no progress feedback and the connection remains open even after the transfer finishes. The only way to make sure the file transferred successfully is to compare the file hashes of the source file and the received file.
Once the server is up, the agent is connected and configured with a route to 172.19.0.0/16
. At this point the web server on 172.19.0.4:80
can be accessed directly from the attack host:
Though the site isn't much to look at, there is an embedded JavaScript script on the page:
At first glance, the script's purpose appears to be a counter for the number of time the site is visited. There is also a backupDatabase()
function for saving the count, which is presumably saved in a database on the other containing running Redis.
The Redis instance can be interacted with using the dedicated redis-cli
tool, or simply using Netcat. As the Ligolo-ng tunnel is already set up for the correct network, the container and the Redis instance can be reached by setting the host address to 171.19.0.2
:
Tip
As it turns out, the web application running on the other container is a direct interface to the Redis CLI. Passing a Redis command to the test
parameter will execute the command on the database backend:
As explained in this guide, access to the Redis CLI can be abused to gain RCE by through a simple PHP shell. The only requirement is a web server running PHP, but as mentioned briefly in one of the comments in the web application source code above, the web directory (/var/www/html/8924d0549008565c554f8128cd11fda4
) is shared between the database and the web server.
Following the guide above, the web shell is stood up like so:
Accessing the shell through the www
container confirms RCE:
With RCE in place, the next step is to get a reverse shell on www
. However, unlike in the case of the Node-RED container, this container isn't accessible from the attack host. Instead, the solution is to set up a double pivot using the Node-RED container as a proxy for accessing a reverse shell.
The double pivot can be set up with Ligolo-ng by starting an additional TCP listener on the agent running on the Node-RED container. By connecting the other end of the listener to a free port (running Netcat in listener mode) on the attack host, a connection made from the web server using the PHP reverse shell over the listener on the Node-RED container will reach the attack host.
The connection setup at this point can be visualized like so:
The additional listener on the Node-RED container is stood up using the Ligolo-ng CLI:
Note
Ligolo-ng listeners can be set up to either connect back to the Ligolo-ng server (port 11601) for a double pivot, or to an arbitrary TCP/UDP port. In this case, as the server is accessible directly from 172.19.0.3
, a direct connection to a TCP port is sufficient.
The reverse shell connection is triggered by sending a POST request to the PHP web shell running on www
with the following payload:
This is easiest to achieve using a web proxy:
Got a call back in the Netcat listener on the attack host as www-data
:
Privilege Escalation
Repeating the file system enumeration from the Node-RED container, this container also has some file systems mounted from the host, including /home
:
/home
contains two users' home directories:
Both directories are readable, though none of them contain anything of value, except the user flag in /home/somaro
. Unfortunately, the file itself isn't readable.
Enumerating the /
file system, there is a /backup
directory containing the following script:
backup.sh
The script is owned by root:root
, but isn't executable. Instead, it's being called by a cron job in /etc/cron.d
:
The script uses rsync
to backup Redis database files from /var/www/html/f187a0ec71ce99642e4f0afbd441a68b
, but it does so by specifying the target .rdb
files with a wildcard and without escaping special characters. The files are backed up to a remote host called backup
, but this isn't listed in /etc/hosts
. The container also doesn't have any tools like host
or dig
to query the DNS server for the IP address. A work-around could have been to use ping
, but www-data
doesn't have the necessary permissions:
In any case, the way the rsync
command is used opens up for abusing the wildcard paramerter. Specifically, running rsync
with the -e
can be used to execute arbitrary shell commands. This can be extended to running longer commands as well by placing them in a script file with an .rdb
extension and trigger them by creating a file on the form -e <script>.pdb
. The following example illustrates the idea:
The script can be created on the attack host with a proper editor and saved to something like test.rdb
:
Next, it can be transferred to the target by setting up a new Ligolo-ng listener, then hosting the file with Netcat like so:
On the target, the file can be downloaded by reading it from a raw socket:
Once transferred, the empty file needed for rsync
is created like so:
/tmp/test.txt
is created within the three minute window defined in the cron job:
From here, there are several ways of getting root on www
. One way is to stand up yet another reverse shell by replacing the payload above with the following:
Repeated the process above and got a reverse shell as root
:
With root access to the container, the user flag in /home/somaro
is readable.
Root access also makes it possible to find the backup
host's IP address using ping
:
Going back to the backup script from earlier, the rsync
command can be modified to exfiltrate files from backup
. For instance, /etc/passwd
is exfiltrated like so:
Note
Note the use of /src/
in the file path above. rsync
does not accept relative paths and directory traversal characters.
Pivot to backup
The next step is getting code execution on the backup
host. Since this target is only accessible from the www
container, this requires yet another pivot and yet another reverse shell. Unlike in the previous case with the reverse shell from www
going over the Node-RED
container, this setup requires an additional Ligolo-ng agent on www
to act as a proxy.
The agent can be transferred to www
using Netcat and raw sockets as shown in previous examples. In order to connect the new agent to the Ligolo-ng server on the attack host, a new listener is needed:
Since the goal is to connect the new agent to the Ligolo-ng server, the new listener is connected to Ligolo-ng's listening port (11601). The agent on www
is started like so:
The new connection is picked up by the Ligolo-ng server:
The next step is to set up an interface and route for the new agent on the Ligolo-ng server:
With the tunnel up, 172.20.0.2
is directly accessible from the attack host:
The last step is to set up a listener on the new agent that can connect back to the attack host:
At this point, the network looks like this:
A reverse shell is within reach, but the fact that there is no direct way to run commands on backup
from www
makes this a challenge. The only way around this is to leverage rsync
to set up a cron job on backup
and wait for it to trigger.
The cron
job is simple enough:
The file can be transferred to backup
with rsync
:
Got a callback in a Netcat listener as root
:
Privilege Escalation (root)
Similar to the containers encountered earlier, backup
also has several active mounts on /dev/sda2
:
/backup
doesn't appear to have anything of interest:
However, having /dev/sda2
mounted directly to /backup
may be a hint that the container is privileged and is allowed to mount directories from the host. Assuming /
on the host is on /dev/sda2
, mounting it to /mnt
should only succeed if this is the case:
No /.dockerenv
confirms that /mnt
is actually the root file system on the host, which also means:
Got the root flag.