26

I have been hearing about the Bash Shellshock problem since yesterday and am curious to see where in the source code this problem occurs. I have downloaded the source for Bash 4.2 from here.

Where exactly should I look for Shellshock in the source code of Bash 4.2?

I was able to find this patch information for 4.2 (from this page), but still if someone could explain clearly where Shellshock occurs, it would be helpful.

Peter Mortensen
  • 877
  • 5
  • 10
Jake
  • 1,095
  • 3
  • 12
  • 20
  • 2
    Here are some fixes: [CVE-2014-6271](https://gist.github.com/drj11/e85ca2d7503f28ebfde8) [CVE-2014-7169](https://gist.github.com/drj11/239e04c686f0886253fa) [CVE-2014-718*](http://www.openwall.com/lists/oss-security/2014/09/25/32) – user10008 Sep 27 '14 at 14:53
  • Here's another useful link I found: https://bitbucket.org/carter-yagemann/shellshock/ – Jake Oct 03 '14 at 15:59

1 Answers1

34

CVE-2014-6271

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

From Wikipedia:

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:

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

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, 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 by Tavis Ormandy. The fix 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 'echo date' string that's also given to bash. Its equivalent to rm echo; bash -c '> echo date'; cat echo.

Further resources on the oss-sec mailing list.

user10008
  • 4,315
  • 21
  • 33
  • For 6271 is [this](http://git.savannah.gnu.org/cgit/bash.git/tree/builtins/evalstring.c?id=ac50fbac377e32b98d2de396f016ea81e8ee9961#n366) the place where the extra command after function definition is actually executed ? – Jake Oct 06 '15 at 23:00
  • I think so, yes. – user10008 Oct 07 '15 at 04:44
  • Can you please confirm ? The response I got from another question I asked on same topic was different - see last comment for http://unix.stackexchange.com/a/233097/63934 – Jake Oct 07 '15 at 05:03
  • I can only see your question "is this the function call that contained the original 6271 bug ?", and that has indeed to be answered with no. The bug was fixed elsewhere, so you can't say that it "contained the bug". I confirm my answer to your different question over here: the function does get executed when exploiting 6271. – user10008 Oct 16 '15 at 20:45
  • Although I understand the patch for 6271, I wanted to ask regarding the intention of bash in the first place. If function definition is in shell variable (in environment e.g. `var=() { echo hello; };`), why does bash have to execute it while parsing ? Why doesn't bash just parse it, and if it used e.g. via `bash -c foo`, only then execute the function ? – Jake Oct 30 '15 at 22:04
  • @Jake thats more a software design question. The `parse_and_execute` function has been added in order to have a single point other code can call to execute some shell code, whether from the -c param, the stdin, or a shell script file. Now that's why the function exists the first place. The person who added the "read function definitions from env vars" probably used the function, because for function definitions, it does right what they wanted: it parses the function, without executing it. Now it seems the author ignored that the function does stuff for non-function definitions too. – user10008 Nov 11 '15 at 00:36