SSH port forwarding turns an encrypted admin channel into a general-purpose tunnel. The three flags that matter are -L (local forward), -R (remote forward) and -D (dynamic SOCKS proxy). Because the payload is encrypted, we detect the tunnel from process arguments, session duration and byte symmetry rather than from content.
Signals at a Glance
| Signal | Where | ATT&CK |
|---|---|---|
| ssh/plink process with -L, -R or -D forwarding flags | Sysmon 1, 4688, auditd, EDR | T1572 |
| Outbound SSH from a workstation or non-admin server to the Internet | Firewall, Zeek conn.log | T1572 |
| Session lasting hours with steady bidirectional bytes | Zeek conn.log, NetFlow | T1090 |
| ssh -D opens a local SOCKS listener on the client | EDR listening-port telemetry | T1090.001 |
| sshd with AllowTcpForwarding or GatewayPorts enabled on servers that do not need it | Config audit | T1021.004 |
Command-Line Forwarding Flags
The forwarding flags are explicit and rarely appear in routine administration of a workstation. The Windows OpenSSH client ships with the OS, so ssh.exe on an endpoint with -R or -D deserves a look. Start with a process-creation rule:
title: SSH Client Port Forwarding Flags
logsource:
category: process_creation
product: windows
detection:
selection_img:
Image|endswith:
- '\ssh.exe'
- '\plink.exe'
selection_flags:
CommandLine|contains:
- ' -R '
- ' -L '
- ' -D '
- ' -N '
condition: selection_img and selection_flags
level: medium
# tune/validate: allow-list admin jump hosts and developer workstations;
# -N with -R or -D (no remote command) is the higher-signal combination.
Linux hosts need auditd execve or EDR telemetry for the same arguments. The same logic applies there: forwarding flags combined with -N and -f (background, no command) indicate a tunnel rather than an interactive session.
Session Duration and Server Configuration
On the wire we cannot read the payload, but we can measure it. Interactive SSH is bursty and short; a tunnel carrying proxied traffic stays up for hours and moves data in both directions. Start from Zeek connection logs:
# Splunk (Zeek conn.log) - long outbound SSH to external hosts
index=zeek sourcetype=bro_conn id.resp_p=22 service=ssh
| where duration > 3600 AND NOT cidrmatch("10.0.0.0/8", 'id.resp_h')
| eval ratio=round(orig_bytes/(resp_bytes+1),2)
| stats count AS sessions, max(duration) AS longest, sum(orig_bytes) AS up, sum(resp_bytes) AS down
BY id.orig_h, id.resp_h
| sort - longest
# tune/validate: exclude approved bastions, Git hosting and vendor support
# destinations, and consider non-22 ports where ssh is detected by app-ID.
- Audit
sshd_configacross servers:AllowTcpForwarding noandGatewayPorts noare the safe defaults for hosts that are not bastions. - Alert when a server that has never initiated outbound SSH begins doing so, especially to residential or VPS address space.
- Pair with
ssh -Dlisteners (127.0.0.1 on high ports) from EDR, because a SOCKS listener on an endpoint is the pivot point for follow-on tooling. - Treat SSH on non-standard ports as the same signal: use firewall app-ID rather than port numbers.
Use the SigmaHQ process-creation rules for SSH and plink tunneling, Elastic detection rules for SSH port forwarding, Splunk ESCU analytic stories for lateral movement over SSH, and Zeek connection and SSH logs. Always baseline your own environment — administrators and developers use forwarding legitimately, so scope by host role.
Build Detections That Fire
Mjolnir Security helps blue teams turn threat intelligence into high-fidelity detections across endpoint, network, identity and cloud telemetry.
- Detection Engineering We build and tune detections mapped to ATT&CK from your own telemetry.
- Threat Hunting as a Service Hypothesis-driven hunts for the behaviours on this page.
- 24/7 Incident Response Containment when a detection fires. Call +1 833 403 5875.
Stay ahead of emerging threats. Get notified when we publish new intelligence reports and advisories.
Subscribe to Alerts