Sunday, March 4, 2012

SSH/HTTP(S) multiplexing with sshttp

Sebastian Krahmer (@steaIth, c-skills) made and released a nice SSH/HTTP(S) multiplexer: sshttp. Such a program is needed when you want to share your HTTP (or HTTPS) port with SSH to be able to use SSH when behind a network that only allows outbound connections to HTTP (or HTTPS) and does not bother to do protocol inspection. Learn more by reading the readme.

I was previously using sslh but sshttp has a killer feature: it uses Linux IP_TRANSPARENT feature with netfilter trickery (marking + specific routing table) to pass the original IP to destination (sshd or httpd). Since it's a great program, not necessarily easy to set up (not yet? packaged), in this post I'm sharing my setup.


How does it work?

With HTTP the client speaks first (GET/POST/etc. request) whereas with SSH the server speaks first (banner). Similarly, HTTPS uses the SSL/TLS protocol where the client speaks first (ClientHello). Using this property and by waiting a little time, the multiplexer is able to determine whether the traffic should be sent to SSH server or HTTP/HTTPS server.

Setup

I have a main router/firewall in front of the LAN where the server with ssh/http/https is. I keep my webserver running on default ports (80/443), ssh as well (22). I want multiplexing on both http and https, so I'll have two daemons running on 2280 and 2244. Since sshttp requires an INPUT DROP on the ports where traffic is redirected, and I want to keep normal ssh access on port 22, I open a second port for ssh at 222 only for sshttp.
1
2
3
4
5
6
7
8
9
10
[HTTP]
           _____               ______
 port 80  |     |  port 2280  |      |
--------> | NAT | ----------> | serv | sshttp listening on 2280
          |_____|             |______|            ||
                                            /- multiplex -\
                                           /               \
                                    http server        ssh server
                                  listening on 80   listening on 222
  
1
2
3
4
5
6
7
8
9
10
[HTTPS]
           _____               ______
port 443  |     |  port 2244  |      |
--------> | NAT | ----------> | serv | sshttps listening on 2244
          |_____|             |______|            ||
                                            /- multiplex -\
                                           /               \
                                   https server        ssh server
                                 listening on 443   listening on 222
  

Installation

Compile from source on github:
1
2
3
4
5
6
$ cd /usr/local/src
$ git clone https://github.com/stealth/sshttp
$ cd sshttp
$ make
$ cp sshttpd /usr/local/sbin/sshttp
$ cp sshttpd /usr/local/sbin/sshttps
Note: different file names and no hardlink or start-stop-daemon is unhappy...

Add dedicated user accounts for the two daemons:
1
2
3
4
$ adduser --system --ingroup nogroup \
  --home /var/run/sshttp --no-create-home sshttp
$ adduser --system --ingroup nogroup \
  --home /var/run/sshttps --no-create-home sshttps

Debian-like init scripts /etc/init.d/sshttp and /etc/init.d/sshttps:
1
2
3
$ cd /etc/init.d
$ wget https://raw.github.com/StalkR/misc/master/sshttp/etc/init.d/sshttp
$ wget https://raw.github.com/StalkR/misc/master/sshttp/etc/init.d/sshttps
Install them with the new dependency-based init:
1
2
$ insserv sshttp
$ insserv sshttps
Or old-school:
1
2
$ update-rc.d sshttp defaults
$ update-rc.d sshttps defaults

Configuration

Default configuration for both /etc/default/sshttp /etc/default/sshttps:
1
2
3
$ cd /etc/default
$ wget https://raw.github.com/StalkR/misc/master/sshttp/etc/default/sshttp
$ wget https://raw.github.com/StalkR/misc/master/sshttp/etc/default/sshttps
It's simple:
1
2
3
4
5
6
$ cat /etc/default/sshttp
SSH_PORT=222
HTTP_PORT=80
LISTEN_PORT=2280
USER=sshttp
CHROOT=/var/run/sshttp
1
2
3
4
5
6
$ cat /etc/default/sshttps
SSH_PORT=222
HTTP_PORT=443
LISTEN_PORT=2244
USER=sshttps
CHROOT=/var/run/sshttps

Next is to configure iptables, marking and routing. I use ferm (for Easy Rule Making) as a frontend to iptables and I highly recommend it. As long as you are familiar with iptables, it gives you the power to do a complex set of rules easily and ships with an easy init script to safely start/stop/reload the firewall. Much better in so many ways than those manual iptables.sh scripts.

So here are the sshttp rules to integrate with your /etc/init.d/ferm.conf. Note the hooks at the beginning to add the marking rule (with sh+true to avoid ferm failing if one of the commands does not exit with 0) and local routing table when marked.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@hook pre "sh -c 'ip rule del fwmark 22 lookup 22 2>/dev/null; true'";
@hook pre "ip rule add fwmark 22 lookup 22";
@hook pre "sh -c 'ip route add local 0.0.0.0/0 dev lo table 22 2>/dev/null; true'";
 
domain ip {
  table filter {
    chain INPUT {
      proto tcp dport (80 443 222) DROP;
      proto tcp dport (2280 2244) ACCEPT;
    }
  }
  table mangle {
    chain OUTPUT {
      proto tcp outerface eth0 sport (80 443 222) jump SSHTTP;
    }
    chain PREROUTING {
      proto tcp mod socket jump SSHTTP;
    }
    chain SSHTTP {
      MARK set-mark 22;
      ACCEPT;
    }
  }
}
  

Finally, open the sshttp dedicated ssh port by adding in /etc/ssh/sshd_config:
1
Port 222

Run

Reload firewall, ssh and start multiplexers:
1
2
3
4
$ invoke-rc.d ferm reload
$ invoke-rc.d ssh reload
$ invoke-rc.d sshttp start
$ invoke-rc.d sshttps start

If I did not forget anything, you should be ready to use your http/https ports with ssh and still have your webserver running and serving smoothly. Enjoy! 

No comments:

Post a Comment