SSH Tunnel
How SSH Tunnel works
Network traffic flow
- D port
dynamic port forwarding
|
|
Client / Browser <==== local TCP port ====> SSH client <=== Secure Tunnel over Internet ===> OpenSSH (sshd) <== naked Internet ==> destination
| |
| |
loopback or any interface GatewayPorts=yes
NOTE: Traffic between the SSH client(s) and SSH server is encrypted. Like all remote access VPN (IPsec, OpenVPN, WireGuard) solutions.
SSH Client
OpenSSH CLI (ssh, sshd, ssh-add, ssh-agent, ssh-copy-id, ssh-keygen, ssh-keyscan, scp, sftp) for all platform.
Optional: PuTTY. Cygwin (mintty) for Windows - highly recommended.
Recommended Terminal Emulators:
Linux
GNOME Terminal, Konsole, Terminology, lx-terminal, guake (GNOME), Yakuake (KDE),finalterm, alacritty, kitty, etc.macOS
iTerm2 or macOS built-in Terminal.appWindows
Cygwin (mintty), Windows Terminal, PuTTY, SuperPutty (puttycm successor), MobaXterm (good GUI for SSH TCP Forwarding)
SSH Dynamic Application Level Port Forwarding via SOCKS5
TL;DR
The process
Establish SSH connection to target host (sshd)
Dynamic application-level port forwarding by allocating a socket to listen to port on the local side, optionally bound to bind_address
When a connection is made to this port, the connection is forwarded over the secure tunnel, and the application protocol is then used to determine where to connect to from the remote host
by default the local port is bound in accordance with the GatewayPorts setting
ssh -g -qfnNT -D 1080 <SSH_SERVER>
ssh -g -qfnNT -D *:1080 <SSH_SERVER>
# local use only
ssh -g -qfnNT -D localhost:1080 <SSH_SERVER>
Options explained:
-q Quiet mode.
-T Disable pseudo-terminal allocation.
-f Requests ssh to go to background just before command execution. This is useful if ssh is going to ask for passwords or passphrases, but the user wants it in the background.
-n Redirects stdin from /dev/null (actually, prevents reading from stdin). This MUST be used when ssh is running in the background.
-N Do not execute remote command
-D specifies a local "dynamic" application level port forwarding (DynamicForward in ssh_config)
-g Allows remote hosts to connect to local forwarded ports, share the SOCKS5 proxy across the network (GatewayPorts in ssh_config)
In addition on a slow connection you can gain performance by enabling compression with the -C option. In addition, putty-tools package provides plink which is very useful, here is a simple sample which shares the tunnel with all other hosts in the LAN.
NOTE: SOCKS5 operates in layer 5 (session) of the OSI model. Above Transport layer, below presentation.
NOTE: If you feel the options are too hard to remember, just use a simple version:
ssh -g -D 1080 <SSH_SERVER>
Alternative - plink (Provided by putty on Arch, Red Hat based distros, putty-tools on Debian based)
Linux / macOS
plink -C -A -N user@ssh_host -D 0.0.0.0:port
Windows
plink.exe -C -A -N user@ssh_host -D 0.0.0.0:port
Options explained:
-C
Enable SSH Compression-A
Enable agent forwarding-N
Don't start a remove command or shell at all-D
Dynamic port forwarding, same as -D for ssh
It is the same as ticking - Tunnels - Local ports accept connections from other hosts. Others in the same LAN will be able to share the ssh tunnel as well.
Note: -D 0.0.0.0 means to bind all interfaces (most likely eth{0..n}) so that other hosts in the same network will be able to use the tunnel.
How to browse via the secure tunnel (SOCKS5 proxy)?
Firefox Proxy Selector
Multiproxy Swith add-on
Note: set network.proxy.socks_remote_dns to true, use remote socks5 proxy (SSH Host) to lookup DNS instead of local DNS. This is normally used to avoid GFW DNS pollution/spoofing in China mainland. If NOT in PRC, it protects your privacy;-)
IMPORTANT NOTE: For DNS to be encrypted as well, consider running dnscrypt-proxy locally, or use DNS over TLS / DNS over HTTPS (e.g. Cloudflare's 1.1.1.1).
Chrome
Option 1: Proxy SwitchyOmega
Option 2: pass the parameter when launching chrome, create a launcher or shortcut
Linux in Terminal
google-chrome --proxy-server=socks5://localhost:port
macOS (doesn't work as expected...)
/Application/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --proxy-server=socks5://host:port
Windows in Command Line Prompt
%userprofile%\AppData\Local\Google\Chrome\Application\chrome.exe --proxy-server=socks5://localhost:port
Otherwise, use Proxy Switchy! extension to switch proxy settings.
Recommended: Extension: Proxy Switchy!
Use SSH Port Forwarding (Tunnel) To Access Server Behind Firewall / NAT Gateway
sshd <============================ reverse direction
GatewayPorts=yes ssh -R 22222:*:22 user@EC2
| |
| |
SSH Client (Laptop) <=== Internet ===> EC2 Instance <=== Firewall ===> Linux Server (behind NAT)
|
|
jump server
Listen on *:22222
On the middle EC2 instance (Linux), set GatewayPorts yes in sshd_config /etc/ssh/sshd_config, restart sshd (systemctl restart sshd.service).
RTFM: man sshd_configEstablish SSH connection from Linux Server (behind firewall) to the EC2 instance
On the Linux Server behind firewall (reverse direction)
# * indicates that the port should be available from all interfaces
ssh -R 22222:*:22 user@<EC2 INSTANCE>
# want to save step 3? bind to loopback only
ssh -R 22222:localhost:22 user@<EC2 INSTANCE>Block access to port 22222 on public facing network interfaces (elastic IP, this can be done by using security group, too, but at a different layer - Linux bridge + iptables in hypervisor dom0) by using iptables, e.g. iptables -A INPUT -p tcp -i <interface> --dport 22222 -j DROP
NOTE: if the same trick has been used multiple times, occupying port 12222, 22222, etc
iptables -A INPUT -p tcp -i <interface> -m multiport --dport 12222,22222 -j DROPOn the laptop, establish SSH connection to the EC2 instance (optionally acts as SOCKS server using -D which does dynamic application level port forwarding) ssh -D 1080 user@EC2_INSTANCE
NOTE: applications can use localhost:1080 as SOCKS5 proxy, communication between laptop and the EC2 instance is encrypted and secure.Then in the already established SSH session (on the EC2 instance), open a tmux session and connect to port 22222 bind to the loopback address: ssh -p 22222 <user on host behind NAT>@localhost
If public key or host-based authentication is configured, you should be now into the Linux Server through the middle man ;-D
NOTE: In this case the SSH client (laptop) CANNOT directly access the Linux server (SSH port 22) behind firewall (no access to edge router or gateway to configure port forwarding). However, the Linux server can ssh to the EC2 instance (via NAT gateway) in the reverse direction. That's why we use the -R option.
IMPORTANT: This works fine as a PoC or temporary workaround but NOT suitable as highly-available, secure, reliable and scalable long term solution. Overlay network solutions like nebula or tailscale and VPN (IPsec using strongSwan, or WireGuard) are better solutions. See below.
There are new open source scalable global overlay network implementation available to solve mesh network (network mesh?) use cases, like Nebula (good!) and others like: Zerotier, Tailscale (WireGuard powered).
NOTE: Tinc works well in some cases.
OpenSSH Tips & Tricks
Some tips:
- Customise ~/.ssh/config to suite your needs (be careful with storm - manage ssh like a boss, it helps when scripting or searching hosts but has a outstanding bug converting keywords to lowercase [1]
- Use ed25519 key over RSA
- OpenSSH 8.1 added support for FIDO/U2F (use your YubiKey or equivalent)
- ~/.ssh/authorized_keys can do a lot more than just adding trusted keys' public bit, e.g. it can restrict source IPs by using from="pattern-list" like from="192.168.55.0/24,172.16.55.0/24,*.terry.im" nice, isn't it ;-) RTFM for more.
- leverage ssh-copy-id
- ssh -vvv | ssh -G (troubleshooting from client side)
- /usr/sbin/sshd -p 2222 -f /path/to/sshd_config -D -ddd (troubleshooting sshd server side)
- Be careful with `UsePAM no`, make sure the user account has a usable password (P), instead of L (locked password).
- use AllowUsers / DenyUsers vs DenyGroups vs AllowGroups , mind the order
- know how to use ssh-add / ssh-keygen / ssh-agent / ssh-keyscan
- audit SSH config (ssh-audit / lynis), version control ssh_config / sshd_config properly if possible
- openssh + tmux magic (share troubleshooting session or pair programming)
Personal favourite tips/tricks:
- ssh -D (used to use this dynamic port forwarding, open a local Socks5 proxy to punch hole in firewall, encrypt traffic, it worked for a while against the infamous GFW, only a little while though)
- ssh -L | -R TCP forwarding
- ssh -X | -Y X11 forwarding (run X11 apps remotely and display it on X Server locally)
- More personal SSH tricks put together over the years, surprise to find that my person OpenSSH notes are 150+ pages in Google Docs, sorry can't put all in a comment... [2]
- RTFM works, OpenSSH is worth the time ;-)
[1]: https://github.com/emre/storm/issues/157
HN post: https://news.ycombinator.com/item?id=23038264
Default IPQoS changed since OpenSSH 7.8p1
IMPORTANT: OpenSSH 7.8p1 introduced a change to default IPQoS which may break the SSH connection for people who runes rolling Linux distribution (e.g. Arch, Gentoo, etc.) or on macOS using homebrew installed openssh (why wouldn't they?).
Cause:
Client and server use different IPQoS for interactive traffic and bulk.
Example:
Client 8.1 using IPQoS af21 cs1
Server 7.6p1 using IPQoS lowdelay throughput
Symptom: client is able to connect to the server successfully but the terminal will stop responding within 5~10s. On the server side, it resets the connection without detailed error information (hard to troubleshoot and identify root cause).
ssh(1)/sshd(8): the default IPQoS used by ssh/sshd has changed. They will now use DSCP AF21 for interactive traffic and CS1 for bulk.For a detailed rationale, please see the commit message: https://cvsweb.openbsd.org/src/usr.bin/ssh/readconf.c#rev1.284
Solution
add into your ~/.ssh/config
Host *
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
ChallengeResponseAuthentication no
PubkeyAuthentication yes
HostKeyAlgorithms ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ssh-ed25519,ssh-rsa
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
Macs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com
ServerAliveInterval 30
VisualhostKey yes
HashKnownHosts no
IPQoS lowdelay throughput
Pass as opitons when running ssh command
ssh -o IPQoS="lowdelay throughput" user@host
# use -G to print out the configuration
Reference: https://www.openssh.com/releasenotes.html
sshuttle
GitHub repo: https://github.com/sshuttle/sshuttle
Docs: https://sshuttle.readthedocs.io/en/stable/usage.html
Someone calls it poor man's VPN (why poor?). I'd rather call it simple transparent proxy / VPN over SSH.
Usage
sshuttle -r username@sshserver 0.0.0.0/0
# DNS queries to be proxied as well
sshuttle --dns -r username@sshserver 0/0
Hardening OpenSSH
References
O’Reilly - SSH, The Secure Shell: The Definitive Guide