Best practices for secure script programming

Small Wonders

Vulnerable Temporary Files

Assume a script needs to make the credentials available to other applications. Temporary files are often used for this purpose. If you create the file in the /tmp/ folder, it should disappear after the script is processed. A classic example of this is the following script, which creates a file with a password:

SECRETDATA="ITA says hello"
echo > /tmp/mydata
chmod og-rwx /tmp/mydata
echo "$SECRETDATA" > /tmp/mydata

Problems arise if an attacker monitors the temp file with the very fast acting FileSystem Watcher API. In the case of the present script, the attacker could call fopen and tag the file as open to harvest the information deposited there immediately or later.

As before with plaintext passwords, the safest way is not to write the credentials to a temporary file. However, if this is essential to perform a function, the following approach is recommended:

SECRETDATA="ITA says hello"
umask 0177

First, the umask command assigns the access attributes passed in as parameters to newly created files. Second, the mktemp command creates a random file name each time, which makes configuring the FileSystem Watcher API more difficult. Numerous sources are critical of the use of temporary files in shell scripts in general [5].

Securing Against Incorrect Parsing

The most frequent point of criticism levied against the shell execution environment is that it lacks methods for string processing. In combination with passing in variables directly to the shell, serious vulnerabilities arise. A classic example is a script that reads in a parameter from STDIN and executes it with an eval command:

read BAR
eval $BAR

The eval command is normally intended to perform calculations and harmless commands. One legitimate use case would be to deliver an echo command (with a pipe redirect if needed):

echo hello

A malicious user would not pass an echo call with redirection at this point but a command such as rm, as mentioned earlier. As a matter of principle, the shell does not recognize this, which is why the script destroys data.

Because some eval-based tasks are not feasible without this command, shell-checking tools like shellcheck do not complain about the use of the command. The script

read BAR
eval "$BAR"

would trigger the error message Double quote to prevent globbing and word splitting . However, because this change does not verify the values contained in BAR, the vulnerability still exists. From a security perspective, the only correct way to handle the eval command is not to use it at all. If this is not an option, be sure to check the commands entered for strings like rm and for non-alphanumeric characters.

Queries are another way to provoke problems. In the next example, I'll look at a script that checks to see whether the user enters the string foo. The payload, which here consists only of a call to echo, should only be executed if foo is entered:

read FOO
if [ x$FOO = xfoo ] ; then echo $FOO

In a superficial check of the script behavior, the input (e.g., itahello ) would not lead to the execution of the payload. However, if you pass a string following the pattern foo = xfoo <any>, the payload is processed:

foo = xfoo -o sdjs
foo = xfoo -o sdjs

Damage occurs if something more sensitive, such as an eval call, needs to be secured instead of the echo payload. In the worst case, the payload then contains a command that causes damage because of invalid values.

To work around this problem, you can use quoting, as in other shell application scenarios:

if [ "$FOO" = "foo" ] ; then echo $FOO

The "new" version of the script evaluates the content of the variable completely, which results in the rejection of invalid input:

foo = xfoo

Quoting is even more important when working with older shells. A program built inline with the following pattern could be prompted to execute arbitrary code by entering a semicolon-separated string:

read VAL
echo $VAL

The cause of this problem is that older shells do not parse before substituting the variable: The semicolon would split the command and cause the section that followed to be executed.

Ruling Out Parsing Errors

The rather archaic language syntax sometimes makes for strange behavior, as in the following example, which is supposed to call the file /bin/tool_VAR:

echo /bin/$ENV_VAR

If you run this at the command line, you will see that it uses the value stored in the variable ENV_VAR. The shell separates echo /bin/$ENV_VAR at the underscore. To work around this problem, it is advisable to set up all access to variables in curly brackets as a matter of principle. A corrected version of the script would be:

echo /bin/${ENV}_VAR

Running the corrected script shows that the string output is now correct:


Problem number two concerns the analysis of the shell user permissions. The $UID and $USER variables can be used to analyze the permissions of the currently active user:

if [ $UID = 100 -a $USER = "myusername" ] ; then
  cd $HOME

Unfortunately, the two variables are not guaranteed to contain the value corresponding to the active user at runtime. Although many shells now save the variable $UID, this is usually not the case for $USER: In older shells it is even possible to provide both variables with arbitrary values. To circumvent this problem, it is a good idea to determine the username and user ID by calling operating system commands. The values returned in this way can only be influenced by setting the path variable, which complicates the attack.

In particularly critical execution environments, it is always advisable to write out paths in full. For example, if you call echo by its path /bin/echo, the activated program is independent of the path variable.

Finally, I should point out that the Bash shell supports a special version of the shebang: #!/bin/bash -p. This variant instructs the shell not to execute the .bashrc and .profile files as part of the startup. The SHELLOPTS variable and the start scripts created in ENV and BASH_ENV are also cast aside in this operating mode. The purpose of this procedure is that manipulations of the environment carried out by the attacker cannot be activated by random script executions.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy ADMIN Magazine

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus
Subscribe to our ADMIN Newsletters
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs

Support Our Work

ADMIN content is made possible with support from readers like you. Please consider contributing when you've found an article to be beneficial.