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!