Podcast #128: We chat with Kent C Dodds about why he loves React and discuss what life was like in the dark days before Git. Listen now.
1 of 4

CVE-2014-6271

CVE-2014-6271 was the first vulnerability discovered. A patch can be found here.

From wikipedia explanation:

Function definitions are exported by encoding them within the environment variable list as variables whose values begin with parentheses ("()") followed by a function definition. The new instance of Bash, upon starting, scans its environment variable list for values in this format and converts them back into internal functions.

Bash performs this conversion by creating a fragment of code that defines the function and executing it, but it does not verify that the fragment is merely a function definition. Therefore anyone who can cause Bash to execute with a particular name/value pair in its environment, can also execute arbitrary commands by appending those commands to an exported function definition.

In the source code, we can see this in variables.c:

/* Initialize the shell variables from the current environment.
   If PRIVMODE is nonzero, don't import functions from ENV or
   parse $SHELLOPTS. */
void
initialize_shell_variables (env, privmode)
     char **env;
     int privmode;
{
  [...]
  for (string_index = 0; string = env[string_index++]; )
    {

      [...]
      /* If exported function, define it now.  Don't import functions from
     the environment in privileged mode. */
      if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))
      {
        [...]
        parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
        [...]
      }
}

We can see a for loop over all the environment variables given to the function, and then an if about whether we are in privileged mode, but that is disabled most times. The "not verify that the fragment is merely a function definition" vulnerability is in the parse_and_execute line. The function description from builtins/evalstring.c:

/* Parse and execute the commands in STRING.  Returns whatever
   execute_command () returns.  This frees STRING.  FLAGS is a
   flags word; look in common.h for the possible values.  Actions
   are:
    (flags & SEVAL_NONINT) -> interactive = 0;
    (flags & SEVAL_INTERACT) -> interactive = 1;
    (flags & SEVAL_NOHIST) -> call bash_history_disable ()
    (flags & SEVAL_NOFREE) -> don't free STRING when finished
    (flags & SEVAL_RESETLINE) -> reset line_number to 1
*/
int
parse_and_execute (string, from_file, flags)
     char *string;
     const char *from_file;
     int flags;
{

The patch introduces flags SEVAL_FUNCDEF and SEVAL_ONECMD that can be passed in the flags field to parse_and_execute:

+ #define SEVAL_FUNCDEF 0x080       /* only allow function definitions */
+ #define SEVAL_ONECMD  0x100       /* only allow a single command */

The patch also adds functionality to parse_and_execute to comply with those new flags, and changes the call to parse_and_execute to pass those flags:

-     parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
+     /* Don't import function names that are invalid identifiers from the
+        environment. */
+     if (legal_identifier (name))
+       parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD);