RedTeam Pentesting GmbH - Blog

9 May 2023

Introducing resocks - An Encrypted Back-Connect SOCKS Proxy for Network Pivoting

Compromising a host in a company’s perimeter often creates the opportunity to pivot into an internal network. From there on, each additional compromised system may grant us access into further subnets. Pivoting like this is second nature to pentesters, but let’s step back and look at this scenario in detail in order to understand why we created our newest open-source tool resocks - a secure reverse SOCKS5 proxy (reverse as in reverse shell, not as in reverse proxy).

Suppose we want to route our traffic through a compromised system into adjacent networks that we can’t reach directly. While there are countless techniques to achieve this, many come with significant downsides. Forwarding ports is cumbersome and may allow other parties to also access the forwarded ports. As penetration testers, we don’t want to expose our customers’ networks like this. SSH supports dynamic port forwarding, but it is often not available on Windows hosts and reverse SSH connections to traverse NAT require some thought and setup. For example, the trust-on-first-use approach for verifying the SSH host key is not an option when you assume a machine-in-the-middle which may intercept the connection. Instead, we wanted a solution with as little friction as possible. This is where resocks comes into play.

resocks in Action

The program resocks is written in Go and compiles to a single binary that is deployed on the attack system as well as the compromised host. It can be run in one of two modes: listener and relay. First, on the attack system resocks is run in listener mode, opening a TCP port to accept incoming connections. On the compromised host (called “relay host” from now on), resocks is run in relay mode and instructed to connect back to the attack system, establishing a secure channel that can traverse NAT.

When the secure channel is established successfully, the listener instance running on the attack system opens up a SOCKS5 server port and routes all traffic through the secure channel to the relay host and from there to the target. Think of it as combining the back-connect technique used by reverse shells with the dynamic forward function of SSH.

resocks Overview

You might have noticed we keep saying “secure” channel. resocks secures the channel using a common connection key that can be generated ad-hoc. However, just generally claiming that something is secure has actually very little meaning unless we’ve defined a clear threat model which we aim to defend against. So let’s do that!

Threat Model: Attacking the Attackers

Establishing a clear threat model is one of the most important steps in the development of security-related applications. We cannot recommend enough to both formulate and communicate a threat model. Unfortunately, especially offensive tools often lack a threat model and do not consider the attackers using such tools (such as pentesters) being themselves targeted by other malicious attackers.

In contrast, resocks was designed with the following attacker models in mind:

  • A: Malicious Observer: Attackers with network access between the listener and the relay host should not be able to see the SOCKS5 traffic that is routed through the secure channel.
  • B: Malicious Listener: Attackers should not be able to run their own resocks listener and redirect a resocks connection to it, as this would grant them access to the relay host’s network and services.
  • C: Malicious Relay: Attackers should not be able to connect to an existing listener in order to be able to receive the traffic that was meant to be routed through the relay host.

In a pentest, all three scenarios would endanger the customer. Scenarios A and C allow attackers to observe the pentesters at work. This endangers the customer, as the network traffic may contain vital hints about the vulnerabilities that are exploited by the pentesters. Scenario B allows attackers themselves to directly attack internal networks of the customer through the relay deployed by the pentesters. This demonstrates that even offensive tools used by pentesters can be worthwhile targets for actual attackers.

One thing these attacker models have in common is that they assume an attacker somewhere in between the listener and the relay. However, attackers could also already have access to the host on which the listener or the relay runs. This creates the following two additional attacker models:

  • D: Malicious User on Listener System: Malicious users on the system hosting the listener are generally able to connect to the SOCKS5 proxy or extract the connection key.
  • E: Malicious User on Relay System: A malicious user on the system hosting the relay can generally extract the connection key.

In contrast to attacker models A, B and C, resocks was not designed to defend against D and E and only provides somewhat limited defense-in-depth measures against such attackers.

Securing the Tunnel

Defending against a malicious observer of the traffic between listener and relay (model A) obviously mandates encryption of the traffic. When taking malicious listeners or relays into account, we need to establish trust between the legitimate listener and relay. These are the primary concerns from a security perspective, however, they have to be balanced with ease-of-use, convenience and implementation complexity.

One of the first things that comes into mind when looking at these security demands is TLS. With client certificates in addition to server certificates, TLS can be used to establish a mutually trusted connection (commonly called mTLS). Additionally, modern TLS offers perfect forward secrecy. However, manual certificate management is certainly not convenient to do in practice. A certificate would either have to be deployed alongside the relay or embedded into the relay binary. The former method introduces more initial steps required to use resocks on a newly compromised host while the latter requires users to re-build resocks on-demand. Both scenarios are obviously deal breakers for ease-of-use and convenience. In order to limit implementation complexity, we also decided against implementing a custom protocol.

Finally, we came up with a way to tame the setup cost of TLS: A certificate generation scheme based on a compact shared key. We first generate a so-called connection key that we share between the listener and relay. The connection key acts as a seed for an ed25519 key that is used to create a self-signed CA certificate. So you can say that the connection key is the ed25519 key.

Why do we use ed25519 specifically? In contrast to for example RSA, it does not rely on entropy for signing, which enables us to deterministically generate the same (byte-identical) CA certificate on both the listener and the relay given the same connection key. Under the hood, TLS with ed25519 uses an algorithm called Edwards-curve Digital Signature Algorithm (EdDSA), which was created as a successor to the Elliptic Curve Digital Signature Algorithm (ECDSA):

As with other discrete-log-based signature schemes, EdDSA uses a secret value called a nonce unique to each signature. In the signature schemes DSA and ECDSA, this nonce is traditionally generated randomly for each signature—and if the random number generator is ever broken and predictable when making a signature, the signature can leak the private key, as happened with the Sony PlayStation 3 firmware update signing key.

In contrast, EdDSA chooses the nonce deterministically as the hash of a part of the private key and the message. Thus, once a private key is generated, EdDSA has no further need for a random number generator in order to make signatures, and there is no danger that a broken random number generator used to make a signature will reveal the private key.

Additionally, the connection key is small enough to be conveniently passed as a command-line argument. In the next step, both the listener and the relay independently generate completely random keys to be used for independently generated client or server certificates. Finally, both components can use the identical CA key to sign their client or server certificates. As a result, the client and server certificates can be used to establish a mutually trusted connection.

Key-Based TLS

We found that simply passing the 256-bit seed values for ed25519 keys as a 43 character string to both components is convenient enough in practice. As a result, we have begun to use this approach to secure the traffic of other tools we have developed for internal use. We also published our Go implementation kbtls (Key-Based TLS) as a library which is used by resocks. While we are unaware of any security risks caused by this technique, it does use TLS in a slightly unconventional way, so please let us know if you are aware of any security concerns.

With this out of the way, let’s take a look at how resocks can improve your life as a pentester.

Designing resocks

The corner stone of resocks' user experience is the listener. One of the key concerns of the listener is to convey the current state of resocks. Is the listener waiting on reverse connections from the relay system? Or is the relay already connected and the SOCKS proxy is ready to use? Initial testing showed that a traditional logging-style output did not convey the current state effectively in scenarios where the relay system frequently disconnects and re-connects (which sometimes happens during a pentest). Based on this experience we chose a stateful display. After all, what could convey state better than a stateful display? Here’s what it looks like in practice:

The next user experience issue is the connection key. Of course, having to specify a matching connection key still introduces some friction, even if it is way better than manual certificate management. That’s why resocks offers multiple ways to handle the connection key. As shown above, resocks listen displays a newly generated connection key by default that can be copied and passed to the relay as follows:

# relay: connect back to listener at 10.0.2.2
$ resocks 10.0.2.2 --key "ilKync+DujFREmiXlYB+/+UpXsUhuFJIeOwFloZWItk"
connected to 10.0.2.2:4080

However, this connection key becomes invalid when the listener is restarted, as it generates a new connection key by default. A static key could also be passed to the listener via --key to avoid generating a new key, but a static key can also be conveniently stored in an environment variable:

$ export RESOCKS_KEY="$(resocks generate)"

In order to avoid having to set the same environment variable on the remote relay system, resocks can also just embed a pre-generated connection key during compilation as follows:

$ go build -ldflags="-X main.defaultConnectionKey=$(resocks generate)"

This binary can then be used to start both the listener and the relay without having to pass the connection key for each run.

Remember the threat models we discussed before? We stated that resocks was not explicitly designed to protect against scenarios D and E: A malicious user on the listener or relay system. For some specific cases of these scenarios, alternate ways to specify the connection key can offer some protection. If the malicious user can see process listings including command-line arguments, the connection key will be exposed if it is passed via the --key option. In this case, an environment variable or a connection key compiled into the binary may be a better choice as long as the binary is not readable for the attacker. Unfortunately, if the malicious actor uses the same account or a high-privileged account you are out of luck.

One of the reasons we developed resocks in the first place was that the proxy feature of Metasploit’s meterpreter was often quite unreliable and could crash the whole meterpreter session. This experience made us think about resilience (for example against unreliable network connections) during the development of resocks. For instance, we added the option --reconnect-after to the relay to make the relay recover from intermittent connection issues. With the following command you can start a relay that re-connects after one second when the connection is interrupted:

$ resocks 10.0.2.2 --reconnect-after 1s --key "ilKync+DujFREmiXlYB+/+UpXsUhuFJIeO..."

The listener will wait for the relay to reconnect by default unless you specify --abort-on-disconnect. By default, the reconnect feature is disabled in order to avoid situations where a misconfigured resocks instance keeps on living forever attempting to connect.

One of the advantages of using Go for resocks is that it can easily be cross-compiled for almost any operating system without any special setup. Just specify the target operating system using the $GOOS environment variable during compilation:

$ GOOS=windows go build

For a list of available operating system ($GOOS) and architecture ($GOARCH) combinations, run the following command:

$ go tool dist list

Of course, we have not tested all of these targets, but we expect that resocks works at least as a relay in most cases.

Open Source at RedTeam Pentesting

We are always proud to be able to contribute to the InfoSec community by releasing our offensive tools. If you like resocks, check out our name resolution spoofer pretender (blog post) or our HTTP fuzzer monsoon (blog post) and stay tuned for future releases which we announce here and on our social media.