Further Securing OpenSuSE 11.1 Against SSH Script Attacks | SUSE Communities

Further Securing OpenSuSE 11.1 Against SSH Script Attacks



0. The Obvious
1. Securing OpenSSH
2. Initial Firewall Configuration
3. SSHBlacklist
4. Integrating SSHBlack in to SuSEFirewall2
5. Checkpoint and Restart


As initially installed, an OpenSuSE 11.1 machine is reasonably secure. The SuSE firewall is installed and activated, which blocks SSH access to the machine. This is secure, but not very manageable. Most experienced Linux admins will want to be able to use SSH for remote access to the OS.

In the base installation, the OpenSSH daemon (sshd) is installed and running, so the only thing preventing access to it is the SuSE firewall.

0. The Obvious

The obvious thing to do is to go in to YaST, configure the firewall, and permit access to the SSH daemon. Save the change, the firewall is rebuilt with the new rules, and voila, SSH works. This is great, except for a couple of problems.

Problem #1: The default configuration of the OpenSSH daemon is less secure than it should be.

Problem #2: The world is full of "script kiddies" that will find your server and attack it.

Assuming that you have a machine that is on a publicly accessible IP network, you can watch the SSH attacks come in because the OpenSSH daemon logs them to /var/log/messages. Assuming you have Really Good Passwords, it can be instructive and entertaining to watch what the kiddies are trying. If your machine is not publicly accessible, or you are just in a hurry, try to log in via SSH from some other machine. Using invalid accounts and invalid passwords will show you what log messages are produced.

1. Securing OpenSSH

Solving the first problem is just a matter of tightening up the OpenSSH daemon’s configuration. This is found in the /etc/ssh/sshd_config file. As root, edit this file, and find the line that says:

      PermitRootLogin yes

Change this to:

      PermitRootLogin no

What this setting does is tell the OpenSSH daemon that the root user is not allowed to log in via SSH. The default, "yes", allows the script kiddies to pound on login by root and try to guess the right password. By changing this to "no", the kiddies can pound away, but root will never be allowed to log in.

Second, find the line that says:

      Port 22

Change this to some other value. Pick any port number that is not already in use for some application hosted on this server. Use something unusual, like 224, or 75, or 400. So:

      Port 75

What this setting does is tell the OpenSSH daemon what port number to listen on. The normal port for SSH is 22. Everybody knows this is the default port. So everything that might normally try to connect via SSH is going to use port 22. This includes the script kiddies’ scripts. So by changing to a port other than 22, 95% of the script attacks will fail, simply because they’re trying to connect to a port that your SSH daemon is not listening to.

These two changes are minor, but surprisingly effective.

Save the ssh_config file and restart the SSH daemon (/etc/init.d/sshd restart).

Now try to connect to your machine from some other machine via SSH:

  ssh -p 75 bob@mynewhost.company.com

It fails to connect, because the SuSE Firewall is blocking access to port 75.

2. Initial Firewall Configuration

In YaST, edit the firewall configuration. Remove the SSH port (22) that you added earlier, and allow the port that you just changed for the SSH daemon (75 in this example). Save and exit YaST.

Repeat the SSH connection test. It should work now.

These simple changes will remove almost all of the scripted attacks on SSH, because they are going to be trying port 22. A somewhat more sophisticated attack will attempt to determine what ports are available, then will attempt to figure out what is listening on those ports, in order to attack it correctly. These are exactly the attacks that you want to block, because they will not just be attempting to guess a valid account/password combination by brute force.

3. SSHBlacklist

There are several methods available to thwart these more intelligent attacks. After looking in to them, I settled on this one. I like the way it works, I like what it does, and I like the way it does it with a minimum of intrusion on an already running system.

SSHBlacklist runs in the background as a daemon and watches whatever log file you tell it to for signs of an ongoing SSH attack. When it sees enough evidence that something untoward is going on, it blocks the offending IP address for a period of time. As far as the attacker is concerned, your machine just ceases to exist.

Download the latest version from the author’s web site (http://www.pettingers.org/code/sshblack.html).

Before continuing, read the documentation that comes with the SSHBlacklist package. Really. Now.

Unpack SSHBlacklist in to /usr/local/sbin/sshblack.

Edit the sshblack.pl script and make the following changes:

    my($LOG) = '/var/log/messages';

This parameter specifies which log file to watch for messages. Some other Linux distributions have a /var/log/security where those sorts of things will show up. For some reason, SuSE puts these messages in the system log (/var/log/messages), so you have to configure the SSHBlacklist script to look there.

my($ADDRULE) = '/usr/sbin/iptables -I BLACKLIST -s ipaddress -j DROP';

This parameter specifies how SSHBlacklist will add blocked hosts to the iptables chain that blocks them. Make sure that the path to the iptables binary is correct. Some other Linux distributions put it in /sbin, but SuSE puts iptables in /usr/sbin.

my($DELRULE) = '/usr/sbin/iptables -D BLACKLIST -s ipaddress -j DROP';

This parameter is the inverse of the ADDRULE, above. It specifies how SSHBlacklist will remove a formerly blocked host from the iptables chain, once the timeout period has expired.

my($REASONS) = '(Invalid user|PAM: Authentication failure|Did not receive identification string|POSSIBLE BREAK IN ATTEMPT)';

In Step 0, we saw the types of messages in /var/log/messages that we want SSHBlacklist to watch for and respond to. These are working for me, but you may need to modify the list to reflect what you see on your system.

my($MAXHITS) = 5;

This parameter specifies the number of failed attempts SSHBlacklist should allow before blocking the offending host. 5 is a good number. You can lower it if you want. But if you tend to fumble finger your own login from time to time, you could block yourself for a while.

Save the changes.

Create iptables chain:

    iptables -N BLACKLIST

Route packets through it:

    iptables -I INPUT 3 -p tcp --dport {port number} -j BLACKLIST

Start /usr/local/sbin/sshblack.pl script

Test …

Use “tail -F /var/log/sshblacklisting” to watch SSHBlacklist’s activity. From another machine, attempt to connect to this one with SSH. Use various bad user names and passwords until you see SSHBlacklist detect and block access from the other machine. After this, attempts to connect will be blocked.

Wait for the timeout period to expire, after which you can attempt to connect via SSH again and it should work.

4. Integrating SSHBlack in to SuSEFirewall2

This is all well and good, except that you have to start it manually. The SuSEfirewall2 startup scripts called by init.d destroy and rebuild the iptables configuration each time the machine is booted, and each time that a network interface comes up. On a server that stays up for weeks or months at a time, this might be ok, but on something like a laptop that gets booted and activates / deactivates a wireless network connection regularly, this gets to be annoying. So I set off to find out how to integrate SSHBlacklist with the SuSEfirewall2 scripts.

The answer comes in two parts. As with setting it up manually for testing, you have to create the iptables chains, and you have to start the sshblack.pl daemon script to use them.

The first part, creating the iptables chains, is done from within the custom hooks section of the SuSEfirewall2 scripts. To set this up:

  1. Edit /etc/sysconfig/scripts/SuSEfirewall2-custom

    Insert lines to create BLACKLIST chain and insert in to INPUT processing here

    fw_custom_before_antispoofing() {
        # these rules will be loaded before any anti spoofing rules will be
        # loaded. Effectively the only filter lists already effective are
        # 1) allow any traffic via the loopback interface, 2) allow DHCP stuff,
        # 3) allow SAMBA stuff [2 and 3 only if FW_SERVICE_... are set to "yes"]
        # You can use this hook to prevent logging of uninteresting broadcast
        # packets or to allow certain packet through the anti-spoofing mechanism.
    #example: allow incoming multicast packets for any routing protocol
    #iptables -A INPUT -j ACCEPT -d
            iptables -N BLACKLIST
            iptables -I INPUT 3 -p tcp --dport 444 -j BLACKLIST
  2. Edit /etc/sysconfig/SuSEfirewall2

    Comment out FW_CUSTOM_RULES=””
    Uncomment FW_CUSTOM_RULES=”/etc/sysconfig/scripts/SuSEfirewall2-custom”

  3. Edit /sbin/SuSEfirewall2

    Comment out FW_CUSTOM_RULES=””

    Now, when SuSEfirewall2 starts or rebuilds the iptables configuration, it will also build your BLACKLIST chain and will link it in to the INPUT chain.

    Second, to start the sshblack.pl daemon script:

    Startup script for init.d (/etc/init.d/sshblack)

    #       /etc/rc.d/init.d/sshblack
    # Controls the sshblack.pl sshd breakin attempt monitoring script
    # Provides:            sshblack
    # Required-Start:      $local_fs $network $syslog SuSEfirewall2_setup sshd $ALL
    # Required-Stop:       $network $syslog SuSEfirewall2_setup sshd
    # Should-Start:
    # Should-Stop:
    # Default-Start:       3 5
    # Default-Stop:        0 1 2 6
    # Short-Description:   SSH Blacklist daemon
    # Description:         Start / stop sshblacklist daemon
    # chkconfig:  345 86 14
    # description: SSH Black monitors ssh connections for attacks
    # processname: sshblack
    # pidfile: /var/run/sshblack.pid
    #              :   :  :
    #              |   |  |
    #              |   |  priority for kill scripts
    #              |   |
    #              |   priority for start scripts
    #              |
    #              run levels at which to start service
    # The code in this script adapted from /etc/init.d/atd on my RHEL4-derived system,
    # with some additional clues from [http://www.pettingers.org/media/sshblackinit.txt]
    # See also: http://www.netadmintools.com/art94.html
    # Original script source: http://wiki.oss-watch.ac.uk/InstallingSshblack
    # Modifications for OpenSuSE by David Gersic (dgersic@niu.edu) 4 May 2009
    # Shell functions sourced from /etc/rc.status:
    #      rc_check         check and set local and overall rc status
    #      rc_status        check and set local and overall rc status
    #      rc_status -v     be verbose in local rc status and clear it afterwards
    #      rc_status -v -r  ditto and clear both the local and overall rc status
    #      rc_status -s     display "skipped" and exit with status 3
    #      rc_status -u     display "unused" and exit with status 3
    #      rc_failed        set local and overall rc status to failed
    #      rc_failed   set local and overall rc status to 
    #      rc_reset         clear both the local and overall rc status
    #      rc_exit          exit appropriate to overall rc status
    #      rc_active        checks whether a service is activated by symlinks
    #      rc_splash arg    sets the boot splash screen to arg (if active)
    . /etc/rc.status
    # Reset status of this service
    # Return values acc. to LSB for all commands but status:
    # 0       - success
    # 1       - generic or unspecified error
    # 2       - invalid or excess argument(s)
    # 3       - unimplemented feature (e.g. "reload")
    # 4       - user had insufficient privileges
    # 5       - program is not installed
    # 6       - program is not configured
    # 7       - program is not running
    # 8--199  - reserved (8--99 LSB, 100--149 distrib, 150--199 appl)
    # Note that starting an already running service, stopping
    # or restarting a not-running service as well as the restart
    # with force-reload (in case signaling is not supported) are
    # considered a success.
    export PATH="/sbin:/bin:/usr/sbin:/usr/bin"
    # echo ${prog}
    test -x ${prog} || exit 0
    #       See how we were called.
    start() {
    	# Sleep - gives time for SuSE Firewall to get itself up and running
    	echo "Waiting for SuSE Firewall..."
    	sleep 30
            # Create firewall table for blacklist (in case it got lost)
            if iptables -L INPUT | grep BLACKLIST >/dev/null
                # Blacklist already configured
    		echo "Blacklist chain already configured in iptables."
                # Blacklist missing.
    		echo "Blacklist chain not configured in iptables."
                iptables -N BLACKLIST
                iptables -I INPUT 3 -p tcp --dport ${sshport} -j BLACKLIST
                # Remove any old blacklist cache
                # (if iptables is reset without clearing this, previously started attacks
                # may be allowed through)
                rm -f /var/tmp/ssh-blacklist-pending
            # Check if prog is already running
            if [ ! -f /var/lock/subsys/${progname} ]; then
                echo -n $"Starting ${progname}: "
                ${prog} &
                if [ $RETVAL -eq 0 ]; then
                    touch /var/lock/subsys/${progname}
    		rc_status -v
    		rc_status -v
            return $RETVAL
    stop() {
            echo -n $"Stopping $progname: "
            killproc ${prog}
            [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/${progname}
    	rc_status -v
            return $RETVAL
    restart() {
    reload() {
    status_prog() {
            echo -n "Checking for $progname: "
            ## Check status with checkproc(8), if process is running
            ## checkproc will return with exit status 0.
            # Status has a slightly different for the status command:
            # 0 - service running
            # 1 - service dead, but /var/run/  pid  file exists
            # 2 - service dead, but /var/lock/ lock file exists
            # 3 - service not running
            # NOTE: checkproc returns LSB compliant status values.
    	checkproc ${prog}
            rc_status -v
    case "$1" in
            if [ -f /var/lock/subsys/${progname} ]; then
            [ -f /var/lock/subsys/${progname} ] && rm -f /var/lock/subsys/${progname}
            echo $"Usage: $0 {start|stop|restart|condrestart|status}"
            exit 1
    exit $?
    exit $RETVAL


      /sbin/insserv /etc/init.d/sshblack

    to insert this script in to the list of scripts to run at boot time.


  1. Reboot machine – should see that SuSEfirewall sets up, and sshblack.pl starts
  2. Unplug network cable – then plug it back in. SuSEfirewall rebuilds the whole of iptables configuration. Should rebuild it with BLACKLIST chain.

    Use “iptables -L INPUT” to see that the chains are correct.

    Use “iptables -L BLACKLIST” to see that hosts are being blocked.

5. Checkpoint and Restart

Now that you have SuSEfirewall2 and SSHBlacklist integrated, each time SuSEfirewall2 starts, it will start up everything needed, but it loses track of what it was that it had done before. If SSHBlacklist detects and blocks a bunch of hosts, you don’t want to lose those just because you booted your laptop or changed network connections on your server.

There is no perfect answer to this, but you can periodically checkpoint the iptables chains and the database that SSHBlacklist is using to track what it has put in to the iptables BLACKLIST chain.

Checkpoint: Save current state of BLACKLIST

     iptables -S BLACKLIST | sed 's/^/iptables /' > /usr/local/sbin/sshblack/restart.sh
     cp /var/tmp/ssh-blacklist-pending /usr/local/sbin/sshblack/ssh-blacklist-pending

Run these two commands in a cron.hourly script. This will save a snapshot of the current iptables BLACKLIST chain, as well as SSHBlacklist’s list of what it thinks is in that chain so that it can be correctly cleaned up later when the timeout expires.

Restart: Restoring the last saved running state

Edit /etc/sysconfig/scripts/SuSEfirewall2-custom

Instead of recreating the BLACKLIST chain, use

       cp /usr/local/sbin/sshblack/ssh-blacklist-pending /var/tmp/ssh-blacklist-pending

These two commands should re-create the last saved BLACKLIST chain in iptables, and restore the database that SSHBlacklist is using to maintain its status.


Security is enhanced here in two ways. The first, though simple, reduces the exposure of your machine by taking advantage of your enemy’s unsophisticated scripted attacks by simply sidestepping them. The second is much more sophisticated, and will help block anything that sneaks past the first set of defenses.

(Visited 1 times, 1 visits today)


  • magpie says:

    Excellent write up! I have longed for such a solution to brute force attacks. Simply changing the SSH port always worked well enough. However I like the sophistication of this and I don’t need to change my default port.

  • magpie says:

    There are several other scripts that perform the iptables add and delete functions. You can either change those scripts to /usr/sbin/iptables or just create a symbolic link in /sbin to /usr/sbin/iptables

  • Leave a Reply

    Your email address will not be published.