SSH Tunneling and Pivoting

SSH tunneling and pivoting: local, remote, and dynamic port forwarding and SSH agent forwarding and hijacking for post-exploitation.

VariableDescription
LPORTPort on your attacker machine. With -L: port that listens (you connect here). With -R: port where your listener/service runs (tunnel forwards here).
SSH_HOSTIP/Hostname for the SSH server (the pivot)
USERNAMESSH Username
SERVICE_HOSTIP/Hostname of the service you are trying to tunnel to (local port forwarding)
SERVICE_PORTPort number for the service you are trying to tunnel to (local port forwarding)
RPORTPort that will listen on the pivot (remote port forwarding)

Prerequisites: You have SSH access (password or key) to a host $SSH_HOST. That host is your pivot: you run all ssh commands on your own machine (attacker box); the SSH client connects to $SSH_HOST, and traffic you send to a forwarded port (or through the proxy) is carried over the encrypted SSH channel and appears to come from $SSH_HOST.

What tunneling gives you: Reach internal services (databases, web apps, etc.) that only the pivot can see; expose your own listeners to the pivot for callbacks; or send arbitrary traffic through the pivot via a SOCKS proxy.


Local Port Forwarding (-L)

Running ssh -L on your machine so the SSH client opens a port on your box (e.g. localhost:8080). When you connect to that port, the client sends the traffic through the SSH session to the server on the pivot ($SSH_HOST). The SSH server on the pivot then connects to a destination host:port (on the pivot or elsewhere). So you reach a remote service by connecting to a local port: your machine → local port → tunnel → pivot → destination service.

Full Command:

ssh -L $LPORT:$SERVICE_HOST:$SERVICE_PORT $USERNAME@$SSH_HOST
  • Leave the session open (or run in background).
  • On your machine, connect to localhost:$LPORT with your client (mysql, curl, browser, etc.).
  • Your client’s traffic is sent through the tunnel; the SSH server on $SSH_HOST connects to the destination. You receive the response as if the service were local.


Example: MySQL on the pivot:

MySQL runs on $SSH_HOST on port 3306 (or is only reachable from there). You want to use your local MySQL client to talk to it.

# On your machine (leave this session open or run in background)
ssh -L 3306:127.0.0.1:3306 $USERNAME@$SSH_HOST

In another terminal on your local machine:

mysql -h 127.0.0.1 -P 3306 -u root -p

What you get: A normal MySQL session. All queries go through the tunnel to the database on $SSH_HOST; the database sees the connection as coming from $SSH_HOST.


Example: Internal web server (pivot can reach it, you cannot):

A web server at 192.168.10.5:80 is only reachable from inside the target network. You use $SSH_HOST as a pivot to reach it.

# On your machine (leave this session open)
ssh -L 8080:192.168.10.5:80 $USERNAME@$SSH_HOST

The 8080 in the command is the local port: SSH is now listening on your machine at localhost:8080. So you connect to that:

curl http://localhost:8080

What you get: The HTTP response from 192.168.10.5:80 (the internal site’s HTML, API data, etc.). Your request goes: your machine → localhost:8080 → tunnel → $SSH_HOST → 192.168.10.5:80. The internal server only sees a connection from $SSH_HOST. This is a way to access (and effectively exfiltrate content from) an internal web server through the pivot.


Remote Port Forwarding (-R)

Running ssh -R on your machine makes the SSH server on the pivot ($SSH_HOST) listen on a port. When something on the pivot connects to that port, the SSH server forwards the traffic back through the tunnel to your machine and connects it to a port you choose (e.g. your listener or web server). So the pivot can reach a service on your box even when your IP is not directly reachable: pivot → tunnel → your machine local port.

VariableDescription
RPORTPort that will listen on the pivot (e.g. 8888). Something on the pivot connects here.
LPORTPort on your machine where your listener or service runs (e.g. 80 for web server, 4444 for nc).

Full Command:

ssh -R $RPORT:127.0.0.1:$LPORT $USERNAME@$SSH_HOST
  • Start the service that should receive connections on your machine first (e.g. nc -lvnp $LPORT or a web server on port 80). Leave it running.
  • Run the ssh -R command on your machine and leave the session open.
  • On the pivot, connect to 127.0.0.1:$RPORT; that connection is forwarded to your machine’s $LPORT.


Example: Expose your local web server to the pivot

You have a web server or tool listening on your machine on port 80. You want the pivot to be able to request it (e.g. to pull a payload or page) even though the pivot cannot reach your IP directly.

# On your machine: ensure something is listening on port 80, then (leave session open):
ssh -R 8888:127.0.0.1:80 $USERNAME@$SSH_HOST

On the pivot (e.g. in another SSH session to $SSH_HOST):

curl http://127.0.0.1:8888

What you get: The request hits your machine’s port 80. The pivot only sees localhost:8888; the response is served by your box. Useful for serving payloads or files when the pivot cannot reach your IP.


Example: Reverse shell or callback

You want a reverse shell (or any callback) to hit your listener, but the pivot cannot reach your IP:port. Expose your listener through the tunnel so the pivot connects only to its own localhost.

# Step 1: On your machine — start listener (e.g. in one terminal)
nc -lvnp 4444
# Step 2: On your machine — in another terminal, create the tunnel (leave open)
ssh -R 4444:127.0.0.1:4444 $USERNAME@$SSH_HOST

On the pivot, run your payload so it connects to 127.0.0.1:4444; that connection is forwarded to your nc.

What you get: The shell (or callback) appears on your listener. No inbound firewall rule on your side is needed; the callback is carried over the tunnel.

Thursday, February 5, 2026 Thursday, February 5, 2026