Bypassing deep packet inspection with socat and HTTPS tunnels
Recently I found myself with a few hours to kill, but with the only
available connectivity provided by an annoying firewall which would
normally allow requests only to a few very specific web sites.
This post shows how to work around this kind of restrictions by hiding
SSH in an HTTPS connection, which then can be used as a SOCKS proxy to
allow general connectivity.
socat
does all the hard work.
First, create two self-signed RSA keys pairs, one for the client (bongo) and one for the server (attila):
domain=bongo.example.net openssl req -x509 -newkey rsa:2048 -days 7300 \ -subj /CN=$domain -addext "subjectAltName = DNS:$domain" \ -keyout socat.key -nodes \ -out socat.pem
Then, concatenate the public and private keys to create the file
provided to the cert option, and use the public key as
the file for the cafile option on the other
side.
On the client side, if you normally would connect to
attila.example.net then you can add something like this to
~/.ssh/config:
Host httpstunnel-attila.example.net
ProxyCommand socat --statistics STDIO OPENSSL:attila.example.net:443,↩️
cert=$HOME/.ssh/socat-bongo.pem,cafile=$HOME/.ssh/socat-attila.pem,↩️
snihost=${SOCAT_SNI:-x.com}
DynamicForward 1080
Compression yes
HostKeyAlias attila.example.net
ControlMaster yes
ControlPath ~/.ssh/.control_attila.example.net_22_%r
The ProxyCommand directive uses socat to
provide the connectivity which ssh will use over stdio
instead of connecting to port 22 of the server.
The snihost option is enough to make many firewalls
believe that this is an authorized HTTPS request.
On the server side we use a simple systemd unit to start a forking
instance of socat, which will accept and process requests
from the client (and from random crawlers on the Internet: expect a lot
of cruft in that log...):
[Unit] Description=socat tunnel After=network.target [Service] Type=exec ExecStart=socat -ly OPENSSL-LISTEN:443,fork,reuseaddr,↩️ cert=%d/tlskey,cafile=%d/tlsca TCP:localhost:22 SuccessExitStatus=143 LoadCredential=tlskey:/etc/ssh/socat-attila.pem LoadCredential=tlsca:/etc/ssh/socat-bongo.pem Restart=on-abnormal RestartSec=5s DynamicUser=yes PrivateDevices=yes PrivateTmp=yes ProtectClock=yes ProtectControlGroups=yes ProtectHome=yes ProtectHostname=yes ProtectKernelLogs=yes ProtectKernelModules=yes ProtectKernelTunables=yes ProtectProc=invisible ProtectSystem=strict RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX RestrictNamespaces=yes RestrictRealtime=yes RestrictSUIDSGID=yes LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE SystemCallArchitectures=native SystemCallErrorNumber=EPERM SystemCallFilter=@system-service SystemCallFilter=~@resources SystemCallFilter=~@privileged [Install] WantedBy=multi-user.target
Strong sandboxing is enabled, so the socat instance
is confined with very limited privileges. An interesting point is the
use of systemd credentials
to provide the cryptographic keys, since it allows to store them in a
part of the file system which would not be accessible to the program.
Advanced users can use this method to provide the keys from secure
storage.