What do you do when you’re a university student who’s just learnt about network sniffing and how anyone can capture your (unencrypted) UNIX account credentials from the network and log in as you? You create a challenge-response authentication system using a Bourne Shell script to stop them of course. It is also how I almost locked myself out of my university UNIX account.
Okay, so I’m no longer a young university student, but brace yourselves because I’m going to reminisce about when I was, and the time when I discovered that it was possible to sniff network packets on a network. Back then, this was more of a problem than it is today, as remote access to UNIX boxes was mostly by telnet and FTP.
If you know your network protocols, you’ll know that both telnet and FTP send login credentials as unencrypted text over the network. While this is easier to implement, it also means that anyone can sniff the network, get your account credentials, and then log in to your account as yourself.
This got me wondering if I could create something that would verify that it was actually me providing my credentials, rather than somebody else using my credentials — in other words, an extra form of authentication.
This extra authentication would need to use some sort of information that would change after each login session, so if anyone obtained a copy of the information, they could not reuse it to log in to my account (that is, a replay attack). This is essentially a one-time password mechanism.
Unfortunately, I no longer have a completely readable version of my original script — I though that I kept a hard copy somewhere, but I have been unable to find it. However, unlike my high school attempt of getting Logo to play hangman for some reason, I can actually remember how I did this.
I recreated my original script as best as I could remember it, and have just recently named it crab — Challenge-Response Authentication in a Bourne shell script, and you can download it from the new malwaremusings github repository (bare with me, I only got into the whole git and github thing just over a month ago!). See the wiki pages for installation instructions.
As I was developing the script, I kept coming up with ways that I could get around it. Here then, is a list of problems that I came up with, from the initial problem of preventing a replay attack, and how I solved them… and can you believe it — all without using sed or awk.
Preventing replay attacks/reuse of credentials
This is the main purpose of my script — to prevent replay attacks. This is the most obvious problem when you think about people sniffing network traffic — the gathering and reusing of login credentials sent as unencrypted text over the network.
This problem exists because the same login information (namely my user name and password) is sent again and again, each time I (or someone wanting access to my account) attempt to log in. This makes it easy for anyone wanting access to my account, to send that same information and successfully log in as myself.
To fix this problem, we need to introduce some information that changes with each login. We can do this with a one-time password scheme, or anything that can produce some changing information that must be presented to log in. This information will need to change after each (successful at least) login.
Now, this dynamic information that we create and use must be known by myself, being the authenticating user (so that I can authenticate), the server to which I’m authenticating (so that it can authenticate me), and it cannot be sent over the network for any purpose other than the actual authentication itself (otherwise it can be sniffed and used by someone other than myself).
One solution would be to use a one-time password. That is a list of passwords (essentially) that the server has, and which I have. Each time that I log in successfully, I cross that one-time password from the list and the server deletes the password from its list, and next time that I log in I present the next one-time password from the list along with my normal UNIX user name and password.
The problem with the one-time password approach, is that the list of one-time passwords needs to be stored on the server, and readable by my security script. If it is readable by my script, then it is readable by my user account and hence readable by someone using FTP to access my account/files. Since login scripts don’t run when logging in with FTP, my script won’t offer any protection against this.
This problem can be fixed, as explained later, but another problem with a one-time password is that the list will need to be updated on the server, and it either needs to exist on the server as unencrypted text, or the key to decrypt it needs to exist on the server. I don’t want to spoil the suspense or anything, but as it turns out, a solution to one of the other problems also fixes this one.
The solution that I came up with was (as I later found out) a challenge-response algorithm. We are going to use some information that is available at login time to calculate a response which, when given to my script, will allow me to log in. This approach means that we don’t have to store the information itself on the server, however, we need the algorithm on the server and anyone with access to the algorithm can also generate the response information.
More on this later, when I talk about protecting the challenge-response algorithm from prying eyes.
Shell scripts can be interrupted with ctrl-c
As it turns out, pressing ctrl-c (‘control’ and ‘c’ — a key combination commonly used by both MS-DOS and UNIX to interrupt a process) to interrupt your login script (in a Bourne Shell at least) will skip it and drop you at a shell prompt. This will allow any unauthorised person to gain access to my UNIX account by simply interrupting the login script — not cool!
We can get around this problem by using the Bourne Shell‘s built-in trap command:
trap [-lp] [[arg] sigspec ...]
Basically, this command causes the shell to run the command(s) specified by arg, when it receives any of the signals specified by sigspec. If arg is an empty string, then the signal is ignored.
Knowing this, we can start our script with the following command:
trap "" 2 3 15
Or, if you prefer to read English signal names:
trap "" sigint sigquit sigterm
This will cause the shell instance that is running the script to ignore sigint (ctrl-c), sigquit (ctrl-\), and sigterm (kill -15) while the script is running.
FTP access to my account can be used to overwrite my security script(s)
The shell doesn’t run login scripts when accounts are accessed using FTP. This is a problem because it means that if someone logs in to my account using FTP, then my login script won’t run and consequently neither will my security script. This renders my security script ineffective against FTP access. This means that an unauthorised user could FTP to my account using my user name and password that they sniffed from the network, and overwrite either my security script or my login script.
Alternatively, they could download a copy of my login script, modify it to stop it from running my security script, and upload it; or they could upload a zero byte file to replace my security script. Either way, it’s bad news for us.
We could attempt to prevent my scripts from being overwritten by removing write permissions from the files. Twenty years ago, when I first developed this, this method worked. These days however, the GNU FTP client/server combination (at least) has a chmod command to change file permissions.
Whether or not FTP can be used to add write permissions before subsequently putting a new file to overwrite my script(s) will depend on the FTP server implementation supporting the command (SITE CHMOD). Back when I developed this, the chmod command wasn’t implemented (but then that was on a Solaris (Sys V r4) server).
While this wasn’t a problem at the time, I was just wondering how to overcome this problem, and the only thing that I can think of is to make the login script and security script(s) owned by a user other than the authenticating (account) user (that is, me in this case). The operating system will fail a chmod operation on a file that I don’t own.
If both of these user accounts (mine and the dummy one) are then protected by the security script, or better still, if the (dummy) user owning the login script and security script(s) doesn’t have a valid login shell and can’t login via FTP, we should be okay. The problem then though, is going to be allowing the authenticating (account) user, and only the authenticating (account) user, the ability to read and execute the script(s) — we’d need to use file ACLs or some such.
There is another more recent problem. Even if the permissions on the .profile file cannot be changed, the file can be deleted using the ftp client (at least by using the GNU client/server combination), which will also stop my security script from running (and save an attacker from having to overwrite the file).
Removing all write permissions from the user’s home directory will prevent the .profile file (and any other files for that matter) from being deleted. However, if the chmod command is implemented over FTP, then that can be used to add write permissions before deleting the file.
These days though, FTP should have been replaced by scp, unless you are running an anonymous FTP server, or some other publicly accessible FTP server, in which case you probably don’t want normal user accounts on there anyway. scp won’t modify file permissions of the remote file — you use an ssh session for that, and an ssh session will run the login script which will then run my security script.
FTP access to my account to read the security script and any of my files
Here’s the clever bit. Preventing unauthorised users from being able to login to my account is good, but only partially useful if they can still use FTP to gain access to any of my files — I wanted to be able to protect my files from unauthorised access too (and no, I wasn’t doing this because I had something to hide — it’s a privacy thing. Just because I don’t have anything to hide doesn’t mean that I want people knowing what I’m doing. That and the challenge of coming up with some protection).
So what do we do? The trick here is to realise that UNIX separates the ability to read the contents of a directory, and the ability to traverse (change to and/or access files underneath) a directory — we hide the files.
To hide the files, I created two subdirectories, front_gate/, and front_door/. front_gate/ was a subdirectory of my real home directory, that is, it was $HOME/front_gate/. The front_door/ subdirectory was naturally a subdirectory of front_gate/ (you need to go through the front gate to get to the front door).
Now, here’s one of the clever bits. We remove read permissions from both my home directory, and from the front_gate/ subdirectory. That is, we give them file permissions of –x——, or 100 when represented as octal. The user and group owners are the same as those for my real home directory, $HOME.
Permissions of –x—— (100) allow files in the directory, and subdirectories, to be accessed (by the owner), but don’t allow anyone (except root) — even the owner — to see which files are in the directory. That means that in order to access any files below it, you need to know the path to, and file name of, the file that you want to access.
Now I’ve just completely blown mine by giving the names of the subdirectories (front_gate/ and front_door/) away in a blog post, but ideally you’d pick unique names for the subdirectories, that are hard to guess.
My security script, knowing the name of the front_door/ subdirectory, will be able to change the HOME environment variable to be $HOME/front_gate/front_door, which will cause the shell and other commands to treat the front_door/ subdirectory, where all my files are, as my home directory. That is, dot files will be stored in the front_door/ subdirectory rather than in the directory specified as my home directory in the /etc/passwd file.
That now brings me to the next problem. My login script needs to reference my security script (to be able to run it), and in doing so it will give the names of the hidden subdirectories away, making it pointless trying to hide my files.
However, if the security script is located in my official (as specified in the /etc/passwd file) home directory, then it will be readable using an FTP client. Removing all read permissions won’t help us here, as the shell needs read permission to be able to read and execute a script.
How do you stop someone from being able to read something when you can’t restrict access to it (apart from blind folding them)? The obvious answer is to encrypt it. However, with that obvious answer there’s another obvious problem — how do you protect the encryption key?
Now today, the encryption key problem can often be solved by using public key encryption. This is an encryption method that uses asymmetric keys — that is, the encryption method and decryption method use different keys (one of which is made public and the other is kept private).
Public key encryption won’t help with my security script though, because the shell needs to be able to decrypt the script, and the shell is acting on behalf of myself (or on behalf of whoever successfully logs in to my account after having sniffed my credentials). In other words, the authorised party (the login shell) and the potentially unauthorised party (the user attempting to access my account), are the same UNIX identity. As such, they will both have access to the necessary information to decrypt my security script.
Storing the key in a file and removing read permissions won’t help because the shell will need read permissions to read the key to decrypt the script.
The security script (and hence the algorithm/key) needs to be readable only when I log in
So to summarise the problems, we need to stop users from using FTP to download my security script to get the challenge-response algorithm and/or encryption key.
We can’t hide the script underneath the hidden directory (front_door/) because the login script will need to be able to find it, and hence we’ll give the name of the hidden directory away in the login script.
An obvious problem with encrypting the file is that the login script needs the ability to decrypt it. If the login shell, running as me, can decrypt it, then so can anyone else with the same access as the login shell — that is, anyone logged in as me.
We need a way of making it so that the security script is only available/readable when I log in, and not when anyone else logs in as me. Not being able to resist quoting a famous animated character, ‘can we fix it?’.
It turns out that ‘yes we can!’.
What if we encrypt the security script but then, instead of storing the key in a file/script on the UNIX host, I type the key in when I log in? ‘What does that solve?’, I hear you ask, ‘it is still vulnerable to a replay attack’, and yes, it is.
As I mentioned before, we need some changing information. What if we kill two birds with one stone and use the key as the changing information? If we do that, though, how will we know what the key is without the script sending the key over the unencrypted network connection, thereby (to make a change from ‘hence’) revealing it to any eavesdroppers?
Here’s the next clever bit. Remember how I mentioned a challenge-response system? In order for someone to successfully authenticate to a challenge-response system, they need the challenge, and the algorithm to calculate a response. Either that or a means of brute-forcing the response, or an exploit for a weakness in the system.
If we implement the algorithm in the encrypted script, so the algorithm can’t be discovered, we can safely send the challenge over the unprotected network connection without giving an eavesdropper the information that they need to successfully authenticate.
How can we do that without either leaving the challenge-response algorithm or leaving the key to decrypt the algorithm, readable? We can’t. Unless, we think a little differently. The obvious implementation of a challenge-response system would be to prompt the authenticating user with a challenge, prompt for a response, run the challenge through the algorithm and compare the generated response with the response that the user entered, right?
What if the algorithm were to take a challenge, use the algorithm to generate a response, then use the response as the encryption key to encrypt the security script (containing itself)? How would we know if the user entered the correct response? We simply decrypt the encrypted file with the user’s response and if we get a valid shell script back, then it is probably reasonable to assume that the user’s response was correct.
Putting it all together
I brought all of these concepts together and created two Bourne shell scripts, a directory structure, and a set of file/directory permissions that will implement a crude challenge-response authentication system for my university UNIX account. You can download my recreation of them from github.
While I developed the original around twenty years ago, it is still useful for adding an extra layer of protection against brute force attacks and otherwise compromised account credentials. Assuming, that is, you pick a sensible challenge-response algorithm.
The more astute of you will realise that the actual challenge-response algorithm that I have used in crab will actually reuse the encryption key/challenge if the user successfully logs in more than once in the same minute — hence, under such circumstances, a replay attack will work. This is easily fixed by using the seconds component of the time in the challenge. The response should probably also be a bit more complex/longer.
However, modern systems such as SSH, lessen the chance of credentials being compromised while in transit, and today’s FTP implementations implementing the UNIX chmod functionality make my script(s) easier to defeat (without extra measures such as changing file ownership, which won’t be possible for ordinary users without root privileges, and file ACLs).
Overall, my scripts probably aren’t as useful today as they once were, but hey, we had joy, we had fun, we wrote shell scripts on a Sun (well, I wrote the original version on a Sun box). Seriously though, some of the concepts that my scripts use are still relevant. For instance, the use of an encryption key rather than a stored password/token for authentication/authorisation can be seen in 802.11 wireless networks (if you are allowed to use the system, you’ll know the encryption key and hence be able to encrypt and decrypt packets correctly), and authentication systems like Kerberos (and I suspect Banyan Vines did, back in its day) use a changing piece of non-guessable information to prevent replay attacks.
There is still some room for improvement — the system that I’ve presented here is what I developed twenty years ago (or at least as I remember it — if anyone knows how to decrypt UNIX crypt encrypted files without the key, let me know, as then I’ll be able to get my original script back).
You could hide the security (stage 2) script under the front_gate/ subdirectory by naming it using some changing information which could be pre-determined in a similar manner to that of the encryption key. That way, the authenticating user’s response would be broken down in to two pieces of information — part of the name of the second script, and the encryption key to decrypt said (as opposed to sed) script. The problem with this, is that you’d need to find some way of obtaining challenge information to present to the user, and as it is, my script uses the last modified time stamp of the (encrypted) second stage script (as it is rewritten when it is re-encrypted). This challenge information though could be calculated after a successful login, and then obfuscated in a file.
It would also be possible, these days, to write a mobile phone application to implement the challenge-response algorithm to calculate a response, given a challenge. That will enable the use of a more sophisticated algorithm that doesn’t rely on a user being able to work the response out in their head. Malicious software intercepting the challenge-response on the user’s mobile device isn’t a problem, for the same reason that sniffing the network isn’t a problem — the system is designed to protect against replay attacks.
However, malicious software on the user’s mobile phone may be able to access and leak the algorithm, which would then allow an attacker to calculate their own responses to challenges. That is where another tactic comes in handy, even if it is just security-by-obscurity — notice how the scripts never prompt for a response, nor do they present the challenge in an obvious way. The authenticating user also needs to know how to extract the challenge from the information presented by the script, and since this post is getting quite long (and I’ve always wanted to say this), I shall leave that as an exercise for the reader.