Oftentimes on engagements, I find myself with credentialled access to a Windows host. Whether it be the product of harvesting credentials, stealing them from docs or scripts, or being provided with them as part of a collaborative exercise, the first thing I like to achieve is the ability to route network traffic via the host and to avoid having to do anything else with the host for the rest of the engagement. Typically speaking, if you can avoid conducting anomalous behavior on a host with monitoring and instead tunnel your traffic right through the host, the risk of detection is significantly reduced (although not entirely).

Tools for tunneling traffic

When it comes to tunneling network traffic, there are lots of tools available like chisel or SOCAT. Where possible, I like to lean on one of my absolute favourite tools of the last year: ligolo-ng. The source code is fully available for remixing and rearchitecting to reduce detection and, since it’s written in Go, there are an ocean of obfuscation tools and techniques available. But what about the cases where you can’t get a binary to the box, whether because of web content restrictions or application allow lists. Thanks to Microsoft’s molasses-like adoption of *nix tooling to their core releases, you can now routinely find OpenSSH available on Window’s hosts.

Tunneling with SSH

We’re not going to delve into all the ways you can use SSH for offensive security (although there are several and you should get familiar with local port forwarding, dynamic forwarding, non-interactive command execution, jump hosts, and agent forwarding). Here we’re just going to look at reverse port forwarding. A reverse forward creates a listener on a specified port on a remote host through which you can send traffic that will be destined for the originating host. The process is as follows (using examples):

  • Windows host (local) uses SSH to connect to Kali host (remote).
  • When establishing the SSH connection, the Windows host specifies a reverse port forward using the -R argument.
  • The value passed to -R should be of the format REMOTE_PORT:LOCAL_IP:LOCAL_PORT.
  • For example, if the following command is executed on the Windows host ssh kali@ -R 8000:, a listener will be created on on the Kali host on port 8000/tcp and traffic sent to that port will be forwarded to port 8000/tcp on the Windows host.

To exemplify this, let’s create the tunnel and demonstrate the connection. First of all, using ssh on the Windows host, we can create a reverse forward on port 8000/tcp and use -fNT to background the process so we don’t actually connect to a shell (this is just a preference thing - you can let the session establish normally).

Successful SSH connection from Windows host to Kali

After the reverse forward is established, we’ll start up a Python web server on port 8000/tcp on the Windows host so we have something with which to test our connection.

Python Web Server running on Windows

Over on our Kali host, we can now curl port 8000/tcp on our localhost to create a connection with the Python web server on the Windows host.

Curl connection to Python Web Server from Kali

And on our web server we can see the established connection.

Python Web Server connection

Targeting remote hosts

So we can establish a reverse port forward that lets us access a web server on the Windows host - what’s the value of that for adversarial assessments? Typically, I make use of this kind of forward to target the Domain Controller in a Windows AD environment. I like to interrogate the SMB shares and LDAP service but I often want to use tools that will either trigger a detection if run on disk on the Windows host or which have dependencies that I cannot install on the Windows host. So with a reverse forward, I can change the target of the reverse forward to establish a tunnelled connection to the Domain Controller.

The process is the same as above but instead of supplying the localhost address or the IP address of an adapter on the Windows host, we can instead provide the IP address of a remote host. Jumping over to my hacklab, we have a Windows 10 host, a Kali machine, and a Windows 2012 domain controller. The Windows 10 host exists at, the Kali host exists at, and the DC exists at

From the Windows 10 host, we establish a reverse forward to the Kali host using SSH.

SSH connection from Windows 10 in hacklab to Kali

Now we have a listener on the Kali host on port 445/tcp which should connect directly to the domain services listener on the DC. We can run a quick nmap with some default scripts to enumerate information from the DC to confirm.

Nmap of DC from Kali host

As you can see the scan is targeting 445/tcp on the localhost of the Kali box but our scan is tunnelled to the DC and we get our results. It’s important to remember that a tunnel over SSH is going to have some limitations and will not support UDP or ICMP. For nmap scans, you’ll still find the best success using Connect() scans (-sT) over syn scans.

Do a little dynamic forwarding

The reverse forward can be redirected to target other ports on the DC or to target other hosts in the network. This specific targeting approach increases the likelihood of maintaining covertness in an environment by reducing the chances of inadvertently directing traffic at the wrong host. Repeatedly reconfiguring the forward can, however, be tedious. If you’re confident in your ability to avoid making noise, then you can take advantage of the dynamic reverse forward functionality in more modern versions of OpenSSH. You just need to change the format of the reverse directive: ssh user@destination -R In this context, the IP address is optional and only matters if you’d like to bind to a specific interface address.

The ability to handle authentication through keys also enables a non-interactive option. In a scenario where you can only execute commands non-interactively (say from a C2 implant with limited execution function), you can generate an SSH key, grab the public key, add it to the authorised keys on your destination host, and create connections without needing an interactive terminal to enter a password. Using SSH to tunnel through an otherwise trusted host has really become a staple of my daily engagements.