SSH Tunneling & Port Forwarding

CybersecurityBeginner
Practice Now

Introduction

Secure Shell (SSH) is not just a tool for remote terminal access; it is also a powerful networking utility. SSH Tunneling, also known as SSH Port Forwarding, allows you to route local network traffic through a secure SSH connection to remote hosts.

The core idea is that you are borrowing connectivity from another machine. The tunnel does not create new network reachability from nowhere; it reuses the remote host's position and access.

This technique is widely used by system administrators, developers, and security professionals to bypass rigid corporate firewalls, encrypt legacy application traffic, and access hidden internal services that are not directly exposed to the public internet.

In this lab, you will learn the concepts and practical applications of two primary types of SSH port forwarding:

  • Local Port Forwarding (-L): Forwards a port from your local machine to a remote destination.
  • Remote Port Forwarding (-R): Forwards a port from a remote server back to your local machine.

Understand the Network Topology

In this step, we will explore the simulated network environment that has been prepared for you.

To practice SSH tunneling, we need a scenario involving multiple machines. Since we are working within a single virtual machine environment, we have simulated a complex network topology using different network ports on your local system (127.0.0.1).

Here is the conceptual mapping of our simulation. Your Local Machine is represented by your current terminal session. The Bastion Host, or Edge Server, is simulated using a Docker container that exposes SSH on 127.0.0.1:2222. The Internal Service is a web server running on the same private Docker network at 172.31.0.20:80. Conceptually, imagine this service is deep inside a corporate network and blocked by a firewall, meaning it can only be accessed from the Bastion Host. Finally, the Local Service is a web server running on port 8000 on the LabEx VM. You can think of this as a development server running on your personal laptop that you want to share with someone on the internet.

Let's verify that our simulated services are running. We will use the curl command, which is a command-line tool for transferring data with URLs.

First, check the "Local Service" running on port 8000:

curl http://127.0.0.1:8000

You should see the output Local Developer Machine Data.

The internal service is intentionally not exposed directly on the LabEx VM. You will reach it through the SSH tunnel in the next step.

In the following steps, we will use SSH tunneling to manipulate how we access these services, simulating real-world firewall bypass scenarios.

Configure Local Port Forwarding (-L)

In this step, you will configure Local Port Forwarding.

Imagine you are at home and need to access an internal corporate dashboard (our service at 172.31.0.20:80). A firewall blocks direct access to that internal address from the outside world. However, the firewall allows SSH connections to a specific edge server (our Bastion Host).

Local Port Forwarding allows you to open a port on your local machine, and SSH will securely forward any traffic sent to that local port through the SSH tunnel to the destination.

The syntax for Local Port Forwarding is: ssh -L [local_port]:[destination_host]:[destination_port] [user]@[ssh_server]

Local forwarding is easiest to understand as "make a remote service appear local to me."

We will forward our local port 8080 to the internal service (172.31.0.20:80) via our simulated SSH bastion host. We will also use two additional flags. The -N flag tells SSH not to execute a remote command, which is useful when you only want to forward ports. The -f flag tells SSH to run in the background, allowing us to continue using our terminal.

Execute the following command to create the tunnel:

ssh -N -f -L 8080:172.31.0.20:80 \
  -o StrictHostKeyChecking=no \
  -o UserKnownHostsFile=/dev/null \
  -i ~/project/pivot_key -p 2222 pivot@127.0.0.1

The tunnel is now running silently in the background.

Now, let's test the tunnel. Instead of accessing the internal Docker address directly, you will access your newly opened local port 8080. The SSH tunnel will grab this request, encrypt it, send it to the bastion host, which will then forward it to 172.31.0.20:80.

curl http://127.0.0.1:8080

You should see the Secret Internal Corporate Data output. You have successfully bypassed the conceptual firewall!

Configure Remote Port Forwarding (-R)

In this step, you will configure Remote Port Forwarding.

Imagine you are developing a web application on your local laptop (our service on port 8000). You want to show your progress to a client, but your laptop is behind a home router (NAT) and doesn't have a public IP address.

Remote Port Forwarding solves this by allowing you to connect to a public SSH server and open a port on that remote server. Any traffic hitting that remote port is forwarded back through the SSH tunnel to your local machine.

The syntax for Remote Port Forwarding is: ssh -R [remote_port]:[local_host]:[local_port] [user]@[ssh_server]

Remote forwarding flips the perspective: "make my local service appear reachable from the remote side."

We will expose our local service (127.0.0.1:8000) to the bastion host on port 8081.

Execute the following command to create the remote tunnel:

ssh -N -f -R 8081:127.0.0.1:8000 \
  -o StrictHostKeyChecking=no \
  -o UserKnownHostsFile=/dev/null \
  -i ~/project/pivot_key -p 2222 pivot@127.0.0.1

The tunnel is now established in the background. The bastion host is now listening on port 8081. When someone accesses port 8081 on the remote server, the traffic is sent back to your local port 8000.

Let's test this by querying the forwarded service through the bastion host container:

docker exec bastion_host curl -s http://127.0.0.1:8081

You should see the Local Developer Machine Data output. You have successfully exposed a local service to a remote server!

Access Internal Web Service

In this final step, we will solidify our understanding by using the tunnels we created to download the data and save it to files in our project directory.

Ensure you are in your default project directory:

cd ~/project

First, let's use the Local Port Forwarding tunnel (port 8080) to fetch the internal corporate data and save it to a file named internal_data.txt. We will use the -o flag with curl to specify the output file.

curl http://127.0.0.1:8080 -o internal_data.txt

Next, let's use the Remote Port Forwarding tunnel (port 8081) to fetch the local developer data through the bastion host and save it to a file named remote_data.txt.

docker exec bastion_host curl -s http://127.0.0.1:8081 > remote_data.txt

Finally, let's verify the contents of the files you just downloaded using the cat command:

cat internal_data.txt
cat remote_data.txt
Secret Internal Corporate Data
Local Developer Machine Data

By successfully downloading these files through the forwarded ports, you have proven that the SSH tunnels are actively routing traffic securely between the endpoints.

Summary

Congratulations! You have successfully completed the SSH Tunneling & Port Forwarding lab.

In this lab, you learned to understand the concepts of SSH tunneling and how it can be used to bypass network restrictions. You configured Local Port Forwarding (-L) to securely access hidden internal services by routing local traffic through a bastion host. Additionally, you configured Remote Port Forwarding (-R) to expose local development services to a remote server, successfully bypassing local NAT and firewalls. Finally, you learned how to run SSH tunnels quietly in the background using the -N and -f flags.

These skills are essential for secure system administration, remote development, and navigating complex network topologies safely.