## CVE-2014-6271 ##

[CVE-2014-6271](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-6271) was the first vulnerability discovered. A patch can be found [here](https://gist.github.com/drj11/e85ca2d7503f28ebfde8).

From [Wikipedia](https://en.wikipedia.org/wiki/Shellshock_%28software_bug%29#CVE-2014-6271_vulnerability_details):

> 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 the importing of the function variables in [`variables.c`](http://git.savannah.gnu.org/cgit/bash.git/tree/variables.c?id=ac50fbac377e32b98d2de396f016ea81e8ee9961#n315):

    /* 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`](http://git.savannah.gnu.org/cgit/bash.git/tree/builtins/evalstring.c?id=ac50fbac377e32b98d2de396f016ea81e8ee9961#n178):

    /* 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;
    {

So everything that's passed to the function gets executed as if it would be an ordinary bash command. The flags `SEVAL_NONINT` and `SEVAL_NOHIST` are self-explanatory ([explanation of interactivity](http://www.tldp.org/LDP/abs/html/intandnonint.html), `NOHIST` doesn't add the definition to your bash history) don't prevent passing other things than function definitions. 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);

## CVE-2014-7169 ##

CVE-2014-7169 bases on a function parsing issue that has been [pointed out](https://twitter.com/taviso/statuses/514887394294652929) by Tavis Ormandy. [The fix](https://gist.github.com/drj11/239e04c686f0886253fa) of `parse.y` seems very simple, but its trickier than CVE-2014-6271:


    /* Called from shell.c when Control-C is typed at top level.  Or
       by the error rule at top level. */
    void
    reset_parser ()
      [...]
      FREE (word_desc_to_read);
      word_desc_to_read = (WORD_DESC *)NULL;
    + eol_ungetc_lookahead = 0;
    +
      current_token = '\n';	/* XXX */
      last_read_token = '\n';
      token_to_read = '\n';

The `eol_ungetc_lookahead` variable is explained at its definition:

    /* This implements one-character lookahead/lookbehind across physical input
       lines, to avoid something being lost because it's pushed back with
       shell_ungetc when we're at the start of a line. */
    static int eol_ungetc_lookahead = 0;

Its read inside the `shell_getc` function, and if its set, its (one-character) content is read instead.

The command `rm echo; env -i X='() { function a .>\' bash -c 'echo date'; cat echo` first creates a syntax error with the `.` character (you can also use other characters here, like `a` or `=`), and then uses the insufficient cleanup of the `eol_ungetc_lookahead` variable in the `reset_parser` function to inject the `>` character into the invocation of bash. Its equivalent to `rm echo; bash -c '> echo date'; cat echo`.

Further resources [on the oss-sec mailing list](http://seclists.org/oss-sec/2014/q3/679).