1. Overview

A shell is a powerful tool that serves as an interface between the user and the operating system. It allows users to interact with the system through commands and scripts. Bash and POSIX (/bin/sh) are two popular Linux shells.

Shell scripts are handy for IT professionals, as they help automate repetitive tasks. Although it eases our various tasks, it’s still important for us to prioritize security when building shell scripts. In this tutorial, we’ll delve into why quoting variables in a Bash script is crucial, and the security risks of them being left unquoted.

2. Importance of Quoting Variables

We can consider variables as containers that store information for us to use later. While developing Bash/POSIX scripts, neglecting to quote a variable leads shell scripts to expand the variable, which later interprets it as multiple arguments.

Let’s write an example.sh script where a variable stores a message without quotation:

#!/bin/bash
message=$1
echo $message

When we execute the following command in Bash, we might expect it to print “Hello World” in the terminal on the same line, however, it doesn’t happen:

$ ./example.sh "Hello World"
Hello
World

In the example above, we see that the script uses the echo command twice: once for “Hello” and again for “World”. Consequently, it treats the variable as two separate inputs instead of one. By enclosing the variable message on the echo line in quotes, such as echo “$message”, the script would interpret everything inside the quotes as a single argument, regardless of its content.

3. Security Risks and Mitigation Strategy

Failure to quote variables can have various security risks. In this section, we’ll look into how this can cause issues like information disclosure and arbitrary code execution.

3.1. Information Disclosure

An attacker can exploit unquoted variables to list directories or access sensitive files, which can expose sensitive information. Let’s examine a Bash script named read_file.sh that employs unquoted variables, leading to information disclosure:

#!/bin/bash
if [ "${1##*.}" != "txt" ]; then
    echo "Only text files allowed"
    exit 1
fi
filename=$1
contents=$(cat $filename)
echo "$contents"

In the provided code, ${1##*.} extracts the file extension from the filename. It then checks if the filename has a txt extension; if not, it exits the script. The script expects a filename argument from the command line. When provided with a filename with a txt extension, it prints the file’s contents with the echo command:

$ ./read_file.sh baeldung.txt
---Baeldung.txt---
You are currently viewing baeldung file.
---End of Baeldung.txt---

As demonstrated, it prints the file’s content. However, passing incorrect arguments triggers a rejection message:

$ ./read_file.sh secret
Only text files allowed

In the previous example, we were unable to directly access the secret file. However, by passing an argument with a space, the script allows the argument to split:

$ ./read_file.sh 'secret baeldung.txt'
***Disclaimer: You are accessing secret. Please be sure you are authorized***
Hey! this is a sensitive file.
***End of Secret***
---Baeldung.txt---
You are currently viewing baeldung file.
---End of Baeldung.txt---

As illustrated above, we successfully loaded the secret file. The presence of the baeldung.txt file with a valid txt extension meets the script’s requirements. Moreover, the space in secret baeldung.txt’ results in the script interpreting it as two separate arguments, allowing us to access the secret file.

3.2. Arbitrary Code Execution

This can be counted as one of the most severe consequences of an unquoted variable. An attacker can use such unquoted variables to inject commands, leading to unauthorized access or system compromise. Let’s illustrate this with an example.

First, let’s create a file filename.txt with the following content:

a b c
d e f
g h i

Next, let’s define a variable char_to_search to hold the character we want to search for in the first column of filename.txt, and use the awk command to find the pattern:

$ char_to_search='a'
$ awk -v var=$char_to_search '$1 == var' filename.txt

The above command searches for a in the first column and outputs the matching line:

a b c

Now, let’s see how an attacker could exploit an unquoted variable to execute arbitrary code:

$ user_input='a BEGIN{system("whoami")}'
$ awk -v char_to_search=$user_input '$2 == char_to_search' filename.txt
ekuta

In the above example, we can observe the whoami command is unintentionally executed due to the unquoted user_input variable, resulting in ekuta being output. This is possible because commands like awk and find can execute other commands. The script first checks for a, and then, due to the unquoted user_input, it treats the space as a delimiter and interprets the BEGIN{system(“whoami”)} part as a separate argument. This causes it to be executed as an awk command, leading to arbitrary command execution.

3.3. Mitigation Strategy

The most straightforward way to address this vulnerability is to use quoted variables. However, if there are reasons not to use quotes, it’s essential to properly validate user input. This involves checking for any special characters, utilizing arrays to store each word and then individually verifying whether the user is performing intended actions or carry out malicious operations.

4. Conclusion

In this article, we’ve discussed why putting quotes around variables in shell scripts is important and the risks associated with skipping them. Using quotes around variables is a fundamental aspect of writing secure and robust code. If we leave security vulnerabilities while developing the script, it helps attackers in different hacking phases, one of which is privilege escalation. All in all, it’s important to use the quotes in variables while building the shell scripts.