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:
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