The FreeBSD Diary

The FreeBSD Diary (TM)

Providing practical examples since 1998

If you buy from Amazon USA, please support us by using this link.
[ HOME | TOPICS | INDEX | WEB RESOURCES | BOOKS | CONTRIBUTE | SEARCH | FEEDBACK | FAQ | FORUMS ]
Putting sshd on a higher port 4 December 2006
Need more help on this topic? Click here
This article has 12 comments
Show me similar articles

ssh is the standard protocol used when you want to access a shell on a remote machine. By remote, I mean you are not sitting in front of the console. Remote may mean next door, the next building, or the next continent. ssh is a secure method for talking to that computer.

ssh is also a common attack vector. There are many scripts that can be used to attempt to break in via ssh. As a general rule, I greatly restrict access via my packet filter rules. I allow incoming connections only from my other servers, my home, and a few trusted hosts and friends. At worst, this reduces the number of spurious log-in attempts recorded in my logs. At best, it reduces the risk.

However, there are some instances when I am not at one of these trusted locations and I still need to ssh in. This is why I also run sshd, unfiltered, on a high random port. This article shows the configuration I used to achieve this.

NOTE: If all you want is sshd listening on another port, the configuration below is overkill. If that case, you probabaly want something like this:

# grep ListenAddress /etc/ssh/sshd_config
ListenAddress 10.2.3.4:22
ListenAddress 10.2.3.4:44444

However, if you want the second sshd to have a different configuration, such as only permit public key authorization, then this article is for you.

The startup script

Here is the startup script from /usr/local/etc/rc.d:

#!/bin/sh
#
# $NetBSD: sshd,v 1.18 2002/04/29 08:23:34 lukem Exp $
# $FreeBSD: src/etc/rc.d/sshd,v 1.8 2005/01/16 03:12:03 obrien Exp $
#

# PROVIDE: sshd
# REQUIRE: LOGIN cleanvar

. /etc/rc.subr

name="sshd_higher_port"
rcvar=`set_rcvar`
command="/usr/sbin/sshd"
keygen_cmd="sshd_keygen"
start_precmd="sshd_precmd"
pidfile="/var/run/${name}.pid"
extra_commands="keygen reload"

timeout=300

user_reseed()
{
	(
	seeded=`sysctl -n kern.random.sys.seeded 2>/dev/null`
	if [ "x${seeded}" != "x" ] && [ ${seeded} -eq 0 ] ; then
		warn "Setting entropy source to blocking mode."
		echo "===================================================="
		echo "Type a full screenful of random junk to unblock"
		echo "it and remember to finish with . This will"
		echo "timeout in ${timeout} seconds, but waiting for"
		echo "the timeout without typing junk may make the"
		echo "entropy source deliver predictable output."
		echo ""
		echo "Just hit  for fast+insecure startup."
		echo "===================================================="
		sysctl kern.random.sys.seeded=0 2>/dev/null
		read -t ${timeout} junk
		echo "${junk}" `sysctl -a` `date` > /dev/random
	fi
	)
}

sshd_keygen()
{
	(
	umask 022

	# Can't do anything if ssh is not installed
	[ -x /usr/bin/ssh-keygen ] || {
		warn "/usr/bin/ssh-keygen does not exist."
		return 1
	}

	if [ -f /etc/ssh/ssh_host_key ]; then
		echo "You already have an RSA host key" \
		    "in /etc/ssh/ssh_host_key"
		echo "Skipping protocol version 1 RSA Key Generation"
	else
		/usr/bin/ssh-keygen -t rsa1 -b 1024 \
		    -f /etc/ssh/ssh_host_key -N ''
	fi

	if [ -f /etc/ssh/ssh_host_dsa_key ]; then
		echo "You already have a DSA host key" \
		    "in /etc/ssh/ssh_host_dsa_key"
		echo "Skipping protocol version 2 DSA Key Generation"
	else
		/usr/bin/ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N ''
	fi

	if [ -f /etc/ssh/ssh_host_rsa_key ]; then
		echo "You already have a RSA host key" \
		    "in /etc/ssh/ssh_host_rsa_key"
		echo "Skipping protocol version 2 RSA Key Generation"
	else
		/usr/bin/ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N ''
	fi
	)
}

sshd_precmd()
{
	if [ ! -f /etc/ssh/ssh_host_key -o \
	    ! -f /etc/ssh/ssh_host_dsa_key -o \
	    ! -f /etc/ssh/ssh_host_rsa_key ]; then
		user_reseed
		run_rc_command keygen
	fi
}

load_rc_config $name
run_rc_command "$1"

This script is based entirely upon /etc/rc.d/sshd. The diff is here:

12c12
< name="sshd"
---
> name="sshd_higher_port"
14c14
< command="/usr/sbin/${name}"
---
> command="/usr/sbin/sshd"

The configuration

The truth is, I set up this daemon several months before I wrote this article. When it came time to write, it took me about 5 minutes to figure out where the configuration was done. Finally, I found it in /etc/rc.conf:

sshd_enable="YES"
sshd_higher_port_enable="YES"
sshd_higher_port_flags="-p 57328 -f /usr/local/etc/sshd_config_higher_port"
sshd_higher_port_program="/usr/sbin/sshd"

NOTE: you probably already have sshd_enable="YES" set. It is best not to duplicate settings.

NOTE: My testing shows you can omit the sshd_higher_port_program directive from /etc/rc.conf.

If memory serves, it took some time to figure out how to configure this properly and get it to run at boot time. I will explain the flag items:

  • -p 57328 : This is the port upon which the second ssh daemon will listen. No, this is not the port I am using.
  • -f /usr/local/etc/sshd_config_higher_port : this is the configuration file used by the second daemon

That second configuration file is pretty much identical to the original /etc/ssh/sshd_config. Here are the items I added to the new configuration file after copying it from the original:

Port                            57328
PasswordAuthentication          no
PidFile                         /var/run/sshd_higher_port.pid
ChallengeResponseAuthentication no

Looking at this, I see that the port number is specified in both /etc/rc.conf and in the configuration file. I've just implemented this on another server, without the /etc/rc.conf setting for the port, and it works fine.

The key point to this configuration is the second line. That completely disables login by password. This means that someone must have an ssh key to login. This setting completely eliminates any dictionary attacks (e.g. password guessing). The attacker *must* have your private ssh key in order to get in.

Firewall rules

Here is the new PF firewall rule I added:

pass in quick proto tcp from any to $MYSELF port 57328 flags S/SA synproxy state
Starting the daemon

This command started the daemon:

# /usr/local/etc/rc.d/sshd_higher_port start
Starting sshd_higher_port.

And here is what it looks like when running:

# ps auwx | grep sshd
root   871 0.0 0.2 3520 1836 ?? Is 9Nov06 0:02.59 /usr/sbin/sshd
root 89864 0.0 0.3 6244 2840 ?? Is 7:40AM 0:00.05 sshd: dan [priv] (sshd)
dan  89870 0.0 0.3 6224 2852 ?? S  7:41AM 0:00.36 sshd: dan@ttyp0 (sshd)
root 90687 0.0 0.3 3520 2584 ?? Ss 7:59AM 0:00.00 /usr/sbin/sshd -f 
                       /usr/local/etc/sshd_config_higher_port
#
Testing the theory

To test that passwords are not accepted, I did this:

$ ssh -p 57328 myserver.example.org
Enter passphrase for key '/home/dan/.ssh/id_dsa':
Permission denied (publickey).

At the prompt, I pressed enter. If passwords are accepted, I would have been presented with a password prompt, like this:

$ ssh xeon
Enter passphrase for key '/home/dan/.ssh/id_dsa':
Password:

Convenience versus risk

What I have done is a trade off between convenience and risk. It is more convenient to use the regular port (22). But it is annoying to get all the failed login attempts in my logs. The higher port is still wide open, but it is much less likely to get any login attempts.

What would you prefer to do? Please leave your comments.


Need more help on this topic? Click here
This article has 12 comments
Show me similar articles