SSH over SSL, episode 4: a HAproxy based configuration

Purpose of this article

In this article, I am describing how to SSH to a remote server as discreetly as possible, by concealing the SSH packets into SSL. The server will still be able to run an SSL website.


In most cases, when your outgoing firewall blocks ssh, you can work around with sslh, a tool that listens on the port 443 server-side, and selectively forwards, depending on the packet type, the incoming TCP connections to a local SSH or SSL service. You can then happily ssh to your server on the port 443 (normally dedicated to HTTPS), and also run a website on the same server so your connections look you are just harmlessly visiting this website. However, if your firewall is really sneaky, it will detect that you are sending the wrong packet type to the SSL port, and block your connection. In this case, there is not much choice: we must hide the SSH connection into a real SSL tunnel.

Comment for the long time readers

I know, I know: I covered this topic a few times already (here are the first, second, and third episodes). All of these setups were relying on a feature of HTTP 1.1 called CONNECT. However, it turns out that most webserver do not implement this CONNECT feature. As a consequence, if you wanted to do this, you were more or less stuck with Apache. This time, we are breaking free from Apache, with a HAproxy-based configuration. We will use HAproxy advanced packet inspection capabilities to implement a switch of protocol, the same way sslh works.

Server configuration

Some assumptions:

Now, you need to setup HAproxy. HAproxy defines backends and frontends, and it can communicate with these backends both at the HTTP and at the TCP level. Let us start with the backends:

The web server backend: we tell HAproxy that a server is running on the port 80, and speaks HTTP. On this backend, we add a X-Forwarded-Proto header, such that the web server knows that the clients are connecting securely. If you expose the same backend with HAproxy on the port 80, don't forget to filter the X-Forwarded-Proto header!

backend secure_http
    reqadd X-Forwarded-Proto:\ https
    rspadd Strict-Transport-Security:\ max-age=31536000
    mode http
    option httplog
    option forwardfor
    server local_http_server

The ssh server:

backend ssh
    mode tcp
    option tcplog
    server ssh
    timeout server 2h

And now, the magic. This happens in the frontend section. We listen in TCP mode and inspect the connections. Depending on whether we see ssh or not, we hook it to one of the backends.

frontend ssl
    bind X.X.X.X:443 ssl crt /etc/ssl/private/certs.pem no-sslv3
    mode tcp
    option tcplog
    tcp-request inspect-delay 5s
    tcp-request content accept if HTTP

    acl client_attempts_ssh payload(0,7) -m bin 5353482d322e30

    use_backend ssh if !HTTP
    use_backend ssh if client_attempts_ssh
    use_backend secure_http if HTTP

Once you are done, you can test if this works by connecting on the server with openssl.

openssl s_client -connect -quiet

If you see a string that looks like

SSH-2.0-OpenSSH_6.6.1p1 Debian-7

then everything went fine!

Connecting from an SSH client

To connect to your server from linux, just drop this in your ~/.ssh/config:

    ProxyCommand openssl s_client -connect -quiet

If you are on windows and you cannot install anything client side, there is also a solution for you. Download socat and putty (none of them requires admin rights). Then, with socat, run:

socat TCP-LISTEN:8888,verify=0

And with putty, direct your client to on the port 8888.

For the technically aware readers

So how does this work exactly? Basically, the RFC 4253, section 4.2 states that clients must send a string that starts with 'SSH-2.0' (this is also how sslh does it). Moreover, 5353482d322e30 is the binary representation of the string 'SSH-2.0'. So everything boils down to this line:

acl client_attempts_ssh payload(0,7) -m bin 5353482d322e30

When a new connection is made on the port 443, HAproxy decrypts the SSL layer, and checks whether the stream of data sent by the client starts with this string. We use the result of this condition to choose the backend. This handles the case of 'active' SSH clients (like openssh-client on linux), who send a packet as soon as they connect. There are also 'passive' SSH clients (like putty), who wait for the server to send a string. These clients will get that string after 5 seconds (the inspect-delay).


Happy SSH!

To the comments

All posts

  1. Editing a CV in markdown with pandoc
  2. Using openid and the likes to protect static content (lighttpd)
  3. Git on lighttpd
  4. Sigal, a static gallery generator
  5. Jabber notifications on ssh login
  6. Choose your passphrase with a die
  7. Operations Research and Beer drinking
  8. Releasing Michel, a flat-text-file-to-google-tasks uploader
  9. Going static
  10. plowbot, a jabber bot that downloads links from 1-click hosters
  11. SSH over SSL, episode 3: Avoiding using a patched apache.
  12. [Je préfère ton clone] padopi
  13. Using a shell version of supergenpass from vimperator/pentadactyl
  14. Saving your crontab in your dotfiles
  15. Notifications from google calendar on my desktop
  16. SSH over SSL, episode 2: replacing proxytunnel with socat
  17. SSH over SSL, a quick and minimal config.
  18. Vim: complete C++ accurately, pulling informations from the compiler, with gccsense and clang_complete
  19. Google releasing a constraint programming library
  20. Mise à jour de TalkMyPhone
  21. TalkMyPhone, une appli android pour recevoir des notifications de son téléphone
  22. De l'intérêt de détacher des programmes de la console (sans screen)
  23. renaming files and variables from vim
  24. Continuous background compilation within vim
  25. Utilisons incron pour être notifiés des événements du système de fichiers
  26. La TODO liste du pauvre
  27. Gérer ses plugins vim avec :GetLatestVimScripts
  28. gdb 7.0 est sorti, c'est une merveille et vous ne le saviez pas.
  29. autotools, doxygen, et génération conditionnelle
  30. Mettre des couleurs un peu partout (gcc, diff, grep...)
  31. vim+gdb=vimgdb
  32. l'UML automatisé et le libre : c'est pas gagné!
  33. Les lecteurs de flux rss, en ligne, indépendants, libres (suite).
  34. Les lecteurs de flux rss en ligne libres
  35. Couper une vidéo et extraire une scène d'un film
  36. Faire un gif animé à partir d'un film

Atom feed