56

最近披露了Bash如何解释环境变量的漏洞CVE-2014-6271。该漏洞利用 Bash 将一些环境变量声明解析为函数定义,然后继续执行定义后的代码:

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

但我不明白。我在 Bash 手册中找不到任何关于将环境变量解释为函数的内容(除了继承函数,这是不同的)。事实上,一个正确的命名函数定义只是被视为一个值:

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

但是一个腐败的人什么也没打印:

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

$ # Nothing but newline

损坏的函数是未命名的,所以我不能直接调用它。这个漏洞是纯粹的实现错误,还是这里有我看不到的预期功能?

更新

根据 Barmar 的评论,我假设函数的名称是参数名称:

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

我可以发誓我以前尝试过,但我想我没有足够努力。现在可以重复了。这里还有一点测试:

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

wat

…所以显然在漏洞执行时没有设置参数。

无论如何,我的问题的基本答案是,是的,这就是 Bash 实现继承函数的方式。

4

4 回答 4

49

这似乎是一个实现错误。

显然,导出函数的工作方式bash是它们使用特殊格式的环境变量。如果你导出一个函数:

f() { ... }

它定义了一个环境变量,如:

f='() { ... }'

可能发生的情况是,当新的 shell 看到一个值以 开头的环境变量时(),它会在变量名之前添加变量名并执行结果字符串。错误在于,这也包括在函数定义之后执行任何内容。

所描述的修复显然是解析结果以查看它是否是有效的函数定义。如果不是,它会打印有关无效函数定义尝试的警告。

这篇文章证实了我对错误原因的解释。它还详细介绍了修复如何解决它:它们不仅更仔细地解析值,而且用于传递导出函数的变量遵循特殊的命名约定。这种命名约定与为 CGI 脚本创建的环境变量所使用的命名约定不同,因此 HTTP 客户端永远不能踏入这扇门。

于 2014-09-24T16:59:36.583 回答
16

以下:

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

印刷

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

似乎,Bash 在解析 之后x=...,发现它作为一个函数,导出它,看到declare -fx x并允许在声明之后执行命令。

echo vulnerable

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

印刷:

vulnerable
x () 
{ 
    echo I do nothing
}

并运行x

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

印刷

Vulnerable
Segmentation fault: 11

segfaults - 无限递归调用

它不会覆盖已经定义的函数

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

印刷:

x () 
{ 
    echo Something
}
declare -fx x

例如,x 仍然是先前(正确)定义的函数。

对于 Bash 4.3.25(1)-release,漏洞已关闭,因此

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

印刷

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

但是 -有什么奇怪的(至少对我来说)

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

仍然打印

x () 
{ 
    x
}
declare -fx x

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

分段错误也是如此,所以它仍然接受奇怪的函数定义......

于 2014-09-24T17:12:02.803 回答
13

我认为看看 Bash 代码本身是值得的。该补丁对问题提供了一些见解。尤其是,

*** ../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))

当 Bash 导出一个函数时,它会显示为一个环境变量,例如:

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

当新的 Bash 进程在其环境中发现以这种方式定义的函数时,它会使用parse_and_execute(). 对于正常的、非恶意代码,执行它只是在 Bash 中定义函数并继续。但是,因为它被传递给通用执行函数,所以 Bash 将正确解析并执行在函数定义之后在该变量中定义的附加代码。

您可以看到,在新代码中,SEVAL_ONECMD添加了一个名为的标志,告诉 Bash 只评估第一个命令(即函数定义)并且SEVAL_FUNCDEF只允许函数定义。

于 2014-09-25T15:31:40.520 回答
0

关于您关于文档的问题,请在命令的命令行文档中注意此处,env对语法的研究表明它env按文档说明工作。

  • 可选地,有 4 种可能的选项
  • 作为同义词的可选连字符-i(我假设是为了向后兼容)
  • 零个或多个 NAME=VALUE 对。这些是可以包括函数定义的变量赋值。
  • 请注意,在分配之间或分配之后不需要分号 (;)。
  • 最后一个参数可以是单个命令,后跟它的参数。它将使用已授予正在使用的登录名的任何权限运行。安全性是通过限制登录用户的权限和设置用户可访问的可执行文件的权限来控制的,这样可执行文件所有者以外的用户只能读取和执行程序,而不能更改它。
[ 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/>
于 2014-09-30T08:44:23.830 回答