The port knocking concept has been around for a while, and there are many different port knocking implementations. The core idea is that you send a sequence of innocuous looking packets to a server, which have the effect of adjusting the firewall rules to allow you to connect through on a port that was previously firewalled off.
Originally, this was simply conceived as a series of connection attempts to closed ports in a specific order. The "knock" would be something like trying to connect to ports 12, 23, 18, 66, or whatever, in that order, over a specific period of time.
The problem with the original concept was that if your port sequence was observed by passive eavesdropping, it was easily replayable. The obvious solution was to develop a port knocking system that did not allow for replay attacks. Such a solution suggests the use of cryptography.
And this is where the insanity begins. People started implementing all kinds of stuff to achieve this goal, and along the way lost sight of the original intent behind port knocking.
Let me tell you what I don't want from a port knocking implementation:
- I don't want something written in an an unsafe language. This should be a very small application, and the performance requirements should be minimal.
- I don't want something that runs in the kernel.
- I don't want an entirely new service that binds to a port.
- I don't want something that uses libpcap and inspects every packet.
- I don't want something that uses UDP.
- I don't want something that generates obvious port-knock requests and leaves an observer knowing exactly what port is about to open up.
- I don't want something which requires that more than one packet move over the network.
- I don't want something that uses half-baked cryptography and isn't provably IND-CCA secure.
But from what I can tell, this is exactly what people have implemented. Despite the fact that the main thrust of this is to have less services running, the direction that this has been going is to have whole servers written in C that bind to sockets and exchange UDP packets. The entire point of port-knocking was for it to be stealthy, simple, and secure. The goal was to eliminate network services, not create more of them. We can use cryptography to simplify the initial port knocking concept, rather than making it more complex.
knockknock Overview:So here's how knockknock works:
- Servers run the python app 'knockknock-daemon', and clients open ports on those servers by running the python app 'knockknock'
- 'knockknock-daemon' simply tails kern.log. It doesn't bind to any sockets, load libpcap and inspect every packet, or send anything onto the network at all.
- When you want to open a port from a client, you run 'knockknock', which sends a single SYN packet to the server. The packet's IP and TCP headers are encoded to represent an IND-CCA secure encrypted request to open a specified port from the source IP address.
- Fields from this packet are logged to kern.log and processed by 'knockknock-daemon', which validates them.
- You connect from the client to the now-open port on the server.
- The port closes behind you and doesn't allow any new connections.
That's it. Written in python, simple, single-packet, secure.
The request is encrypted using AES in CTR mode, with an HMAC-SHA1 using the authenticate-then-encrypt paradigm. It protects against evesdropping, replay attacks, and all known forms of cryptanalysis against IND-CCA secure schemes.
Why Is knockknock Secure?
- The knockknock-daemon code is very simple, and is written in python (a 'safe' language). The code is concise enough to be easily audited, and doesn't make use of any crazy libraries like libpcap.
- While knockknock-daemon needs root priviledges to adjust iptables rules, it employs privilege separation to isolate the code that actually runs as root to ~15 lines. So even though the entire code base is very small and very simple, the only part of the code actually running with root privilges is even smaller and even simpler. When you run knockknock-daemon, it will fork out the privileged code and drop privilges everywhere else before processing knockknock requests.
- The communication protocol is a simple IND-CCA secure encryption scheme that uses standard, contemporary, cryptographic constructions. An observer watching packets is not given any indication that the SYN packet transmitted by 'knockknock' is a port knocking request, but even if they knew, there would be no way for them to determine which port was requested to open. Replaying the knock request later does them no good, and in fact does not provide any information that might be useful in determining the contents of future requets.
Why Is This Even Necessary?
Becuase you are running network services with security vulnerabilities in them. Again, you are running network services with security vulnerabilities in them. If you're running a server, this is almost universally true. Most software is complex. It changes rapidly, and innovation tends to make it more complex. It is going to be, forever, hopelessly, insecure. Even projects like OpenSSH that were designed from the ground-up with security in mind, where every single line of code is written with security as a top priority, where countless people audit the changes that are made every day — even projects like this have suffered from remotely exploitable vulnerabilities. If this is true, what hope do the network services that are written with different priorities have?
The name of the game is to isolate, compartmentalize, and expose running services as little as possible. That's where knockknock comes in. Given that your network services are insecure, you want to expose as few of them to the world as possible. I offer knockknock as a possible solution to minimizing exposure.
- Python >= 2.4 (apt-get install python)
- PyCrypt (apt-get install python-crypto)
- hping3 (client only — apt-get install hping3)
Installing The Server:After installing the requirements, the first step is to download, unpack, and install the knockknock tarball:
- wget http://www.thoughtcrime.org/software/knockknock/knockknock-0.6.tar.gz
- tar zxvf knockknock-0.6.tar.gz
- cd knockknock-0.6
- sudo python setup.py install
Once this is done, we need to configure the server.
Configuring The Server:
Every (user,machine) tuple that the server wishes to grant port knocking access to gets a 'profile'. If, for instance, there were a user 'clement' who needed port knocking access from three remote machines — 'laptop', 'munin' and 'storage', you would create three profiles on the server, perhaps named: 'clement-laptop', 'clement-munin', and 'clement-storage'. Each profile maintains its own encryption keys and state. Each profile has its own 'knock port' where the port knock requests are sent. This has to be a port that you don't plan on using for a running service.
You can create profiles on the server by running:
- sudo knockknock-genprofile <profileName> <knockPort>
So, for instance, if we wished to create a profile for 'clement-laptop' that used '666' as a knock port, we'd run 'sudo knockknock-genprofile clement-laptop 666 '
That's it, your server is now configured. To run it, simply execute:
- sudo knockknock-daemon
Configuring The Client:
Follow the server installation instructions for installing the requirements, unpacking, and installing the tarball. To configure the client, however, we need to copy the profile information from the server to the client machine. If we're configuring the user 'clement' on the client machine 'laptop', the profile on the server might be called 'clement-laptop'. We'd need to copy the files from /etc/knockknock.d/profiles/clement-laptop/ on to the client machine. You can copy the values by hand, email the files (securely!), or (if you have root access), scp them.
The files need to end up in '~/.knockknock/<serverHostName>/' on the client machine. That is, if the server is called myserver.com, the profile information would be copied to '/home/clement/.knockknock/myserver.com/' on the client. Using scp:
- scp email@example.com:/etc/knockknock.d/profiles/laptop-clement/* ~/.knockknock/myserver.com/
Configuring The Server Firewall Rules:The goal here is to firewall off all the ports that you don't want to be fully public, and to have connection attempts to firewalled ports be logged to '/var/log/kern.log'. There is a script called 'minimal-firewall.sh' included with knockknock that will will firewall off everything (but of course any port can be opened by a knockknock request). Feel free to use or modify this script. Otherwise, you'll want to setup the firewall rules generally as follows.
Let's say that on the server we're running three services: pop3s (995), ssh (22), and httpd (80). We want httpd to be public, but we want sshd and imapd to only be available to those who send valid port knock requests. The rules might look as follows.
We want to allow existing open connections and all outgoing traffic:
- sudo iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
- sudo iptables -A OUTPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
- sudo iptables -A OUTPUT -j ACCEPT
We want to setup a REJECT logging rule that we'll call REJECTLOG:
- sudo iptables -N REJECTLOG
- sudo iptables -A REJECTLOG -j LOG --log-level debug --log-tcp-sequence --log-tcp-options --log-ip-options -m limit --limit 3/s --limit-burst 8 --log-prefix "REJECT "
- sudo iptables -A REJECTLOG -p tcp -j REJECT --reject-with tcp-reset
- sudo iptables -A REJECTLOG -j REJECT
And finally setup the INPUT rules to allow connections on port 80 but reject everything else:
- sudo iptables -A INPUT -m state --state NEW -p tcp --dport 80 -j ACCEPT
- sudo iptables -A INPUT -j REJECTLOG
You'll want all of this to be in some sort of script that runs at boot.
Using knockknock:Now that you have knockknock-daemon running, the firewall rules configured on the server, and your profile installed on the client, you're ready to open some ports. On the client, you simply run 'knockknock -p <portToOpen> myserver.com'. To open, for instance, ssh (22):
- knockknock -p 22 myserver.com
You now have the amount of time specified by the 'delay' parameter in /etc/knockknock.d/config on the server to connect from your client's IP address to port 22 (defaults to 15 seconds). As soon as you connect, no further connections will be allowed unless another knockknock request is issued.
Optionally using knockknock-proxy:After you have the basic knockknock system running, you might find yourself wishing that you didn't have to type "knockknock -p <whatever> myserver.com" all the time. It's not such a big deal for opening an ssh session, but what about your pop3 client? That's the kind of software which might want to periodically make connections on its own, and even if it doesn't, opening up a terminal to run 'knockknock' every time you'd like to click 'check mail' is kind of a drag.
So knockknock-proxy is a small SOCKS proxy that is knockknock-aware. It binds to a port you specify on localhost, and then implements the SOCKS protocol as usual. However, whenver it sees a request for a connection to a host that you have configured knockknock for, it quickly sends a knock to the server before proxying the connection through. The upshot is that any application which has SOCKS proxy support will seamlessly auto-knock each time it would like to make a connection.
To run knockknock-proxy, you simply execute:
- sudo knockknock-proxy <listenPort>
Be aware that while knockknock-proxy binds to localhost and isn't accessable from the network, it doesn't support any type of authentication mechanism to differentiate between users on a local system. This means that it's best suited for personal, single-user systems.
Changes:Changes in 0.7 (12/18/09):
- Fixed the client-side 'knockknock' command to accurately report success of hping3
- Note: if you are upgrading from 0.3, the introduction of "profiles" into 0.4 has changed the way that keys/counters are stored. Unfortunately, you will have to re-generate and re-distribute your credentials using the knew knockknock-genprofile (knockknock-genkeys has been abandoned).