9月24日,广泛存在于Linux的bash漏洞曝光。因为此漏洞可以执行远程命令,所以极为危。危害程度可能超过前段时间的心脏流血漏洞。
漏洞编号CVE-2014-6271,以及打了补丁之后被绕过的CVE-2014-7169,又称ShellShock漏洞。
漏洞起因:
要是用一句话概括这个漏洞,就是代码和数据没有正确区分。
此次漏洞很像SQL注入,通过特别设计的参数使得解析器错误地执行了参数中的命令。这其实是所有解析性语言都可能存在的问题。
1 | env x= '() { :;}; echo vulnerable' bash -c "echo this is a test" |
这是网上最早流传出来验证漏洞的代码。如果漏洞存在,那个”echo vulnerable”会被执行,屏幕上会输出“vulnerable”。我们分析这句shell命令的语法。
1 | + env 'x=() { :;}; echo vulnerable' bash -c 'echo this is a test' |
可以看出,这个语句原本的意图是使用env命令创建一个临时环境,然后在里面执行一个bash命令。
从解析上看,bash解析并没有问题,语法是正常的。所以应该是env命令处理变量名时的漏洞。
bash可以将shell变量导出为环境变量,还可以将shell函数导出为环境变量!当前版本的bash通过以函数名作为环境变量名,以“(){”开头的字串作为环境变量的值来将函数定义导出为环境变量。此次爆出的漏洞在于bash处理这样的“函数环境变量”的时候,并没有以函数结尾“}”为结束,而是一直执行其后的shell命令。
所以,在某种环境,bash会在给导出的函数定义处理环境时执行用户代码。
漏洞原理:
黑客定义了这样的环境变量(注:() 和 { 间的空格不能少):
1 | export X= '() { echo "inside X"; }; echo "outside X";' |
3 | declare -x X= "() { echo \"inside X\"; }; echo \"outside X\";" |
当我们开启一个子bash时。
1 | [sean@localhost ~]$ export X= '() { echo "inside X"; }; echo "outside X";' |
2 | [sean@localhost ~]$ bash |
变量中的代码被执行了。(以上参考coolshell.cn)
漏洞就在于创建子bash时,注入代码被执行。所以,回忆一下那个漏洞验证代码,env后有一个bash,我试过多次,不直接跟bash都不会触发注入代码。上面的例子很好的解释了这一点,注入代码是在子bash载入用户环境变量时执行的,env后直接跟bash就是为了在env创建的临时环境中创建子bash以触发漏洞。
导出函数的代码:
06 | void initialize_shell_variables (env, privmode) char **env; int privmode; |
09 | create_variable_tables (); |
14 | for (string_index = 0; string = env[string_index++]; ) |
18 | while ((c = *string++) && c != '=' ) ; |
19 | if (string[-1] == '=' ) |
20 | char_index = string - name - 1; |
30 | name[char_index] = '\0' ; |
41 | if (privmode == 0 && read_but_dont_execute == 0 && STREQN ( "() {" , string, 4)) |
42 | string中的 "(){" 用于判断这是一个函数 |
44 | string_length = strlen (string); |
45 | temp_string = ( char *)xmalloc (3 + string_length + char_index); |
47 | strcpy (temp_string, name); |
48 | temp_string[char_index] = ' ' ; |
50 | strcpy (temp_string + char_index + 1, string); |
60 | parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST); |
64 | if (name[char_index - 1] == ')' && name[char_index - 2] == '(' ) |
65 | name[char_index - 2] = '\0' ; |
67 | if (temp_var = find_function (name)) |
69 | VSETATTR (temp_var, (att_exported|att_imported)); |
70 | array_needs_making = 1; |
73 | report_error (_( "error importing function definition for `%s'" ), name); |
76 | if (name[char_index - 1] == ')' && name[char_index - 2] == '\0' ) |
77 | name[char_index - 2] = '(' ; |
代码中,直接把变量值传入parse_and_execute(),网上不少分析都是说这个就是漏洞所在。但是表示略有疑问,为何定义要直接传进去执行呢?真正分离开数据和代码可能才是堵上漏洞的根本办法。
漏洞危害:
其实,一开始知道这个漏洞,还是觉得奇怪,语句本来就是在shell上执行的。后来发现,安全问题在于远程访问时,某些协议正好会组成类似测试代码的语句,触发此漏洞。
因为CGI会把post包中的变量导入成用户变量,并在里面启动子bash,这样就触发了漏洞。
此利用方式,需要以下条件:
- [远程]服务会调用bash。(创建bash子进程)
- [远程]服务允许用户定义环境变量。
- [远程]服务调用子bash时加载了用户定义的环境变量。
注入流程:
看上去,目前CGI已经基本不用了。但是只要符合上面所说的三个条件,还是会触发漏洞。所以,此漏洞危害巨大,又因为是源码级的漏洞,影响广泛。
关于那个被绕过的第一个补丁:
在爆出漏洞第二天,补丁就出来了。
第一个补丁对传入的变量做了一个判断。类似于防SQL注入的过滤方法。第一个补丁判断如果不是函数定义,或者命令(command)超过一个就判为不合法。自然,这种方法很可能就被绕过。
很快,绕过漏洞的测试代码也跟着出来。
1 | [sean@localhost ~]$ env X='() { (a)=>\' sh -c "echo date" ; cat echo |
2 | sh: X:行1: 未预期的符号 `=' 附近有语法错误 |
5 | 2014年 09月 28日 星期日 21:33:50 CST |
当时发现了代码执行后,所在目录莫名其妙出了个echo文件,echo文件存的就是那个日期。
coolshell的解释:
- X='() { (a)=>\’ 这个不用说了,定义一个X的环境变量。但是,这个函数不完整啊,是的,这是故意的。另外你一定要注意,\’不是为了单引号的转义,X这个变量的值就是 () { (a)=>\
- 其中的 (a)=这个东西目的就是为了让bash的解释器出错(语法错误)。
- 语法出错后,在缓冲区中就会只剩下了 “>\”这两个字符。
- 于是,这个神奇的bash会把后面的命令echo date换个行放到这个缓冲区中,然后执行。
相当于在shell 下执行了下面这个命令:
bash中“\”用于命令上下换行,所以,实际执行的是:
1 | [sean@localhost ~]$ > echo date |
bash中“>”符号是输出重定向,这里是把标准输出重定向到echo文件。date是我们费劲心思让它执行的命令。执行结果就是执行了date命令,输出重定向到echo文件。
怎么绕过补丁的:
补丁判断如果不是函数定义,或者命令(command)超过一个。
在代码中,”(){“表示了这是函数定义,命令只有一个,因为分号只有一个。这就绕过补丁的检测。
能想到这个攻击的真的是个变态,连语法出错都用上了。
转载请注明:旅途@KryptosX » ShellShock漏洞原理分析