By Jay Beale ( firstname.lastname@example.org ), Lead Developer, Bastille Linux Project, President JJB Security Consulting and Training (C) 2002, Jay Beale
In this article, I'll start by discussing the weaknesses of each of the these absolutely horrid protocols. I'll then introduce secure shell (ssh) and provide an in-depth guide to using it. Before some of you write this off, realize that if you're still using passwords, you're not using ssh's strongest method of authentication. User-level public/private key authentication, somewhat similar to pgp signatures, is powerful and safe. Combine this with ssh-agent, which implements "single-signon," and you can save yourself hours a week, while remaining secure.
OK, so I claim that each of these protocols are absolutely and totally broken. They're not horrible by design, honestly, because the Internet was a much more trusting place when first invented. Before the Morris Worm, the first major cracking spree, cracking was not well known or feared. But, the Internet is a different place now, a rougher neighborhood. In that neighborhood, using these protocols leaves you open to serious vulnerability. Let's start by talking about telnet.
First, consider your standard telnet session. We know the whole thing is made of up packets, routed from one machine to another in the Internet. When I "traceroute" my current connection from home to the office, there are a number of hosts between that facilitate that connection. First, my home machine throws packets to a neighborhood router, which sends them on to the Baltimore router, which sends them on to one of my ISP's primary area routers. This forwards them to Mae East, a major Internet interconnection site. Inside Mae East, the packets move from my ISP to my employer's ISP's router. This router passes packets through three more routers before the packet gets to my work machine. That was a whole lot of machines. Now, remember that crackers can break into my machine, the other end of the connection (the telnet server), or many of the hosts along the way. But why am I worried about this?
Well, once a cracker takes over a machine, he can eavesdrop on all network traffic that machine is privy to. This includes all packets entering/leaving that machine, along with those traveling on connected hubs/shared media. OK, so, is this a problem? Well, sure! If the cracker roots a host which shares a hub with the my destination server, he can eavesdrop on the whole session. He'll see me typing my name and password, and will see names and passwords for every system I telnet to from there... Actually, the "sniffer" tools that are used to do this are usually configured to simply grab as many name/password pairs as possible. But this isn't the only problem with telnet!
The next problem with telnet is this: because there's no encryption or shared secret between the two connected computers, a cracker can take over the connection! There is a well-known, easy-to-use tool called hunt which makes this simple. The cracker, operating from one of the machines routing packets, can cut me off and take over my connection, or cut out the server and impersonate it, or even do both at the same time. This tool can be rather evil!
ftp is so very, very screwed. I'd like to spend an entire article explaining the rich history of ftp exploits available, but I'll save it for later... For now, I'll outline the biggies. OK, first, ftp is cleartext, same as telnet. Crackers will steal names and passwords by exploiting this until we finally do away with non-anonymous ftp. Next, ftp implementations have a rich history of security vulnerabilities! If you don't follow BugTraq, ask someone who does: WU-FTPd has had three new vulnerabilities in the last year! Each one required a server re-compile. In each case, the window of vulnerability between exploit dissemination and patch/upgrade was way too high. Since ftp servers generally run as root, this produced some very handy remote root compromises. If these two reasons weren't enough, realize this: ftp screws with most of our firewalls!
Most organizations/people deploy the common "non-stateful packet filtering firewalls" built into most routers, or deployable with Linux. For these, running ftp clients in (traditional) "active" mode requires you to open a huge port range on the client hosts. While this is mitigated by passive ftp, passive mode is impractical across a University/enterprise-sized organization, because of the intense user education cost. Even when we switch to passive mode clients, this requires the server's firewall to open up a sizable port range. Eliminating all this, ftp still has problems, either in implementation or protocol. The "bounce attack" paper (Phrack?) helps crackers gain additional access and hide their tracks by abusing ftp servers helpfulness. Crackers use ftp bounce attacks to force an ftp server to transfer data to a 3rd party, rather than back to the client. Trust me, ftp is really broken.1
These protocols are also cleartext protocols, but this is not their main source of insecurity. As with telnet and ftp, it gets worse! Each of these uses "rhosts" authentication2, which is fundamentally flawed. This authentication scheme uses unsafely implemented host-host trust. A user uses an "rhost" file to say that root@sysadmin_box can have root access on server. Host server checks the packets' source address to authenticate. The problem is that this method makes the critical, and wrong, assumption that an attacker cannot mangle the packets, either on the source machine or in transit. Unfortunately, he can, using simple automated tools. These tools "spoof" the source IP, allowing him illegitimate access to the target host. It's well understood that rhosts authentication stinks, because it can be fooled by any program which can craft its own packets, but also because it creates a web of trust. If I can trick machine A into thinking I'm on a host it trusts, then I can log into A as root. Now, if machine B trusts machine A in the same way, I've got easy access to B. Crackers often abuse the extensive webs of trust that are often used all around an enterprise, with devastating results.
Secure SHell (ssh) has none of these problems and can replace EVERY one of these protocols. I'll explain how to replace each protocol, including rsh with its no-password access. 3 I'm won't go too deeply into how ssh works, in favor of covering actual applications. The no-password stuff will require a bit of background, which I'll supply. Let's start with making ssh do simple remote login, a la telnet/rlogin.
Remember how telnet was insecure? Since it's a cleartext, password-based protocol riding on TCP, an attacker can sniff the session for passwords or take it over with a "man in the middle" attack. ssh isn't vulnerable to either of these, because it uses encryption to hide the entire session, passwords and all. An attacker watching your session can't see your passwords or your data - further, he can't participate in the session because he doesn't have the secret (symmetric encryption) key. (Put oversimply, this key is exchanged via a public/private key deal: the client generates an session encryption key, encrypts it with the servers public keys, and sends it over in the clear. The server decrypts this session key, since it has the server private key. From that point on, both machines encrypt their traffic with this secret key. A man in the middle can't use this information to get the secret key, because he doesn't have the server's private key.)
So, how do I use it? Like this:
[jay@max jay]$ ssh user@target_host ( or ssh -l user target_host )
to which you can type your password. Once you do, you'll be logged in. All this happens under the cover of encryption. What's more, your X forwarding is done automatically, without any "setenv DISPLAY" commands. It's really that simple! You can take your organization from insecure remote logins to secure ones just by showing people those top four lines!
By the way, if you wanted to replace rsh, and were willing to type passwords, you can append a command to the end of your ssh line:
[jay@max jay]$ ssh user@target_host "cat /etc/issue >> /etc/motd"
After you've entered a password, this will append the contents of /etc/issue to /etc/motd on the target_host, without interactive login. This is only a partial replacement of rsh, though. We still have yet to see how to skip passwords... Before we get to this, though, let's talk about file transfer.
So, what about replacing ftp and rcp? Well, first, ssh has scp, which works very much like rcp. You can read the man page, but it works like this:
[jay@max jay]$ scp /etc/motd user@target_host:/etc
Again, you'll be prompted with:
to which you type the end password. OK, this works really well for us Unix/Linux people, copying between Unix/Linux machines, but what if you've got Windows users around? (ssh is available for Windows, but scp is not, mostly...) Or what if you want something with ftp's interface? Here, ssh24 offers sftp, which looks like ftp, but doesn't act like it. sftp is encrypted and not a horribly broken, bloated cleartext protocol. Here's a session:
[jay@max jay]$ sftp user@target_host user@target_host's password: sftp> get config config | 6 kB | 6.8 kB/s | TOC: 00:00:01 | 100% sftp>
Now, you ask, what about that overly-handy, though overly insecure, rhosts-authentication? What about rlogin, rsh and rcp? Well, the r-protocols used the IP address on the packet to authenticate, so host A can trust host B, such that the sysadmin (root) on host B automatically gets root on host A. Well, this is handy, but since it can be cheated, it stinks. Now, ssh can replace this with "shost" authentication. This is not my favorite method, but it uses public/private key encryption and is fairly safe. Here's how this operates:
Host A wants to trust host B. It needs to know that the incoming connection from root@hostB is actually is from host B, not faked. Well, if it has B's public key, then it does the following. First, A creates a random number, encrypts it with B's public key and sends it to him. Only B will be able to decrypt that number, since only B has the matching private key. B then modifies this number in a pre-defined way and gets the result back to A. If A gets the right number back, then it knows that it's talking to B. This is called RSA "challenge-response" authentication.
While this is safe from a packet-perspective, I really think host-to-host trust is inherently the wrong model for today's computing. There's another form of non-passworded authentication which is based on account@host -> account@host trust. While it can use RSA-based host checking, it also verifies the identity of the actual user - this prevents root on host B from easily impersonating any user from that host. What do I mean there? Well, consider a shost (safe-rhost) authentication. We set things up so that bob@hostA can login as bob@hostB without a password. But what if someone breaks into root on hostA? They can "su" to bob there and then abuse host-to-host trust to login as bob on hostB5. RSA authentication, when used properly, can stop this kind of attack. Let's see!
OK, we're examining user-based RSA authentication. This is also based on RSA public/private key asymmetric encryption, but it depends on a personal keypair, rather than a host keypair. In essence, it goes like this:
From my account on the source machine, I create a public and private keypair, by typing ssh-keygen and entering a passphrase. (more on this later) I leave the passphrase blank for now, by hitting <enter> is response to the passphrase query. This creates a private key, ~/.ssh/identity, and a matching public key, ~/.ssh/identity.pub.
I protect the private key and keep it secret. I never move the file from my account on the source machine, especially over the network.
I append the public key to a special file, ~/.ssh/authorized_keys in my account on the target host. I make sure that the permissions on the ~/.ssh directory and the ~/.ssh/authorized_keys are good, like 0700 and 0600, respectively.
Now, when I type ssh user@target from my account on the source machine, I'll be granted access to my user account on the target host. I can watch the session in greater detail by typing ssh -v user@target.
OK, so, how does this work? The principle is the same as before. That authorized_keys file has a single (long) line for each account that I can connect from. The end of the line even lists the account name, as a comment. When I run ssh, it will ask the sshd server if it accepts my ~/.ssh/identity.pub file for authentication. If so, the server generates a random number, encrypts it with the public key and sends it to my client. If my client has the matching private key, ~/.ssh/identity, then it can decrypt the random number, apply the predefined transform, and get it back to the server. When the server receives this (correct) number, it knows that I possess the secret key and thus gives me access.
This method of authentication is rather cool. It would be incredibly hard to brute force, unlike the Unix/Linux password scheme, which has a small eight-character keyspace. Passwords are never transferred over the network at all, removing the capability for crypto-based attacks or replay attacks. Finally, if I exercise reasonable care over my secret key, it's pretty darn safe. Hey, I'm not typing passwords anymore! I can even set cron to run commands on other systems using this key! But, what if I don't trust the system that the secret key is stored on, or I simply want to be more careful/paranoid?
In this case, I encrypt the secret key with a passphrase. Remember the ssh-keygen step (step 1 above) where I skipped the passphrase? I can re-run the process, inserting a long passphrase, like "Bastille Linux 1.1 could be the last, best hope for automated Linux hardening!" Note the use of numbers and punctuation marks! Anyway, when we do this, every use of ssh (or scp) that relies on this key will require us to input the passphrase. This is good for security, but having to type that passphrase 53 times per day might be Bad, or at least Annoying. So, there's a good middle-of-the-road solution: ssh-agent.
ssh-agent makes this all so easy. Basically, it loads my private key into memory once per session, prompting me for a passphrase to decrypt the key at the time of load. At that point, I can use this key as if it had no passphrase until I end that session or remove the key from memory. Since it's never written to disk in its decrypted form, this is pretty darn safe. Let's see this at work:
[max@miraclehut ~]$ ssh-agent /bin/bash [max@miraclehut max]$ ssh-add Need passphrase for /home/max/.ssh/identity (max@miraclehut). Enter passphrase: Identity added: /home/max/.ssh/identity (max@miraclehut) [max@miraclehut jay]$ ssh humperdink@castle
In the first step, I invoke the ssh-agent, giving it a child program to run. The agent gives access to my key(s) only to its children. I run bash here, so that every program I run in this new bash shell can have access to my private key. I just as well could have typed ssh-agent xterm or ssh-agent startx to give all programs run in a specific xterm or in X session, respectively, this kind of access.
In the second step, I actually give the agent my key. I decrypt it once, by entering my passphrase. I won't have to type my passphrase again until I quit bash.
Finally, in the third step, I ssh to my humperdink account on the castle host. As long as I have set up that account properly, by appending this account's ~/.ssh/identity.pub to the end of humperdink@castle ~/.ssh/authorized_keys file, I'll connect with no password whatsoever! I can keep doing things like this over and over, using scp to copy files, ssh to login interactively, or ssh user@target "command" to execute commands on a remote host. When I'm done having fun, I can type exit to kill off the bash shell, and thus the agent.
OK, so how do I use this to automate and save myself tons of time? Try this:
[max@miraclehut ~]$ ssh-agent /bin/bash [max@miraclehut max]$ ssh-add Need passphrase for /home/max/.ssh/identity (max@miraclehut). Enter passphrase: Identity added: /home/max/.ssh/identity (max@miraclehut) [max@miraclehut max]$ for target_host in host1 host2 host3 host4 host5 host host9; do > ssh root@$target_host "./tripwire --initialize" > ssh root@$target_host "echo \"This host protected by Tripwire\" >> /etc/motd" > done
This process allows me to type in my passphrase once, and then run two commands, on nine hosts, without having to type any more passphrases. I can walk away now, content that I don't have to manually start Tripwire on each of the nine hosts. Heck, I can use more "for" loops now, since I don't have to re-enter my passphrase again until I exit out of the bash shell! This saves tons of time, without the insecurity of rsh or rlogin's rhost authentication.
OK, so we've explored why each of the protocols named above fails for today's use. You know how to use ssh in the simplest of modes (passwords) but also in even safer, useful, single-signon type modes (user RSA authentication with the ssh-agent). You may need to read the user RSA authentication a second time through, or read the man page. This stuff is not difficult, but it can seem a little involved at first glance. Once you get used to it, though, it's going to save you tons of time, while simultaneously bettering your security stance. Have fun!
Jay Beale is the Lead Developer of the Bastille Linux Project (http://www.bastille-linux.org). He is the author of a number of articles on Unix/Linux security, along with the upcoming book "Locking Down Linux the Bastille Way," to be published by Addison Wesley. At his day job, Jay is a security consultant with JJB Security Consulting and Training. You can learn more about his articles, talks and favorite security links via http://www.bastille-linux.org/jay.
1Really, client-server was a good model! But ftp abuses that model in the name of a two-connection, with servers creating data connections to clients or sending data to a non-involved, non-requesting, non-authenticated third-party machine...
2Well, rlogin can use passwords, but those are cleartext and thus suffer from telnet's issues. Also, all can use Kerberos, but I'm assuming that you don't touch that stuff...
3By the way, there are lots of solutions. I think Secure Shell is the absolute best solution right now. It protects not only the authentication (login)step, but also the data content itself. You can also try using these protocols over IPsec or other types of host-level encryption, but, at some point, you'll need to communicate with machines that use IPsec. There are lots of other attempts at solutions, but the most trustable one is where you, as a single user on a single machine, can use safe authentication and encrypted communications.
4ssh2 has a less "nice" license, compared to ssh, though it is free to Universities... If you've got lots of people who need sftp, it really is worth it.
5From there, they might escalate privilege to turn their "bob" access into "root" access.