Photo by Jorge Fernández Salas on Unsplash

Photo by Jorge Fernández Salas on Unsplash

Best practices for secure script programming

Small Wonders

Article from ADMIN 64/2021
By
The lax syntax verification of shell scripts and a lack of attention to detail in programming can create impressively dangerous security vulnerabilities.

Holistic system security means paying attention to even the smallest detail to avoid it becoming an attack vector. One example of these small, barely noticed, but still potentially very dangerous parts of IT that repeatedly cause serious security issues is shell scripts. Employing best practices can ensure secure script programming.

Letting Off Steam

Impressive proof of the potential harmfulness of shell scripts was provided by US software vendor Valve. The Linux-based version of the Steam game service included a script that was normally only responsible for minor setup tasks [1]. Unfortunately, it contained the following line:

rm -rf "$STEAMROOT/"*

This command, which is responsible for deleting the $STEAMROOT directory, gets into trouble if the environment variable is not set. Bash does not throw an error but simply "disassembles" the environment variable into an empty string. The reward for this is the command

rm -rf /*.

which works its way recursively through the entire filesystem and destroys all information.

Some users escaped total ruin by running their Steam execution environment under an SELinux chroot jail. Others were not so lucky, so it is time to take a closer look at defensive programming measures for shell scripts.

Defining the Shell Variant

On Unix-style operating systems, dozens of shells are available – similar only in their support of the POSIX standard – that come with various proprietary functions. If shell-specific code from one shell is used in other shells, the result is often undefined behavior, which might not be a problem in a controlled VM environment, but deployment in a Docker or other cluster changes the situation.

The most common problem is the use of the #!/bin/sh sequence, or "shebang," which on Unix, is the first line of a script. According to the POSIX standard, the shebang affects the shell pre-selected by the system. It determines which interpreter is to be used for processing the script. The terms sha-bang, hash-bang, pound-bang, or hash-pling can also be found in the literature.

Bash, which is widely used among script programmers, is rarely the default shell. On a workstation based on Ubuntu 18.04, you can check the default by entering the which command. The which sh command returns the output /bin/sh, and the which bash command returns /bin/bash.

Several methods help you work around this problem. The simplest is to stipulate explicitly the use of Bash with #!/bin/bash in the shebang. Especially in cloud environments, it is not a good idea to assume the presence of the Bash shell. If you build your script without specific instructions, you can save yourself some extra work during deployment.

The checkbashisms command helps administrators find and remove Bash-specific program elements. To install the tool, you need to load the devscripts package then create a small shell script (here, itaworker.sh) that contains a Bash-specific for loop:

!#/bin/sh
for ((i=0; i<3; i++)); do echo "$i"
done

If you parse this script with checkbashisms, it complains about the loop (Figure 1). Note that the tool only warns you of Bash-specific code if the shebang does not reference Bash. The following version of the script,

#!/bin/bash
for ((i=0; i<3; i++)); do echo "$i"
done
Figure 1: The checkbashisms command identifies Bash-specific code.

passes the checkbashisms check without problems.

Saving Passwords Securely

Administrators like to use shell scripts to automate system tasks, such as copying files or updating information located on servers, which requires a password or username or both that should not be shared with the general public.

On the other hand, you do need to provide the credentials or enter them manually every time you run the script. Attackers like to capture all the shell scripts they can get on hijacked systems. If they find a group of passwords in the process, the damage done is multiplied.

The first way to fulfill this condition is to pass the credentials to the script as parameters. The following procedure would be suitable:

#!/bin/bash
a=$1
b=$2
while [ TRUE ]; do sleep 1;
done

The variables $1 and $2 stand for the first and second parameters. This script is run by typing ./itaworker.sh tam pass, which is critical from a security point of view. The Linux command-line tool ps for listing running processes can output the parameters entered in the call if so desired. Figure 2 shows how the credentials can be delivered directly to the attacker's doorstep; entering ps is one of an attacker's first steps.

Figure 2: The use of parameters with credentials is not conducive to security.

If dynamic parameterization of the shell script is absolutely necessary, the recommended approach is to store the credentials in environment variables. These cannot be found by an ordinary ps scan, because access to /proc/self/environ requires advanced user rights if the configuration is correct. However, this approach is not universally accepted – the Linux Documentation Project [2] recommends providing the information through a pipe or through redirection.

Consensus across the board in literature is that it is definitely better if the password is not found in plain text in the script file. One way to achieve this is to use a reversible hash function that converts its output values, also known as the "salt," back to the original value.

If you use a program for salt conversion that the attacker does not catch in a scan, you can make life more difficult for them. If the attacker wants to have the credentials in plain text, they have to log in again and run the command. Ideally, the binary file will belong to an account that is exclusively used for shell script execution.

Option number two (Figure 3) relies on a separate configuration file that has different read permissions. As long as the user responsible for executing the scripts is the only one who is allowed to read this file, an attack on the other user accounts is not critical in terms of credentials.

Figure 3: The Unix access control system protects the password configuration file against unauthorized access.

To hide the structure of the script, you can use shc [3], developed by Francisco Rosales. The package can be installed by the package manager on Ubuntu. The call expects the -f parameter to specify the source file:

shc -f itaworker.sh
ls itaworker.sh

After processing, you will find two additional files: In addition to the executable file with the extension .X is the file .X.C, which provides the C code of the script.

SHC wraps the shell script in a C wrapper, which is then converted into a binary file by the C compiler. If you delete the itaworker.sh and itaworker.sh.x.c files, it is more difficult for the attacker to capture the code of the script later on. It should be noted that the wide distribution of SHC has led to the existence of decompilation tools [4].

Buy this article as PDF

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

Buy ADMIN Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus