{
  "$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"
}