Is the behavior behind the Shellshock vulnerabilit

2020-01-27 03:53发布

问题:

A recent vulnerability, CVE-2014-6271, in how Bash interprets environment variables was disclosed. The exploit relies on Bash parsing some environment variable declarations as function definitions, but then continuing to execute code following the definition:

$ x='() { echo i do nothing; }; echo vulnerable' bash -c ':'
vulnerable

But I don't get it. There's nothing I've been able to find in the Bash manual about interpreting environment variables as functions at all (except for inheriting functions, which is different). Indeed, a proper named function definition is just treated as a value:

$ x='y() { :; }' bash -c 'echo $x'
y() { :; }

But a corrupt one prints nothing:

$ x='() { :; }' bash -c 'echo $x'

$ # Nothing but newline

The corrupt function is unnamed, and so I can't just call it. Is this vulnerability a pure implementation bug, or is there an intended feature here, that I just can't see?

Update

Per Barmar's comment, I hypothesized the name of the function was the parameter name:

$ n='() { echo wat; }' bash -c 'n'
wat

Which I could swear I tried before, but I guess I didn't try hard enough. It's repeatable now. Here's a little more testing:

$ env n='() { echo wat; }; echo vuln' bash -c 'n'
vuln
wat
$ env n='() { echo wat; }; echo $1' bash -c 'n 2' 3 -- 4

wat

…so apparently the args are not set at the time the exploit executes.

Anyway, the basic answer to my question is, yes, this is how Bash implements inherited functions.

回答1:

This seems like an implementation bug.

Apparently, the way exported functions work in bash is that they use specially-formatted environment variables. If you export a function:

f() { ... }

it defines an environment variable like:

f='() { ... }'

What's probably happening is that when the new shell sees an environment variable whose value begins with (), it prepends the variable name and executes the resulting string. The bug is that this includes executing anything after the function definition as well.

The fix described is apparently to parse the result to see if it's a valid function definition. If not, it prints the warning about the invalid function definition attempt.

This article confirms my explanation of the cause of the bug. It also goes into a little more detail about how the fix resolves it: not only do they parse the values more carefully, but variables that are used to pass exported functions follow a special naming convention. This naming convention is different from that used for the environment variables created for CGI scripts, so an HTTP client should never be able to get its foot into this door.



回答2:

The following:

x='() { echo I do nothing; }; echo vulnerable' bash -c 'typeset -f'

prints

vulnerable
x () 
{ 
    echo I do nothing
}
declare -fx x

seems, than Bash, after having parsed the x=..., discovered it as a function, exported it, saw the declare -fx x and allowed the execution of the command after the declaration.

echo vulnerable

x='() { x; }; echo vulnerable' bash -c 'typeset -f'

prints:

vulnerable
x () 
{ 
    echo I do nothing
}

and running the x

x='() { x; }; echo Vulnerable' bash -c 'x'

prints

Vulnerable
Segmentation fault: 11

segfaults - infinite recursive calls

It doesn't overrides already defined function

$ x() { echo Something; }
$ declare -fx x
$ x='() { x; }; echo Vulnerable' bash -c 'typeset -f'

prints:

x () 
{ 
    echo Something
}
declare -fx x

e.g. the x remains the previously (correctly) defined function.

For the Bash 4.3.25(1)-release the vulnerability is closed, so

x='() { echo I do nothing; }; echo Vulnerable' bash -c ':'

prints

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'

but - what is strange (at least for me)

x='() { x; };' bash -c 'typeset -f'

STILL PRINTS

x () 
{ 
    x
}
declare -fx x

and the

x='() { x; };' bash -c 'x'

segmentation faults too, so it STILL accept the strange function definition...



回答3:

I think it's worth looking at the Bash code itself. The patch gives a bit of insight as to the problem. In particular,

*** ../bash-4.3-patched/variables.c 2014-05-15 08:26:50.000000000 -0400
--- variables.c 2014-09-14 14:23:35.000000000 -0400
***************
*** 359,369 ****
      strcpy (temp_string + char_index + 1, string);

!     if (posixly_correct == 0 || legal_identifier (name))
!       parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
!
!     /* Ancient backwards compatibility.  Old versions of bash exported
!        functions like name()=() {...} */
!     if (name[char_index - 1] == ')' && name[char_index - 2] == '(')
!       name[char_index - 2] = '\0';

      if (temp_var = find_function (name))
--- 364,372 ----
      strcpy (temp_string + char_index + 1, string);

!     /* Don't import function names that are invalid identifiers from the
!        environment, though we still allow them to be defined as shell
!        variables. */
!     if (legal_identifier (name))
!       parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD);

      if (temp_var = find_function (name))

When Bash exports a function, it shows up as an environment variable, for example:

$ foo() { echo 'hello world'; }
$ export -f foo
$ cat /proc/self/environ | tr '\0' '\n' | grep -A1 foo
foo=() {  echo 'hello world'
}

When a new Bash process finds a function defined this way in its environment, it evalutes the code in the variable using parse_and_execute(). For normal, non-malicious code, executing it simply defines the function in Bash and moves on. However, because it's passed to a generic execution function, Bash will correctly parse and execute additional code defined in that variable after the function definition.

You can see that in the new code, a flag called SEVAL_ONECMD has been added that tells Bash to only evaluate the first command (that is, the function definition) and SEVAL_FUNCDEF to only allow functio0n definitions.



回答4:

In regard to your question about documentation, notice here in the commandline documentation for the env command, that a study of the syntax shows that env is working as documented.

  • There are, optionally, 4 possible options
  • An optional hyphen as a synonym for -i (for backward compatibility I assume)
  • Zero or more NAME=VALUE pairs. These are the variable assignment(s) which could include function definitions.
  • Note that no semicolon (;) is required between or following the assignments.
  • The last argument(s) can be a single command followed by its argument(s). It will run with whatever permissions have been granted to the login being used. Security is controlled by restricting permissions on the login user and setting permissions on user-accessible executables such that users other than the executable's owner can only read and execute the program, not alter it.
[ spot@LX03:~ ] env --help
Usage: env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]
Set each NAME to VALUE in the environment and run COMMAND.

  -i, --ignore-environment   start with an empty environment
  -u, --unset=NAME           remove variable from the environment
      --help     display this help and exit
      --version  output version information and exit

A mere - implies -i.  If no COMMAND, print the resulting environment.

Report env bugs to bug-coreutils@gnu.org
GNU coreutils home page: <http://www.gnu.org/software/coreutils/>
General help using GNU software: <http://www.gnu.org/gethelp/>
Report env translation bugs to <http://translationproject.org/team/>