Using SSH keys from untrusted clients

We all know and love OpenSSH’s scriptability. For example:

# Burn file.iso from 'host' locally without using disk space
ssh host cat file.iso | cdrecord driveropts=burnfree speed=4 - 

# Create a uptime high score list 
for host in hostone hosttwo hostthree hostfour
    echo "$(ssh -o BatchMode=yes $host "cut -d\  -f 1 /proc/uptime" \
                 || echo "0 host is unavailable: ") $host"
done | sort -rn 

The former is something you’d just do from your own box, since you need to be physically present to insert the CD anyways. But what if you want to automate the latter—commands that repetedly poll or invoke something—from a potentially untrustworthy box?

Preferably, you’d use something other than ssh. Perhaps an entry in inetd that invokes the app, or maybe a cgi script (potentially over SSL and with password protection). But let’s say that for whichever reason (firewalls, available utilities, application interfaces) that you do want to use ssh.

In those cases, you won’t be there to type in a password or unlock your ssh keys, and you don’t want someone to just copy the passwordless key and run their own commands.

OpenSSH has a lot of nice features, and some of them relate to limiting what a user can do with a key. If you generate a passwordless key pair with ssh-keygen, you can add the following to .ssh/authorized_keys:

command="uptime" ssh-rsa AAAASsd+olg4(rest of public key follows)

Select the key to use with ssh -i key .... This will make sure that anyone authenticated with this key pair will only be able to run “uptime” and not any other commands (including scp/sftp). This seems clever enough, but we’re not entirely out of the woods yet. SSH supports more than running commands.

Someone might use your key to forward spam via local port forwarding, or they could open a bunch of ports on your remote host and spoof services with remote port forwarding.

Some less well documented authorized_keys options will help:

#This is really just one line: 
no-pty ssh-rsa AAAASsd+olg4(rest of public key follows)

Now we’ve disabled port forwarding including socks, x11 forwarding (shouldn’t matter, but hey), PTY allocation (due to DoS). And for laughs, we’ve limited the allowed clients to a subnet of IPs.

Clients can still hammer the service, and depending on the command, that could cause DoS. However, we’ve drastically reduced the risks of handing out copies of the key.

Two classic ways of getting owned

There are two classic ways that Linux newbies open themselves up for pranks and shenanigans (or worse): double-su and startx. The double-su will not cause any holes that a crafty conman couldn’t already have arranged, but the startx trick can actually be a serious back door.

The double-su is when you su twice from some other user’s shell. Imagine, if you will, that Vidar just called over the admin of the company’s server, pointed to top where a process is running un-niced at 99% and has racked up hours and hours of cpu time. Vidar makes a big fuss about this, so the admin says “fine, move over”, and does the following at Vidar’s terminal:

vidar@kelvin ~ $ su
root@kelvin:/home/vidar# renice 19 3156
3156: old priority 0, new priority 19
root@kelvin:/home/vidar# su vidar
vidar@kelvin ~ $

He then scampers off to lunch. Spotted the problem? “su” doesn’t switch to another user’s account; UNIX/Linux doesn’t allow non-root users to do that, even if they have the password. Instead, it starts another shell on top of the old one. Then the admin run su again, creating a third shell on top of the other two. Now, when Vidar exits the third shell, he finds himself back at the second one, with full root access:

vidar@kelvin ~ $ exit
root@kelvin:/home/vidar# echo "Want to buy: Baggy pants and a more suitable job. Love, your admin" >> /etc/issue
root@kelvin:/home/vidar# exit
vidar@kelvin ~ $

The admin clearly should have ended his su-session with exit rather than su originaluser Of course, the real issue here is using “su” on untrusted hardware and software.

If Vidar was evil, he could just as easily have set up a software or hardware keylogger, a spoofed su or simply used strace. This is the reason why the double-su is more of a prank opportunity than an exploit.

Now, startx, on the other hand…! Some users, mostly for leetness, like to log in in text mode and then “startx” to start X, instead of a graphical login. What most of these don’t consider, is that both the shell and startx are still running on the virtual console it was started on.

If the user dutifully locks the screen before attending wetware chores, you can hit Ctrl-Alt-F1 to get to this shell, Ctrl-Z and bg. You now have a shell running as this user. If that isn’t enough, you can killall xscreensaver and Ctrl-Alt-F7. You now have an unlocked X session:

vidar@kelvin ~ $ startx
[1]+ Stopped startx
vidar@kelvin ~ $ bg
[1]+ startx &
vidar@kelvin ~ $ killall xscreensaver
vidar@kelvin ~ $ clear; exit;

This user should at least have used startx & exit to log off the virtual console when X started.

So how serious is this hole? It depends on how far you’re willing to go. Sure, with physical access you can try all sorts of things, like rebooting with a livecd. If you know there’s a bios password you can’t clear, you can take the disk out. If the disk is encrypted, you can try a cold boot attack. But surely by then, the user’s back and is trying to figure out why you’re pouring liquid nitrogen into his hardware.

It might have been easier to hit him over the head before he locked the screen in the first pace.

More seriously, proper startx usage turns getting your stuff from a trivial act of stealthy espionage into a violent crime or an invasive and time consuming thousand-euro procedure. Don’t underestimate that.

If you can think of any other classical security no-nos being reinvented by every new generation of Linux users, do comment!

Password generation traps

Generating a random password is simple, but generating a secure one is harder if you don’t know what you’re doing. When I looked through the password generation algorithm at work, I found (and fixed) several vulnerabilities and bugs, one of which allowed a remote attacker to crack any known account with a generated password in a couple of minutes.

The password generator itself was just inadequate, but an API misunderstanding made it extremely severe:

The following method signature was used for the password generation:

public static string GenerateReadablePassword(int length, int seed) 

On reading “int seed”, your head should be ringing with warning bells. More on that later. The real kicker was the invocation:

string Password = GenerateReadablePassword(10, DateTime.Now.Millisecond);

If you’re familiar with the C# API, you’re likely rolling on the floor about now. Otherwise, the part about DateTime.Now will give you the shivers. And then you realize that this wouldn’t compile without a cast unless DateTime.Now.Millisecond is an int or narrower, which would make for a pretty lousy timestamp. Oh yes… This is the number of milliseconds into the current second.

A new account got one of 1000 possible passwords, for an effective “two characters, no uppercase or symbols” policy.

Even if this had been the proper epoch time in milliseconds, it still wouldn’t have been secure. There are only 86 400 000 milliseconds in a day, and only 28 million of them are during office hours. If you can narrow it down to a specific hour, you have 3 million possible ones. And if you can hear the beep of the user’s e-mail client as the (plain-text) password e-mail is received, you’re back down to a few thousand.

Going from a timestamp to an actual high quality pseudo-random seed is better, but it still doesn’t win any prizes. You then have a best case of 232 possible passwords regardless of how long you specify the password to be.

Another example of this, included in our source code, was a copy of the first google hit for “generate random password C#”. I won’t link to it in case I increase its standing. It’s a code sample that claims that it “Generates random password, which complies with the strong password rules”, and here is an excerpt:

        // Generate 4 random bytes.
        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

        // Convert 4 bytes into a 32-bit integer value.
        int seed = (randomBytes[0] & 0x7f) << 24 |
                    randomBytes[1]         << 16 |
                    randomBytes[2]         <<  8 |

        // Now, this is real randomization.
        Random  random  = new Random(seed);

No, this is not real randomization, this is 31 bits of high quality randomness sprinkled with a snake oil vinaigrette. Any password of any length generated by this function will not be more secure than a proper 5-character alphanumeric password. You have a secure PRNG right there! Use it!