{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreihbbbgmuxr2xlc74aq7gnlvbf7oaxrstjtnk7pr4g4iecfpmvrer4",
"uri": "at://did:plc:46ti67tc37qcmwp2vaynk6fq/app.bsky.feed.post/3mh5jdk6gcr72"
},
"path": "/debian/id_472",
"publishedAt": "2026-03-16T03:29:19.830Z",
"site": "https://blog.bofh.it",
"tags": [
"`socat`",
"systemd credentials",
"@system-service",
"@resources",
"@privileged"
],
"textContent": "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.\n\nFirst, create two self-signed RSA keys pairs, one for the client (bongo) and one for the server (attila):\n\n\n domain=bongo.example.net\n openssl req -x509 -newkey rsa:2048 -days 7300 \\\n -subj /CN=$domain -addext \"subjectAltName = DNS:$domain\" \\\n -keyout socat.key -nodes \\\n -out socat.pem\n\n\nThen, 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**.\n\nOn the client side, if you normally would connect to `attila.example.net` then you can add something like this to `~/.ssh/config`:\n\n\n Host httpstunnel-attila.example.net\n ProxyCommand socat --statistics STDIO OPENSSL:attila.example.net:443,↩️\n cert=$HOME/.ssh/socat-bongo.pem,cafile=$HOME/.ssh/socat-attila.pem,↩️\n snihost=${SOCAT_SNI:-x.com}\n DynamicForward 1080\n Compression yes\n HostKeyAlias attila.example.net\n ControlMaster yes\n ControlPath ~/.ssh/.control_attila.example.net_22_%r\n\n\nThe `ProxyCommand` directive uses `socat` to provide the connectivity which `ssh` will use over stdio instead of connecting to port 22 of the server.\n\nThe `snihost` option is enough to make many firewalls believe that this is an authorized HTTPS request.\n\nOn 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...):\n\n\n [Unit]\n Description=socat tunnel\n After=network.target\n\n [Service]\n Type=exec\n ExecStart=socat -ly OPENSSL-LISTEN:443,fork,reuseaddr,↩️\n cert=%d/tlskey,cafile=%d/tlsca TCP:localhost:22\n SuccessExitStatus=143\n LoadCredential=tlskey:/etc/ssh/socat-attila.pem\n LoadCredential=tlsca:/etc/ssh/socat-bongo.pem\n Restart=on-abnormal\n RestartSec=5s\n DynamicUser=yes\n PrivateDevices=yes\n PrivateTmp=yes\n ProtectClock=yes\n ProtectControlGroups=yes\n ProtectHome=yes\n ProtectHostname=yes\n ProtectKernelLogs=yes\n ProtectKernelModules=yes\n ProtectKernelTunables=yes\n ProtectProc=invisible\n ProtectSystem=strict\n RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX\n RestrictNamespaces=yes\n RestrictRealtime=yes\n RestrictSUIDSGID=yes\n LockPersonality=yes\n MemoryDenyWriteExecute=yes\n NoNewPrivileges=yes\n AmbientCapabilities=CAP_NET_BIND_SERVICE\n CapabilityBoundingSet=CAP_NET_BIND_SERVICE\n SystemCallArchitectures=native\n SystemCallErrorNumber=EPERM\n SystemCallFilter=@system-service\n SystemCallFilter=~@resources\n SystemCallFilter=~@privileged\n\n [Install]\n WantedBy=multi-user.target\n\n\nStrong 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.",
"title": "Marco d'Itri: Bypassing deep packet inspection with socat and HTTPS tunnels",
"updatedAt": "2026-03-15T13:45:31.000Z"
}