Multiplex SSH and HTTPS on a single port

I want to allow both SSH and HTTPS on port 443 of my server, because port 22 is often blocked by firewalls. The usual tool for this task is the excellent sslh tool, which can recognize SSH and HTTPS connections, but also HTTP, OpenVPN, tinc, and XMPP! Besides, sslh does not rely only on the “who speaks first, server or client?” technique, which makes it compatible with more SSH clients; an excellent port multiplexer indeed!

There is one drawback, though: sslh listens to a port on the server, receives an incoming connection from a remote client, detects the protocol, and then forwards packets for this connection to the adequate service; the problem is that the latter is seeing packets coming from the server itself (usually localhost), not from the IP address of the remote client.

SSH is a very powerful tool, and is thus attacked a lot, with hundreds of connection attempts, even for the smallest and most unknown server… As a consequence, it is very important to protect the server with tools like DenyHosts or Fail2Ban, that detect failed connection attempts, and restrict further connections from the culprits. However, for SSH connections through sslh, the remote host appears to be localhost! Which leaves me with two imperfect options: either let localhost be blocked for periods of time (during which sslh becomes pointless…), or white-list localhost, thus removing any kind of protection against SSH brute-force attacks on port 443

Not any more; I just discovered sshttp :-) This post is all about this tool.

Unlike sslh, sshttp comes with scripts that configure the firewall so that each service sees the right remote IP address and port for each connection; sshttp thus becomes completely transparent!

sshttp needs exclusive access to the ports it redirects to, but I want my web and SSH servers to keep operating as usual on the LAN. So I made the web server listen to ports 443 and 1443, and made the SSH server listen to ports 22 and 1022. I aim at the following configuration:

Normal SSH and HTTPS connections from both Internet and LAN on ports 22 and 443 respectively, plus possibility to connect from the Internet with SSH on port 443, which is linked on the router to server port 444, on which sshttp is running and forwarding to ports 1022 and 1443.

So I first download sshttp. From here on, I use the # prompt to indicate a command run as user root (or with sudo), and the $ prompt to indicate a command run as a normal user.

$ git clone https://github.com/stealth/sshttp
$ cd sshttp

In the Makefile file, there are two lines I need to change: the SMTP_DOMAIN and SSH_BANNER variables. The first one is self-explanatory. As for the second one, I can get the right value by running telnet on my server (ENTER key to exit):

$ telnet localhost 22
Trying ::1...
Connected to localhost.
Escape character is '^]'.
SSH-2.0-OpenSSH_6.0p1 Debian-4

Protocol mismatch.
Connection closed by foreign host.

I highlighted above the line that should be put into the SSH_BANNER variable. Spaces, just like quotes, must be escaped; in my case, I get:

SSH_BANNER=-DSSH_BANNER=\"SSH-2.0-OpenSSH_6.0p1\ Debian-4\"

Compilation is straightforward; no options, no switches:

$ make

No installation method is provided, and there are very few files to install anyway. You could for instance put all needed files together inside a subdirectory of /opt. I chose instead to install things in the most standard way possible, and drew my inspiration from a blog post by Sameh Attia.

First I create a configuration file named default, with this contents:

DEV=eth0
SSH_PORT=1022
HTTP_PORT=1443
LISTEN_PORT=444
USER=nobody
CHROOT=/run/sshttp
INIT_FW=/usr/local/sbin/init_fw.sh

DEV is the internet-facing networking device. SSH_PORT, HTTP_PORT, and LISTEN_POST are the SSH and HTTPS ports exclusively dedicated to sshttp, as well as sshttp’s own port. USER is the user running the process; nobody is fine. CHROOT is the directory to which the process will be “chroot’ed” to; no need to create it. INIT_FW is the path to a script to be run in order to reset the firewall to its initial configuration, whenever sshttp is stopped.

Then I create an initialization script loosely based on the one from StalkR, in a file named init:

#! /bin/sh
### BEGIN INIT INFO
# Provides: sshttp
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: sshttp SSH/HTTPS multiplexer
# Description: sshttp SSH/HTTPS multiplexer
### END INIT INFO

# Author: StalkR <stalkr@stalkr.net>

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="sshttp SSH/HTTPS multiplexer"
NAME=sshttp
DAEMON=/usr/local/sbin/$NAME
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

DAEMON_ARGS="-S $SSH_PORT -H $HTTP_PORT -L $LISTEN_PORT -U $USER -R $CHROOT"
[ -d $CHROOT ] || mkdir -p $CHROOT

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
${DAEMON}-nf-setup
${DAEMON}-nf6-setup
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
start-stop-daemon --start --quiet --exec $DAEMON --test > /dev/null \
|| return 1
start-stop-daemon --start --quiet --exec $DAEMON -- \
$DAEMON_ARGS \
|| return 2
}

#
# Function that stops the daemon/service
#
do_stop()
{
[ -n "$INIT_FW" ] && $INIT_FW
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --quiet --exec $DAEMON --retry=TERM/30/KILL/5 --name $NAME
return "$?"
}

case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac

There is not much to change in this file; there is only this one line, in bold above, where you may want to change the path to the binaries, most notably in case you decide to create a proper package for sshttp.

Next, I edit the nf-setup and nf6-setup files that come with sshttp, and replace the lines where variables are declared, with this single statement (do not forget the leading dot and space):

. /etc/default/sshttp

Finally, as root, I run the following commands:

# cp sshttpd /usr/local/sbin/sshttp
# cp nf-setup /usr/local/sbin/sshttp-nf-setup
# cp nf6-setup /usr/local/sbin/sshttp-nf6-setup
# cp init /etc/init.d/sshttp
# cp default /etc/default/sshttp
# chown root:root /usr/local/sbin/sshttp* /etc/init.d/sshttp /etc/default/sshttp
# chmod 555 /usr/local/sbin/sshttp
# chmod 500 /usr/local/sbin/sshttp-*
# chmod 744 /etc/init.d/sshttp /etc/default/sshttp
# grep -qx xt_TPROXY /etc/modules || echo xt_TPROXY >>/etc/modules
# grep -qx nf_tproxy_core /etc/modules || echo nf_tproxy_core >>/etc/modules
# modprobe xt_TPROXY
# modprobe nf_tproxy_core

The last four lines ensure that the proper kernel modules get loaded on boot (former two lines), and that these kernel modules get loaded now (latter two lines).

Installation is done. Configuration can be changed with the /etc/default/sshttp file, and the service can be started and stopped using the /etc/init.d/sshttp initialization script.

To permanently enable sshttp on Debian, simply run:

update-rc.d sshttp defaults

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.

La discussion continue ailleurs

URL de rétrolien : http://yalis.fr/cms/index.php/trackback/45

Fil des commentaires de ce billet