CyberSecurity Shell Scripting Uncategorized

Securing your script

Figuring out how to secure your shell script can be difficult. One of the great advantages to using a language such as PHP is some of the security items are already built into the functions you use. Many people believe this makes PHP more secure; however the same amount of security can be achieved with a shell script. Rather than thinking BASH is more or less secure, think of it as there is a greater amount of work and knowledge needed to achieve the same level of security in BASH as there is PHP. This may seem like a disadvantage but as you’ll see you have a greater amount of control over the security even if it is much more difficult to achieve.

I am only going to cover a few basics ideas here. The details of your security are going to depend on many factors especially the functions of your script. Think of this as a good basis of knowledge. I am also not covering any confidentiality issues here. That is a separate discussion. This conversation is only about protecting the script itself.

When you consider security for your shell script, you need to consider each function the script does and the level of protection needed for that function. Here is an example of a simple script that takes input, calculates it, then writes it to a file:

  • Get user input – Lots of protection
  • Calculate input – little to no protection
  • Write output to a file – medium protection

One of the more involved tasks when securing your shell scripts is receiving input. Whether you’re getting it from an outside user or another program, receiving input is the biggest risk when looking at the security landscape for your script. You will have to make up your own mind about what is acceptable, but here are the three basics you need to start with:

  • Minimum and maximum length of input
  • Acceptable characters
  • Acceptable results

Minimum and maximum length

The minimum length could be 0 and isn’t always necessary, but maximum length is ALWAYS required for every input no matter where it came from.

Minimum length is easy to test once you set the input to a variable. Use the internal variable length, let’s say the minimum length is 5:

[[ ${#INPUT} < 5 ]] && echo error

Maximum length is a different animal. Consider an attacker sending ten million characters as an input. By the time you set the input to a variable it’s already too late, you’re now the victims of a denial of service (DOS) attack. The trick is to cut off the input after the maximum has been reached; then ignore anything else.

If you’re using the read command, there is a built in counter you can use. In this example, the maximum length is 10:

read -n 10 INPUT

If I’m reading from most anywhere else, I pass the string through the dd command. For example, if you are pulling data from standard input:

INPUT=$(dd bs=1 count=10 2>/dev/null)

This is a great way of pulling your data from a web API. I rarely write API’s in BASH any more (though, I did it a LOT in the 90’s and early 2000’s). I now much prefer GoLang to write my APIs, but every once in a while I’ll still fall back to BASH. When I’m writing an API in shell, I normally set my raw data to the variable CONTENT_STRING to the GET data like so:

CONTENT_STRING=$(dd bs=512 count=1 2>/dev/null )

Acceptable Characters

The first impression for removing unacceptable characters is to just block the characters you don’t want. This method is easy to set up and seems to makes sense at first. It is however the least secure way of achieving your goal. Rather than blocking characters, allow only the characters you do want then block EVERYTHIG ELSE.

This can actually be done pretty easily with tr -d . Let’s take a look:

# Only accept numbers
[[ -z $(echo "$INPUT" | tr -d "[0-9]") ]] && ERROR="invalid characters"

# Numbers or letters
[[ -z $(echo "$INPUT" | tr -d "[1-9a-zA-Z]") ]] && ERROR="invalid characters"

# Numbers, letters, @.+_- (like an email address)
[[ -z $(echo "$INPUT" | tr -d "[A-Za-z0-9@.+_-]") ]] && ERROR="invalid characters"

This looks a little complicated so take it a little at a time.

[[ -z : If the variable is zero

$(echo "$INPUT" | : You’re echoing the contents of your input…

tr -d "[0-9]") ]] : and removing all numbers.

&& ERROR= : if the statement is true, write to your error variable

To summarize, I’m deleting all valid characters from my variable, if there is anything left, show an error.

Acceptable results

This is where you get into the real details of your results. It’s only really useful if you have a small limited number of results, or results which require specific things. There are a million ways you can make these checks, but one of the most useful is the case command. Partially because it will narrow the results a user can input, but also because you can do things easily depending on the result. In addition, case let’s you use regular expressions to make your script a little more user friendly. Let’s look at an example where the only acceptable results are yes or no.

case $X in
        [yY][eE][sS]|[yY]) echo yes;;
        [nN][oO]|[nN]) echo no;;
        *) echo error;;

for yes, this will accept “yes”, “YES”, “Yes”, “Y” or “y.” If anything but some form of yes or no are the input, it will leave an error


File permissions have been built into UNIX almost since it’s beginning and yet many administrators set all files to 777. To write secure scripts it’s very important you know and understand the different parts of the permissions system.

While most people believe file permissions are just about those ten characters which show up before a file in the ls command, but there is a bit more to it than that. There are lots of ways to set up your permissions, much more than I can possibly talk about in this blog entry; Instead, I would like to go over some of the parts you should be thinking about when looking over your permissions.

File Permissions There are three pieces to the permissions on a file, the owner, the group and the permissions. You can’t ignore any of them. Make sure they are set correctly for not only your script, but any file you’re script will create or alter.

Running permissions The permissions of a running processes is normally the same as an at rest process, but not always. The su and sudo command can change those permissions. The same is true for any application your script kicks off. It’s important to make sure these are running correctly.

Users and Groups Make sure you check your /etc/group file and know what other groups the user your running your script as are in. Even more importantly, know what other users are in the group your script is running. This will keep access away from rouge users on the system.

umask Check your umask command before you start writing to files. This mask is what decides the permissions a file will have when you write it. It’s easy to change with the same command.

selinux I am honestly not a huge fan of selinux. I believe it catches too many false positives for the little it does beyond correctly using the built in permissions system. There are a few special cases where it can be beneficial and you may love it. I however find it more of a burden than a help.

Additions and Notes

When using the tr command keep in mind the alphabet order you’re using. Many Linux’s changed the default order of the alphabet a few years back and you should be aware of which you’re using. At some point I’m going to do a write up about alphabet order as it was a sore spot for me when it changed (caused me weeks pain and work trying to track down problems).

These items should considered a starting point touching on major issues to think about, not an all inclusive document.

While I use bash in my examples, every one of these items needs to be kept in mind when writing in any language.

As mentioned, this is not about confidentiality. That is a separate (and much longer) discussion. In this case, I’m only concerned with protecting the script.

Are there any issues or methods you feel I’ve left out which should be mentioned? Please let me know in the comments!

Leave a Reply

Your email address will not be published. Required fields are marked *