Bash 4.3.30 及之后的版本

注意,本文所讲的表现仅适用于 Bash 4.3.30 及之后的版本,

1
2
3
4
5
6
7
root@kali:~# bash --version
GNU bash,版本 5.0.16(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
许可证 GPLv3+: GNU GPL 许可证第三版或者更新版本 <http://gnu.org/licenses/gpl.html>

本软件是自由软件,您可以自由地更改和重新发布。
在法律许可的情况下特此明示,本软件不提供任何担保。
  • 所谓的环境变量的真实面目其实就是个任意字符串
  • Bash 在启动时会将 environ 数组中包含 = 号的字符串导入成为自己的变量
  • Bash 在启动外部命令时会将自己内部标记为环境变量的变量重组成字符串数组赋值给 environ

本文中继续深入讲三点:

  • environ 数组中可能存在 = 左边名字相同的元素,也就是同名的环境变量,Bash 是怎么导入的?
  • Bash 还可以从环境变量中导入函数,甚至同时导入两个同名的变量和函数
  • Bash 还可以同时导出两个同名的变量和函数

如果有两个同名的环境变量,很简单,那么后面的值会覆盖前面的:

1
2
root@kali:~# env foo=1 foo=2 bash -c 'echo $foo'
2

Bash 其实是可以从环境变量中导入函数的,比如下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@kali:~# foo(){ echo test; }
root@kali:~# foo
test
root@kali:~# export -f foo
root@kali:~# foo
test
root@kali:~# set | grep foo
_=foo
foo ()
root@kali:~# env | grep foo
BASH_FUNC_foo%%=() { echo test
root@kali:~# export | grep foo

root@kali:~# bash # 进入子shell
root@kali:~# foo
test

set:显示(设置)shell变量 包括的私有变量以及用户变量,不同类的shell有不同的私有变量 bash,ksh,csh每中shell私有变量都不一样

env:显示(设置)用户变量变量

export:显示(设置)当前导出成用户变量的shell变量。

1
2
3
4
5
6
7
8
9
10
[oracle@zhou3 ~]$ aaa=bbb --shell变量设定   
[oracle@zhou3 ~]$ echo $aaa
bbb
[oracle@zhou3 ~]$ env| grep aaa --设置完当前用户变量并没有
[oracle@zhou3 ~]$ set| grep aaa --shell变量有
aaa=bbb
[oracle@zhou3 ~]$ export| grep aaa --这个指的export也没导出,导出变量也没有
[oracle@zhou3 ~]$ export aaa --那么用export 导出一下
[oracle@zhou3 ~]$ env| grep aaa --发现用户变量内存在了
aaa=bbb

总结:linux 分 shell变量(set),用户变量(env), shell变量包含用户变量,export是一种命令工具,是显示那些通过export命令把shell变量中包含的用户变量导入给用户变量的那些变量.

上一级的 Shell 把函数传给了它的 child shell,Bash 是怎么实现的呢?我们用 env 命令演示一下:

1
2
root@kali:~# env 'BASH_FUNC_foo%%=() { echo test2; }' bash -c 'foo'
test2

其实 Bash 就是把满足 BASH_FUNC_函数名%%=(){ 函数体 格式的环境变量作为函数源码解析并导入。所以两个同名的变量和函数并不会冲突,可以同时导入,像这样:

1
2
root@kali:~# env 'foo=1' 'BASH_FUNC_foo%%=() { echo $1; }' bash -c 'foo $foo'
1

既然可以同时导入,那么导出更没问题了:

1
2
3
4
5
6
root@kali:~# foo=1
root@kali:~# foo(){ echo test3; }
root@kali:~# export foo;export -f foo
root@kali:~# env | grep foo
foo=1
BASH_FUNC_foo%%=() { echo test3

Bash 4.3.30 之前的版本

安全漏洞bashshock https://www.freebuf.com/vuls/44994.html

BASH除了可以将shell变量导出为环境变量,还可以将shell函数导出为环境变量!当前版本的bash通过以函数名作为环境变量名,以“(){”开头的字串作为环境变量的值来将函数定义导出为环境变量。

此次爆出的漏洞在于BASH处理这样的“函数环境变量”的时候,并没有以函数结尾“}”为结束,而是一直执行其后的shell命令。

$ env x=‘() { :;}; echo vulnerable&#039; bash -c "echo this is a test"

之前的 Bash 版本在导出函数时不会给函数名加上 BASH_FUNC_ 前缀和 %% 后缀,在导入时也不会识别前缀后缀,只要看到 右边是 () { 这四个字符,就按函数导入,像这样:

1
2
env 'foo=() { echo foo函数; }' bash -c 'foo'
foo函数

以后的版本:

1
2
3
4
root@kali:~# env 'foo=(){ echo test4; }' bash -c 'foo'
test3 # test3是刚才定义的 BASH_FUNC_foo%%=() { echo test3
# 也就是说foo并没有更新成函数

由于环境变量字符串的转换和识别规则不同,假如你在 Bash 4.3.30 中打开一个 Bash 3.2.25,后者是无法继承到前者导出的函数的:

1
2
3
4
5
6
7
$ bash4.3.30
$ foo() { echo foo函数 ; }
$ export -f foo
$ bash3.2.25
$ foo

bash3.2.25: foo: command not found

反之亦然,同时 foo 会被导入成一个变量:

1
2
3
4
5
6
7
8
9
10
11
$ bash3.2.25
$ foo() { echo foo函数 ; }
$ export -f foo
$ bash4.3.30
$ foo

bash3.2.25: foo: command not found

$ echo $foo

() { echo foo函数 }