SSH Tunneling Magic

A question I keep seeing asked is: “How can I remotely access a box that’s restricted to an internal network?” Fortunately we can leverage SSH to accomplish this goal in most scenarios.

A common example of this is during CTF events when target boxes are on a local network and teams are allowed support from remote members. How do you let your remote members access the challenges? Or perhaps you’re on a penetration testing engagement and you need to use a jump-box to reach your testing environment.

There are an infinite number of scenarios that may occur, but I’m going to cover a few I’ve seen in the past year.

First though, let’s cover the SSH arguments that will be used. If you haven’t read the SSH man page, go do that. The first time I did it, I had a “holy crap you can do so much” feeling. If you’re too lazy, then here are the big ones for this article:

-D port | Specifies a local “dynamic” application-level port forwarding

-L port:host:host_port | Local port forward, forward a port from client machine to remote machine

-R port:host:host_port | Remote port forward, forward a port from remote machine

Scenario One: Only SSH Is Available

Assume the following network:

  • Target: SSH Server (22), Web Server (80, localhost only)

In this scenario, we have SSH credentials to a target box that is hosting a web server that can only be reached on the loop-back interface.

Of course we could SSH into the target box, then from our SSH connection just curl localhost. However, this is a great basic example to get warmed up for some port forwarding. To accomplish this, logically we will be forwarding a local port on the User machine to port 80 on Target. This is done using the -L argument with SSH:

User
----

ssh reznok@10.10.1.100 -L 5000:127.0.0.1:80

What this says is:

Start an SSH connection to 10.10.1.100 (Target) and forward User port 5000 to remote host 127.0.0.1 (Target) port 80. As long as this connection is available, we can reach the web server on Target:

User
----

curl http://localhost:5000
...
<HTTP response from Target>

The remote_host parameter of -L is relative to the SSH server. This becomes more important in the next scenario.

Scenario Two: The Jump-Box

Assume the following network:

  • Jump-Box: SSH Server (22)
  • Internal A: Web Server (80), FTP Server(21)
  • Internal B: Web Server (80), Web Server(8000)
  • Internal C: Web Server (443)

In this scenario, we want to go from our machine to an internal network. We are provided with SSH credentials to a Jump-Box that is both externally reachable by  User (on the 10.10.1.0/24 network) and has access to the target internal network (192.168.1.0/24).

Note: In this scenario, the 10.10.1.0/24 network could also be the “the internet”

We have two options here, either use a SOCKS proxy or forward individual ports. Let’s start with individual port forwarding.

Let’s say we really want to view the web server on port 80 on Internal A from User. To accomplish this, logically we will be forwarding a local port on the User machine to port 80 on Internal A. This is done using the -L argument with SSH:

User
----

ssh reznok@10.10.1.100 -L 5000:192.168.1.3:80

What this says is:

Start an SSH connection to 10.10.1.100 (Jump-Box) and forward User port 5000 to remote host 192.168.1.3 (Internal A) port 80. Remember, the remote host is relative to the SSH server. As long as this connection is alive, we can reach Internal A:

User
----

curl http://localhost:5000
...
<HTTP Response from Internal A>

For a more dynamic approach, as mentioned before, we could also use a SOCKS proxy. SOCKS proxies are great because they’re not tied to one destination port. However, they rely on the client software being used to support SOCKS proxies. For web servers this isn’t an issue, curl and almost every browser support them.

SOCKS proxies over SSH are created using the -D argument:

User
----

 ssh reznok@10.10.1.100 -D 5000

As long as this connection remains open, the User machine can reach any machine the Jump-Box can reach by adjusting their proxy settings:

User
----
curl 192.168.1.3:80 -x socks5h://localhost:5000
...
<HTTP Response from Internal A>

curl 192.168.1.4:8000 -x socks5h://localhost:5000
...
<HTTP Response from Internal B>

curl https://192.168.1.5 -x socks5h://localhost:5000
...
<HTTP response from Internal C>

Scenario Three: The CTF, Putting It All Together

Assume the following network:

  • Jump-Box: SSH Server (22)
  • Attendee: SSH Server (22)
  • Internal A: Web Server (80), FTP Server(21)
  • Internal B: Web Server (80), Web Server(8000)
  • Internal C: Web Server (443)

In this scenario, we want User to be able to reach boxes in the internal CTF network (192.168.1.0/24). User has SSH access to Jump-Box and Attendee has SSH access to Jump-Box. User also has SSH credentials to access Attendee, but cannot directly reach it. The 10.10.1.0/24 network is directly accessible from the internet.

This is the exact scenario my CTF team ran into at the OpenCTF at Defcon 2018. There’s more than one way to solve this, but I’ll only be covering my preferred method which is having the Attendee run an SSH server. Our goal becomes getting direct SSH access from User to Attendee.

The first hurdle is that User and Jump-Box can’t reach Attendee. So, Attendee will have to reach out to Jump-Box. Using the -R argument, we can create a remote port forward:

Attendee
----
ssh reznok@10.10.1.100 -R 1000:localhost:22

What this says is:

Create an ssh connection from Attendee to Jump-Box and forward the remote port 1000 (on Jump-Box) to localhost:22 (on Attendee). As long as this connection is established, we can reach the Attendee SSH server from Jump-Box by connecting to localhost:1000.

Important: With the -R option, unlike -L, the host is relative to the client, not the server.

Scenario One covered how we can access services that are only listening  on the loopback interface, so we can use that to get direct SSH access from User.

Note: There are methods to expose SSH port forwards so that they’re reachable from non loop-back interfaces. Go read the SSH man page!

User
----
ssh reznok@10.10.1.100 -L 5000:127.0.0.1:1000

We now have two SSH connections that are established and open. One from Attendee to Jump-Box with a remote port forward, and one from User to Jump-Box with a local port forward. Because they’re both forwarding using port 1000, and request made to localhost:5000 on User will now be routed to port 22 on Attendee, meaning we can:

User
----
ssh reznok@localhost -p 5000
...
<SSH Response from Attendee>

Because we can now “directly” SSH into attendee, we can now use the exact same methods as Scenario Two for accessing machines on the internal network:

User
----
ssh reznok@localhost -p 5000 -L 4000:192.168.1.3:80

Three consistent connections now, and finally:

User
----
curl localhost:4000 
... 
<HTTP Response from 192.168.1.3>

Be mindful of what ports you’re using!

Further Reading

For some more SSH magic, such as creating these port forward from an already established SSH connection, I highly recommend reading Jeff McJunkin’s article Using the SSH “Konami Code” (SSH Control Sequences)