Introduction
This is a proof of concept for using TOTP with a shared key between client and server to gain access to a normally closed secure shell (ssh) port.
Port knocking is typically implemented as a series of connection attempts to a "secret" sequence of ports. When the correct sequence of connetions are attempted (none of which form a complete connection) the firewall identifies this sequence and opens the desired port (typically ssh, port 22).
The effect of this is that the sensitive ssh port can remain silent, not responding to any connection attempt, until the knock sequence occurs. This is helpful because attack probes will generally not be able to identify that ssh is available for connection (because most of the time it isn't!).
The technique presented here is similar to port knocking in that it keeps the port of interest (ssh) closed until a special signal is received from the client. However, the signal is not a series of connections to special ports. Instead, it is a hash based on a shared secret key sent to a fixed UDP port. The knock signal is generated in the same way as the hash generated by the TOTP protocol. If the correct UDP packet is sent to the knocking port then the server will open the ssh port in the firewall for long enough for the client to make a connection.
The server and client share a single secret key used in the hash (same as TOTP). The server (managing firewall configuration) and the client (attempting to make an ssh connection) can independently generate the correct SHA256 hash.
The correct hash (like TOTP) updates every T seconds by hashing the secret key with a salt of timestamp / T. Rather than send an N digit code based on the hash, this sample code uses the full 32 bytes (256 bits) of the SHA256 hash. If the server is able to match the full 256 bit code then the ssh port is opened for a brief period.
The current, correct hash can be sent in the open over the internet because the hash changes every T seconds. After T seconds that hash will no longer be an effective opening knock.
Get it running (on Ubuntu)
First. This is only a proof of concept. This sample code is not intended to be used in production! Also, be sure you have console access to the machine before trying this. For issues and concerns see the Pitfalls and Disclaimer sections below.
The software uses easyufw as a simple
api to control the firewall through ufw.
Ubuntu based systems have ufw installed
by default, but they do not have the
python-ufw interface package. Therefore
you will need to run the command on the
server machine: sudo apt-get install python-ufw
The remaining command lines separate out the server setup (Ubuntu based) and the client setup (general python).
On the server:
# dependency
sudo apt-get install python-ufw
# get totp_knock
git clone https://gitlab.com/dhj/totp_knock
cd totp_knock
sudo python timeknock_s.py # run service
On the client:
# get totp_knock
git clone https://gitlab.com/dhj/totp_knock
cd totp_knock
# connect
ssh <host> # won't work, server is blocking
python timeknock_c.py <host> && ssh <host> # should work
If you have problems connecting to the server (make sure you have console access) you can run the following from the command line of the server to restore access:
sudo ufw disable
Also helpful is sudo ufw status verbose
You can edit the server and client files to change the super secret key to something actually secret.
Benefits
(similar to port knocking)
-
Even if the port and technique are identified an attacker should not be able to access port 22 (in sample code they could still piggyback when the port is opened, but that can be fixed).
-
Reduces logs of constant bot attacks on port 22.
-
Reduces the ability of an attacker to fingerprint a networked computer (udp port does nothing unless it receives the correct packet)
Pitfalls
-
This is a proof of concept only, do not use this in production! It is not robust to errors.
-
When the sample code is run it will activate the ufw firewall. By default ufw blocks all incoming ports.
-
When port 22 of the firewall is opened it is opened for all ip addresses rather than the address that sent the packet.
-
The sample code is non-concurrent. It opens the port and sleeps for a few seconds. If the previous pitfall is fixed (open to sender only) and two clients try to initiate a connection at the same time then the server would need to handle connections concurrently.
Potential Improvements
-
Make the server code robust against errors and run as a daemon (restarts, logs, etc).
-
Use environment variables for keys
-
Open the port only for the address that sent the packet.
-
Adjust open time and key change time to be equal (5 seconds each)?
-
Make a particular key only work once to open the port and only for the address that sent the packet. This would prevent a replay attack. It would not increase vulnerability to denial of service attack (success for the time period still requires correct 256 bits).
-
Combining 3 and 4 with the existing non-concurrent sleep behavior would have the same effect as 5 (only once per key). Although concurrent connection starts would still be a problem. Implementation of one port opening per valid key would inherently limit throughput.
Similar Tech
Integration of TOTP with knocking has been done by making the knock ports change based on a shared key and TOTP hashing by sshflux.
Remote authorization of firewall rule changes has been implemented using encrypted messages to a server with a technique called Single Packet Authorization (linuxjournal article).
EDIT (2018/05/06): The well polished and effective software that came out of the Single Packet Authorization paper is fwknop. It is a finished security product ready to be used in production. Aside from the proof of concept nature -- the differences are validation technique (TOTP vs HMAC) and the protocol (UDP vs TCP). With one-way UDP ports no ports show up on a port scan.
The wiki page on port knocking also has several thesis references to port knocking.
I did not do a thorough literature search. There are probably other implementations that are even more similar to TOTP Knock than these. Please let me know about them!
Disclaimer
WARNING: DO NOT USE THIS IN PRODUCTION. The code is a proof of concept. If you use this, make sure you have console access to the machine. If you find a bug you can lose ssh access and will need a console to reset the firewall.
The following command on the server will reset the firewall:
sudo ufw disable
The test code will lock down all incoming ports (default for ufw) and listen on UDP port 34999. It will only open port 22 (ssh) when it receives a correct signal based on the time and shared key. A key is valid for 30 seconds and sending a valid key will open the port for 5 seconds. Outgoing ports are not restricted by default with ufw.
Do not use this as your only ssh security. By design the software will open the ssh port for a few seconds at a time. This only reduces the attack profile of a computer connected to the internet (similar to port knocking).
Contact Me
If you have any questions or comments, you can get in touch by e-mailing me at dhj at this domain.
License
The sample code is released under an MIT license. Enjoy!