Writing About Writing Secure Shell Scripts

I recently read this cautionary tale about shell scripts. https://www.linuxjournal.com/content/writing-secure-shell-scripts

It’s cautionary in two ways: it is intended to cause shell programmers caution, and I caution against you taking the article too seriously.

One of the biggest threats to the shell in memory was the Shellshock vulnerability. This wasn’t typically a direct threat to shell scripts, but one caused by a bug in a shell, and by other programs exposing parts of the shell to external input, often in unexpected and unlikely places.

That kind of topic is not what the Linux Journal article is about.

Instead, it provides three cautions to people writing shell scripts:

  • Know the utilities you invoke: fully specify program paths so you don’t accidentally run something out of /tmp
  • Don’t store passwords in scripts: find your own other solution, good luck sucker!
  • Beware of invoking anything the user inputs: with examples that work on no modern Linux

These are all fine recommendations, but are made in terrible ways.

Know the Utilities You Invoke

The author provides an example where a user drops a malicious “ls” script in /tmp, then scripts execute it because they do not completely specify the path of ls. The author’s recommendation is to completely specify all paths. The problems with this are two-fold…

First, to execute a program in a directory that isn’t in your path, you must specify the path of that program. If it’s your current directory, that’s just a “./”, but it still must be present. If you have “.” in your path, as the article suggests, then you have made a configuration error that can easily cause you unexpected behavior at best, and the precise security vulnerability mentioned in the article at worst.

The correct remediation for this issue is to not put “.” in your path.

The second problem with the article’s recommendation is that many programs are not in a set location on the system, and some even move around over time (decades). Many claim that the “/bin”, “/usr/bin”, “/usr/local/bin” system is set, sensible, and regular. This is not the case in practice. Usage varies between distros, and even between versions of distros. A system update can move the location of a program in the worst case. This isn’t a problem if the new location is in your path, you’ll still execute the right thing. Unless you fully specified the old location in all your scripts.

This isn’t to say that you can’t usually specify the full location. And specifying the full path might provide a greater security guarantee. However, the author’s recommendation has some practical downsides, and anyway don’t put “.” in your path!

The great /usr merge makes fully specifying paths more practical.

Don’t Store Passwords in Scripts

This is 100% correct. Anyone with read access to your scripts or your git repo or your backups can pull those passwords right out. The problem with this section of the article is that it doesn’t provide any practical solutions. Anybody that has been doing shell scripting for a time will have run into the password problem, and lots of us have just crammed the password in the script or in a readable file at some point.

There are other better solutions!

First though, another wrong way to do it: specifying passwords on the command line or in environment variables. Don’t do this either! It often seems a better solution than dropping it in the code, but both solutions still expose passwords in many ways.

If your need is for SSH (or related) authentication, consider requiring the script user to type in the password. The password is a way to ensure the user actually has permission to use the resource you’re automating access to.

Often, scripts need to run without user interaction though, so entering a password is not feasible in those cases.

Then you need to limit the damage someone malicious can do if they gain access to the target system. Consider setting up a specific user on the target system that has permissions limited to only what is required for the automated actions. Setup key-based access to that target account, don’t specify a key file password, and don’t provide the target account an actual system password. Place the key file in a location only accessible to the users that should be able to execute the script (maybe you need a custom user or group here, too) and give the file only the minimum required permissions. Now, in your script, invoke SSH, or SCP, etc. with that key file specified.

Using SSH-Agent is probably an even better solution: https://www.akadia.com/services/ssh_agent.html

What if it’s not SSH that you’re sending a password to, what if it’s sudo? I think we’ve all wanted to do this at some point, I confess that I have.

Don’t do it though!

Change your sudo configuration so that for the specific command you need a specific user to run, it doesn’t require a password! Then lock access to that user down some. Even in this case, you’re probably causing problems you don’t expect. Imagine if the target executable has a vulnerability… Now you’re opening the aperture for privilege escalation. A better solution is to change permissions to allow specific users access to the specific resources required, then reduce others’ access to those user accounts.

What if you need to script password sending to telnet or ftp? I don’t have great solutions at hand for you. At some point you may need to put passwords and usernames in files, then limit access to those files, and have your script grab the file contents.

Beware of Invoking Anything the User Inputs

The author suggests that a specific setup and command line can cause, “quite dire consequences.” Try this command for yourself though, after changing the “rm” command to something benign like a version of “ls”.

You didn’t get the output the author suggested you might? That’s because this isn’t really a problem the way he states it.

Oh, the author’s “eval” version might work, but when have you ever written code like that? Please say you haven’t.

It’s definitely possible to accidentally provide ways for users to cause command injection – it’s a very common class of vulnerabilities. Scrubbing the input, as the article author suggests, is a common solution.

One recommendation the author should have made more clear is that when scrubbing input you should permit only data that fits a minimalist whitelist. Don’t try to look for bad characters, like you would with a blacklist… Instead, fail on any characters that aren’t in your specific small set.

More often though, the use case for shell scripts is in accepting input from users already on your system. In this case, the recommendations in the article are lacking.

My main recommendation is to avoid permitting users to run anything as a higher level user, or as a different user at all. If users can only execute scripts as themselves, and the script doesn’t grab extra permissions like via SSH, any commands the user might try to get the script to run would also run with the user’s permissions. Therefore, it’s a thing they could’ve done themselves.

If there are shared resources the script needs that the user shouldn’t normally have, perhaps use sudo within the script to grab/use those resources then drop permissions (as would happen if a separate program did the permissioned work, then ended). Alternatively, create another user with only minimal permissions, including those shared resources, then have the script run as that user.

If the script does have to get extra permissions, like through SSH, avoid sending user input there, or sanitize it with a whitelist (worst case). When I say SSH provides extra permissions, I mean that it does so by providing access to another computer, and potentially another user.

You should try to avoid writing code that eval’s user input, or potentially places it on the command line as multiple arguments, and you should try to sanitize input when you have to take a risk… But the best solution is to design the environment so that even when you inevitably mess up it’s not that big a deal.


Good points Dave, but the recommendations are lacking.

The Apple article he links has better recommendations, and solid examples. It points out that older Bash versions were vulnerable to one of Dave’s examples that I poo-pooed. However, there are many problems with running older Bash versions (you wondered why I mentioned Shellshock?)… Don’t do that anymore. The Apple example also has a great hidden eval command in there – echoing user input to a file then executing the file is essentially eval. Avoid.

Brisket and Salmon Again

I ran out of brisket too quickly last time…  It makes a fantastic sandwich with Philly Cream Cheese (strangely enough).  Then I eat a bunch of salmon on bagels, with Philly Cream Cheese…  The discovery that it’s the right cheese for both sandwiches has made shopping easier.

The brisket is 15.5 lbs, $2.99 per pound.  Salmon is 6 lbs, didn’t get the price before tossing the package.  We have to move in July and I hope I can keep up this hobby at the next stop in life.

1700 30 Dec: Fish begins brining.

2120: Brisket is on the smoker at 225℉, vent half open, fat cap up.  Next temp check should be 8 hours from now at 0520.  Used 4 Tbsp salt, 4 Tbsp pepper, 2 Tbsp garlic powder on the brisket.  I forgot to put water in the tray…  Let’s see what happens.

0520: 163 and 162, next check 0720

0720: 176 and 173…  I missed the crutch temp, but things seem to be proceeding well…  I cranked up the temp to 250.

0920: 193 and 188, smelling real good now…

0940: Fish started drying.

1040: 200 and 201.

1140: 207 and 204.  Close enough!  Meat came off, and I cleaned up the grates to put the fish on.

1210: Fish is on at 120.

1610: Fish is done, right on time.  Final temps were just above 140.

Everything was great, some of the skinny parts of the flat were dry, but not much…  The bark was fantastic.  Crisp, flavorful…  Some parts of the flat were more like roast beef than the brisket normally is.  I think I should try it unwrapped again, but maybe trim less fat on the flat and turn the temp up an hour sooner and see what changes.  Right now though, wrapped seems the way to go.

The water tray was almost full with grease by the end, I think next time I’ll put water in, but don’t think it makes a difference.

Brisket and Salmon!

Just got married, just got honeymooned, just got back to work…  Time for some food!

17 lbs brisket, $61, 6 lbs salmon $9 per pound.  Using the same recipe as usual.

2120: Brisket went on the smoker.  4.5 Tbsp salt, 4.5 Tbsp pepper, 2.25 Tbsp garlic powder.

2200: Fish is brining.

0530: Brisket at 165 and 166.  Crutched it, and turned temperature up to 250.  Also, moved fish to drying.

0845: Brisket at 202 and 189.

0930: Took the flat off, it was done, at 208.

1130: I’ve checked the point temperature periodically this whole time, and it has slowly risen to 201.  I’ll take it off after making coffee.

1145: Point was at 201, close enough, took it off then cleaned the smoker grates and water tray, and removed and chunks.  Set it to reheat to 120.

1230: Fish is on!

1630: All done, delicious.

Next time I need to make sure I slice across the grain.  I mistook the direction of the grain in some of the brisket, I need to double check that next time.  Otherwise I think it all turned out fantastically.

Brisket Numero Quatro

Well, there’s the possibility that the brisket I did last weekend won’t be enough meat…  And there’s the fact that I can get another one done in time…  So I’m making another brisket!  We’ll just try for a repeat performance…

14.1 lbs of brisket at $3.99 a pound…

1045, 21 Jul: Brisket went on, fat cap up, 225℉.  I should check the temperature after 8 hours, at 0645.

0900, 22 Jul: The flat passed 166℉ a couple hours ago, but the point was only at 162℉.  Now, the flat is at about 175℉, while the point is at 165℉…  I wrapped it and increased the smoker temperature to 250℉.  This step took a little longer than last time, but then I was doing to finish up further ahead of the party than I wanted anyway, so the timing should be ok.  After wrapping I kept the fat cap on top.

1200 or so: Brisket got up to temperature, the lean got there first by about 30 minutes.

The party was fantastic!  We had a great time with all the friends that showed up.  We ate almost all of both briskets, leaving only a small container in the end.  I’m very glad I smoked this second one.