SSH

OpenSSH’s ssh is the standard encrypted remote-login client — but on an assessment it’s less a login tool than a general-purpose transport: arbitrary TCP ports, Unix-domain sockets, and X11 connections can all be forwarded over the secure channel, and every forward rides the authentication you already have. That makes SSH the first thing to reach for when you need to move laterally through a network that only permits port 22.

Port forwarding

The single most important mental model: forward specifications read as $FROM_SPEC:$TO_SPEC. A local forward (-L) creates a listening port on the machine where ssh runs; a remote forward (-R) creates a listening port on the server side. Forwarding is always one-way — from the created port to the destination.

The bind address in $FROM_SPEC controls exposure: an explicit localhost keeps the listener loopback-only, while an empty address or * exposes it on all interfaces. The default is governed by the GatewayPorts setting, and only root can forward privileged ports.

Local port forwarding

Forward $LOCAL_PORT from the local machine to $TARGET_HOST:$TARGET_PORT, resolved relative to $REMOTE_HOST:

ssh -L $LOCAL_PORT:$TARGET_HOST:$TARGET_PORT $REMOTE_USER@$REMOTE_HOST

Traffic flows localhost:$LOCAL_PORT$REMOTE_HOST$TARGET_HOST:$TARGET_PORT. This is the workhorse for reaching internal services (databases, RDP, web admin panels) that only the pivot host can see.

Remote port forwarding

Forward $REMOTE_PORT on the server back to $TARGET_HOST:$TARGET_PORT, resolved relative to the local machine:

ssh -R $REMOTE_PORT:$TARGET_HOST:$TARGET_PORT $REMOTE_USER@$REMOTE_HOST

Traffic flows $REMOTE_HOST:$REMOTE_PORT → local machine → $TARGET_HOST:$TARGET_PORT. Use this to expose a service from your side to the target network — e.g. handing a payload or a catch-listener to machines that can’t route back to you directly. (If the pivot is a Windows host and other machines need to reach the forwarded port, you’ll also need a firewall rule — see netsh-windows-firewall.)

Dynamic port forwarding (SOCKS)

ssh -D $LOCAL_PORT $REMOTE_USER@$REMOTE_HOST

ssh listens on $LOCAL_PORT and acts as a SOCKS4/SOCKS5 server; the destination of each connection is determined by the application protocol, so one forward carries traffic anywhere the remote host can reach. Pair it with a proxy-aware tool (or proxychains) to push arbitrary tools through the tunnel without one -L per target.

Reverse dynamic forwarding

Since OpenSSH 7.6 (2017-10-03), -R used without a target gives the mirror image: a SOCKS proxy on the server that forwards through the client:

ssh -R $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST

The remote host (and anything that can reach $REMOTE_PORT there, subject to GatewayPorts) can now use your machine as a SOCKS proxy into your network. It’s implemented entirely client-side, so it works against any server version.

Forwarding UDP

SSH itself only carries TCP, but chaining socat through an SSH tunnel converts UDP↔TCP on each side — the standard trick for reaching SNMP (161/udp), DNS, or other UDP services through a pivot.

The interactive escape command line

With a pty attached, ~ is the escape character (recognized only at the start of a line). ~C opens an ssh> prompt that accepts forwarding directives exactly as they’d appear on the command line:

ssh> -L 3845:localhost:80        # add a local forward at runtime
ssh> -KL 3845                    # kill it again
ssh> ~?                          # list all escape sequences

Runtime reconfiguration means the forwards you actually use never appear in the original ssh invocation — which also means they don’t appear in shell history or process listings tied to the connection’s startup. From OpenSSH 9.2 (2023-02-02) the ~C command line is disabled by default; re-enable per-host with EnableEscapeCommandline yes in ~/.ssh/config (or -o EnableEscapeCommandline=yes). The change lets platforms with client sandboxing (OpenBSD) apply a stricter default policy.

Other useful sequences: ~. closes the connection (kills a hung session), ~^Z suspends, ~? lists the rest.

IPv4 vs. IPv6

OpenSSH is fully IPv6-aware. Where an IPv6 literal is needed in an address-bearing option, enclose it in square brackets — -L [::1]:8080:localhost:80, ssh user@[2001:db8::1]. When a hostname resolves to both A and AAAA records and the choice matters (e.g. one family is filtered, or logs on one are watched more closely), -4 and -6 force the address family; AddressFamily inet|inet6 is the config-file equivalent.

Key management quick hits

ssh-keygen -p -f $SSH_PRIVATE_KEY_FILE   # change a key's passphrase in place

SSH on Windows

OpenSSH has shipped with Windows since Windows 10 1803. Two Windows-specific wrinkles:

  • Kerberos-style usernames leak into SSH. On a domain-joined box the default username is the full $DOMAIN\$USER, which non-Windows SSH servers usually reject or misinterpret. Always set the user explicitly — ssh user@host or a User line in ~/.ssh/config — rather than letting the ambient Kerberos identity flow through.
  • ssh-agent is a disabled-by-default Windows service. It must be enabled (Set-Service ssh-agent -StartupType Automatic) before ssh-add works; don’t assume agent forwarding behaves like it does on a Linux box.
  • ssh-udp-forwarding-socat — carrying UDP through an SSH tunnel with socat
  • ssh-agent-bypass — forcing ssh to ignore the agent and use a specific identity file
  • netsh-windows-firewall — opening the Windows Firewall so forwarded ports are reachable
  • putty-saved-sessions — the other common SSH client on Windows targets, with lootable tunnel configs
  • tmux — the standard pairing: a persistent local multiplexer for the SSH sessions running through all these forwards
  • telnet — the cleartext protocol SSH replaced; still found on legacy gear
  • xterm-reverse-shell — X11-forwarding abuse in the opposite direction (X client → attacker’s X server)

See Also

Sources