« Previous 1 2 3 4 Next »
Best practices for secure script programming
Small Wonders
Stricter Execution Verification
Shell scripts are intended for non-programmers to speed up common tasks in their daily lives on Unix operating systems. As a courtesy to this group of users, virtually all shells are set to be "permissive": In case of an error, they try to continue executing the script. This approach, which is quite friendly for a less technically experienced user, is critical because ambiguities that cause security problems do not stop the execution of the script.
The previously mentioned problem of destroying files would not have occurred with the following shell setting:
#!/bin/bash set -o nounset TAMS_VAR="wrongval" echo /bin/${ENV}_VAR
When called with the -o
parameter, the set
command activates execution options that influence the runtime behavior of the interpreter. The nounset
option used here considers unset variables to be errors. Execution of the shell script shown before now fails with the error shown in Figure 4.
The set -o errexit
command terminates a program if a command called by the shell script does not return a value of zero. Its use can be checked by copying a non-existent file:
#!/bin/bash set -o errexit cp "doesnotexist" "tree.txt" echo "Successfully copied"
The cp
command does not return zero if it fails to find a parameter, which is why the status message passed to echo
does not appear at the command line.
In scripts, you will always run into situations in which some of the code to be executed is non-critical, and the commands specified by -o
can be disabled by calling +o
. In the following script, the errexit
option is no longer active when the copy command is processed:
#!/bin/bash set -o errexit set +o errexit cp "doesnotexist" "tree.txt" echo "Succesfully copied"
A problem can occur when processing commands connected by a pipe. Normally, this kind of command only fails if the last command in the pipe sequence does not return a value of zero. If you want errors somewhere in the hierarchy to cause a termination, then enable the pipefail
option with the command set -o pipefail
.
Changes to Global Variables
Another unintended result of lax language syntax is that variables defined in subfunctions develop unexpected side effects. As an example, look at a script that uses the variable o
both inside and outside a function:
#!/bin/bash hello_world () { o=2 echo 'hello, world' } o=1 echo $o hello_world echo $o
Developers who grew up with other scripting languages would expect both calls to echo $o
to return the value 1
. This is not inherently the case; the changes made in the function remain valid even after they have been executed. To fix the problem, you just need to tag the variables in the function with the local
keyword:
#!/bin/bash hello_world () { local o=2 echo 'hello, world' } o=1 echo $o hello_world echo $o
Although longer shell scripts are not necessarily recommended for maintenance reasons, you should always protect variables used in functions with the local
keyword. A colleague that recycles shell code at some point will be better protected from unexpected side effects.
ShellCheck
C and C++ programmers have used static analysis to check the results of their work for a long time. A static analysis tool has a knowledge base in which it stores programming errors that it checks against the code at hand. A similar tool for shell scripts is available with ShellCheck [6]. The tool, licensed under the GPLv3, behaves much like lint
in terms of syntax: It expects the name of the shell file as a parameter, which it then checks for dodgy elements.
ShellCheck doesn't only look for security-relevant errors. In a list of ShellCheck checks available online [7], four dozen test criteria also identify and complain about general programming errors. Last but not least, a web version of the tool [8] checks scripts for correctness and security without a local installation.
« Previous 1 2 3 4 Next »
Buy this article as PDF
(incl. VAT)