Thoughts on Perimeter Security

Door BreachingWhen it comes to electronic security, a lot of thought has to go into many different areas: workstation security, OS security, privilege separation, malware, social engineering, etc. Perhaps one of the first aspects of network security is perimeter security, or the firewall. In most small networks today, this is relatively straightforward. A router/gateway device sits between the LAN and the Internet, filtering desirable traffic from potentially malicious attacks. Most networks utilize private (RFC 1918) IPv4 addresses, with the router providing NAT on the Internet-facing interface, effectively making inbound connections to LAN devices impossible.

That's not to say that you can take perimeter for granted, though. Protocols like UPnP allow client devices to "open" ports in the gateway essentially at-will. Clients workstations can, of course, be susceptible to attack from any host to which they initiate outbound connections, and firewalls by default offer little or no protection against compromised systems already behind the firewall. Finally, if you're like me, there's usually at least one service that you want to leave available on the Internet-facing link, so your network isn't totally inaccessible if you happen to be away from home or out of the office.

That one service, of course, would be some type of VPN service, or in my case most often, an SSH server. (If you're not familiar with SSH, that stands for Secure Shell, and provides not only terminal access, but file transfer and TCP port forwarding capabilities somewhat like a rudimentary VPN, without the complexities associated with operating a full-blown VPN.)

By default, SSH operates on port 22, and as it's name implies, it is generally very secure. All traffic is encrypted using industry-standard encryption, so you don't have to worry about evesdroppers when you log in from an open wifi hotspot, for example. You can set up SSH to require a simple password, if you like, or use the a more secure method of authenticating with an RSA public-private key pair, which you can create quite easily. I always, always set up my servers to only allow RSA-key based authentication, and block password-based authentication entirely. If your RSA private key is encrypted using a strong passphrase, it's comparable to using two-factor authentication.

A recent check of my /var/log/auth.log entries emphasized the importance of this practice. On a home computer with an Internet-facing SSH daemon, I have seen thousands of failed login attempts in a single day. Though I am quite confident in the security of SSH using RSA-key based authentication, there are apparently enough people running SSH with weak configurations that it is profitable for bots to scan the Internet, attempting to brute-force their way in wherever they find an SSH service.

This got me thinking: what if someone were to find a zero-day in the OpenSSH daemon? Or perhaps just another bug such as the Debian OpenSSL bug that left thousands of servers (un)protected by predictable (non-random) keys? I'd be a sitting duck--in fact it could be minutes or even seconds before my servers were compromised, since the thugs have already been pounding on my (virtual) door.

So, what if I could hide the door? That's when I decided to re-visit the idea of port-knocking. I've heard of port knocking before, but never liked the idea, for several reasons. One, I don't like the idea of having a really strong, secure system being "protected" by a weak, throw-away system of security-by-obscurity. The added "security" could make me lazy in configuring the real defenses, since, after all, it's "protected" by something else. Finally, the added complexity of another security system is just one more thing to go wrong, potentially very wrong, and lock me out of critical services when I'm many miles away.

Those were my objections to the port-knocking idea, but like I said I re-visited the idea. Actually, having multiple layers of security is considered a good thing. After some research, I discovered that it might just be possible to have the best of both worlds.

For several years, I have used Shorewall to configure my (Ubuntu / Debian) Linux firewall/routers. It's very straightforward but extremely flexible and versatile, while keeping things much more neat and tidy compared to configuring IPtables directly. I've used it in complex, multi-site, multi-ISP routing / VPN systems to provide load balancing, content filtering, failover, quality-of-service, etc. Since it's really just a front-end for configuring IPtables, it gives you the full power and stability of the Linux kernel routing capabilities.

And it turns out that you can implement port knocking easily in Shorewall, without the need to add any extra services. I started with an example here, but I didn't like the idea of having just a single port-knock to open the service. After all, there's fair chance that a random port scan could hit this port without hitting the 2 traps as given in the example. So, I expanded the example, using a second routing table to require a sequence of two port knocks, and adding a wide range of "trap" ports to prevent accidental triggering. Of course, I had to adjust my .ssh/config on my client stations in order to open ports seamlessly in the background.

Voila, it works. After several weeks of running on numerous servers, I have never had an instance where I was unable to log in. Also, my auth.log shows zero invalid login attempts, compared to thousands before. I can port-scan my machines, and all ports appear to be closed.

Best of all, I can sleep better at night, knowing that, should the dreaded zero-day hit OpenSSH, I'll have just a wee little bit of extra time to get things patched before the world ends.

If your curious, here's the configuration I used to set up my port-knocking service. (I've changed the numbers, of course!) I assume you have a basic working setup of Shorewall, with zones named inet and fw. My policy is configured to DROP (filter) packets by default, so the rules below reflect this. You could as easily use a REJECT policy, which would give more versatility in the choice of port-knocking clients, but also making your server a little less "stealthy." Just be sure that your actions match the default action you've configured in your shorewall policy.

/etc/shorewall/rules

SSHKstage2:info        inet            fw            tcp        12345
SSHKnock:info           inet            fw            tcp        22,4000:40000

/etc/shorewall/actions

SSHKstage2
SSHKnock

/etc/shorewall/action.SSHKnock: Empty File
/etc/shorewall/action.SSHstage2: Empty File
/etc/shorewall/SSHKnock

use Shorewall::Chains;

if ( $level ) {
    log_rule_limit( $level, 
                    $chainref, 
                    'SSHKnock',
                    'SSHKstage2',
                    '',
                    $tag,
                    'add',
                    '-p tcp --dport 22 -m recent --rcheck --name SSH' );

    log_rule_limit( $level,
                    $chainref,
                    'SSHKnock',
                    'DROP',
                    '',
                    $tag,
                    'add',
                    '-p tcp ! --dport 22 ' );
}

add_rule( $chainref, '-p tcp --dport 22    -m recent --rcheck --seconds 60 --name SSH           -j SSHKstage2' );
add_rule( $chainref, '-p tcp --dport 23456 -m recent                       --name SSH  --set    -j DROP' );
add_rule( $chainref, '-p tcp               -m recent                       --name SSH  --remove -j DROP' );
add_rule( $chainref, '-p tcp               -m recent                       --name SSH2 --remove -j DROP' );

1;

/etc/shorewall/SSHKstage2

use Shorewall::Chains;

if ( $level ) {
    log_rule_limit( $level, 
                    $chainref, 
                    'SSHKstage2',
                    'ACCEPT',
                    '',
                    $tag,
                    'add',
                    '-p tcp --dport 22 -m recent --rcheck --name SSH2' );

    log_rule_limit( $level,
                    $chainref,
                    'SSHKstage2',
                    'DROP',
                    '',
                    $tag,
                    'add',
                    '-p tcp ! --dport 22 ' );
}

add_rule( $chainref, '-p tcp --dport 22    -m recent --rcheck --seconds 60 --name SSH2          -j ACCEPT' );
add_rule( $chainref, '-p tcp --dport 12345 -m recent                       --name SSH2 --set    -j DROP' );

1;

Usage
Of course, you'll want to change the port numbers from the examples above. These port numbers are essentially a pre-shared, 32-bit key (65,536 ports ^ 2). Restart your firewall and then try connecting from another site. Any attempts to connect to port 22 directly should silently fail. Port-knock first, using a tool like netcat:

nc my.protected.host.example.com 12345 -w 1
nc my.protected.host.example.com 23456 -w 1
ssh my.protected.host.example.com

This should let you in.

If you should be stuck on a windows client, you can use putty to manually attempt connecting on the port-knock ports first. You can also use your web-browser with the same strategy. Or, download one of the tools made for this purpose here.

One the client side, configure your SSH client to knock the necessary ports before connecting. You can use netcat, or you could use the knock command from the knockd package. For this example, you will also need the netcat or socket tool to proxy the connection, essentially to make the "hack" work. Adjust your ~/.ssh/config something like this: (examples show both methods)

Host protected1
  HostName protected1.example.com
  ProxyCommand /bin/bash -c 'command nc %h 12345 -w 1; nc %h 23456 -w 1; exec nc %h %p'

Host protected2
  HostName protected2.example.com
  ProxyCommand /bin/bash -c 'command knock %h 12345 23456; sleep 1; exec socket %h %p'

Other Considerations

  • As with anything, adding more complexity to a system means more things that could go wrong. This method is the most straight-forward, in my opinion, but like anything it's not foolproof. If you break your firewall config so shorewall won't start, all bets are off on your security!
  • Consider your choice of "knock" ports carefully. Consider whether you may have to "knock" from behind someone else's white-listing firewall! If you can't make outbound connections on the necessary ports, you are as good as locked out from your services!
  • As stated before, port knocking isn't a substitute for good security. It should be considered a second layer of defense, at best.
Tags: