ShellShock漏洞原理分析-阿里云开发者社区

开发者社区> 数据库> 正文

ShellShock漏洞原理分析

简介:

9月24日,广泛存在于Linux的bash漏洞曝光。因为此漏洞可以执行远程命令,所以极为危。危害程度可能超过前段时间的心脏流血漏洞。

漏洞编号CVE-2014-6271,以及打了补丁之后被绕过的CVE-2014-7169,又称ShellShock漏洞。

漏洞起因:

要是用一句话概括这个漏洞,就是代码数据没有正确区分

此次漏洞很像SQL注入,通过特别设计的参数使得解析器错误地执行参数中的命令。这其实是所有解析性语言都可能存在的问题。

1 env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
2 #env [OPTION]... [NAME=VALUE]... [COMMAND [ARGS]...]

这是网上最早流传出来验证漏洞的代码如果漏洞存在,那个”echo vulnerable”会被执行,屏幕上会输出“vulnerable。我们分析这句shell命令的语法。

1 +env 'x=() { :;}; echo vulnerable' bash -c 'echo this is a test'
2 #这是在bash -x下执行,执行命令时把命令和它们的参数显示出来。

可以看出,这个语句原本的意图是使用env命令创建一个临时环境,然后在里面执行一个bash命令

从解析上看,bash解析并没有问题,语法是正常的。所以应该是env命令处理变量名时的漏洞。

bash可以将shell变量导出为环境变量,还可以将shell函数导出为环境变量!当前版本的bash通过以函数名作为环境变量名,以“(){”开头的字串作为环境变量的值来将函数定义导出为环境变量。此次爆出的漏洞在于bash处理这样的“函数环境变量”的时候,并没有函数结尾“}”结束,而是一直执行其后的shell命令

所以,在某种环境,bash会在给导出的函数定义处理环境执行用户代码

漏洞原理:

黑客定义了这样的环境变量注:() 和 { 间的空格不能少):

1 export X='() { echo "inside X"; }; echo "outside X";'
2 #可以用export看到这个函数变量。
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
3 outside X

变量中的代码被执行了。(以上参考coolshell.cn)

漏洞就在于创建子bash时,注入代码被执行。所以,回忆一下那个漏洞验证代码,env后有一个bash,我试过多次,不直接跟bash不会触发注入代码。上面的例子很好的解释了这一点,注入代码是在子bash载入户环境变量执行的,env后直接跟bash就是为了在env创建的临时环境中创建子bash触发漏洞

 导出函数的代码:

01 /*
02 Initialize the shell variables from the current environment. If PRIVMODE is nonzero,
03  don't import functions from ENV or parse $SHELLOPTS.
04 从当前环境安装shell变量,如果PRIVMODE非零,不要从ENV或者prase $SHELLOPTS导入函数。
05 */
06 void initialize_shell_variables (env, privmode) char **env; int privmode;
07 {
08   ...
09   create_variable_tables ();
10    
11   /*
12   从ENV环境变量中获取参数
13   */
14   for (string_index = 0; string = env[string_index++]; )
15   {
16     char_index = 0;
17     name = string;
18     while ((c = *string++) && c != '=') ;             //查找等号'='
19     if (string[-1] == '=')
20       char_index = string - name - 1;
21  
22     /* If there are weird things in the environment, like `=xxx' or a
23       string without an `=', just skip them.
24        如果这里环境中有诡异的事情,像'=xxx'或者一个没有'='的字符串,就忽略它们。
25     */
26     if (char_index == 0)
27       continue;
28  
29     /* ASSERT(name[char_index] == '=') */
30     name[char_index] = '\0';
31     /*
32   Now, name = env variable name, string = env variable value, and char_index == strlen (name)
33     name=env变量名,string=env变量值,char_index= name长度
34     */
35  
36     /*
37     If exported function, define it now.  Don't import functions from the environment in privi
38 leged mode.
39      如果导出函数,先定义。不要在特权模式下把函数导入到环境。
40     */
41     if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))  //匹配
42 string中的"(){"用于判断这是一个函数
43     {
44           string_length = strlen (string);
45           temp_string = (char *)xmalloc (3 + string_length + char_index);
46      
47           strcpy (temp_string, name);
48           temp_string[char_index] = ' ';
49      
50           strcpy (temp_string + char_index + 1, string);      //字符串拼接 
51      
52           /*
53           这句是关键,initialize_shell_variables对环境变量中的代码进行了执行,由于它错误的信任的外部发送的
54 数据,形成了和SQL注入类似的场景,这句代码和PHP中的eval是类似的,黑客只要满足2个条件
55           1. 控制发送的参数,并在其中拼接代码
56           2. 黑客发送的包含用户代码的参数会被无条件的执行,而执行方不进行任何的边界检查
57      
58           这就是典型的数据和代码没有进行正确区分导致的漏洞
59           */
60           parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);    //执行函数
61      
62           // Ancient backwards compatibility.  Old versions of bash exported functions like name()=() {...}
63           // 古老的向下兼容,老版本的bash导入函数类似name()=(){...}
64           if (name[char_index - 1] == ')' && name[char_index - 2] == '(')
65             name[char_index - 2] = '\0';
66      
67           if (temp_var = find_function (name))
68           {
69             VSETATTR (temp_var, (att_exported|att_imported));
70             array_needs_making = 1;
71           }
72           else
73             report_error (_("error importing function definition for `%s'"), name);
74      
75           /* ( */
76           if (name[char_index - 1] == ')' && name[char_index - 2] == '\0')
77             name[char_index - 2] = '(';     /* ) */
78     }
79   }
80

代码中,直接把变量值传入parse_and_execute(),网上不少分析都是说这个就是漏洞所在。但是表示略有疑问,为何定义要直接传进去执行呢?真正分离开数据和代码可能才是堵上漏洞的根本办法。

漏洞危害:

其实,一开始知道这个漏洞,还是觉得奇怪,语句本来就是在shell上执行的。后来发现,安全问题在于远程访问时,某些协议正好会组成类似测试代码的语句,触发此漏洞。

因为CGI会把post包中的变量导入成用户变量,并在里面启动子bash,这样就触发了漏洞

此利用方式,需要以下条件:

  1. [远程]服务会调用bash。(创建bash子进程)
  2. [远程]服务允许用户定义环境变量。
  3. [远程]服务调用子bash时加载了用户定义的环境变量。

 

注入流程:

shellshock注入流程

 

看上去,目前CGI已经基本不用了。但是只要符合上面所说的三个条件,还是会触发漏洞。所以,此漏洞危害巨大,又因为是源码级的漏洞,影响广泛。

关于那个被绕过的第一个补丁:

在爆出漏洞第二天,补丁就出来了。

第一个补丁对传入的变量做了一个判断。类似于防SQL注入的过滤方法。第一个补丁判断如果不是函数定义,或者命令(command)超过一个就判为不合法。自然,这种方法很可能就被绕过。

很快,绕过漏洞的测试代码也跟着出来。

1 [sean@localhost ~]$ env X='() { (a)=>\' sh -c "echo date"cat echo
2 sh: X:行1: 未预期的符号 `=' 附近有语法错误
3 sh: X:行1: `'
4 sh: `X' 函数定义导入错误
5 2014年 09月 28日 星期日 21:33:50 CST

当时发现了代码执行后,所在目录莫名其妙出了个echo文件,echo文件存的就是那个日期。

coolshell的解释:

  • X='() { (a)=>\’ 这个不用说了,定义一个X的环境变量。但是,这个函数不完整啊,是的,这是故意的。另外你一定要注意,\’不是为了单引号的转义,X这个变量的值就是 () { (a)=>\
  • 其中的 (a)=这个东西目的就是为了让bash的解释器出错(语法错误)。
  • 语法出错后,在缓冲区中就会只剩下了 “>\”这两个字符。
  • 于是,这个神奇的bash会把后面的命令echo date换个行放到这个缓冲区中,然后执行

相当于在shell 下执行了下面这个命令:

1 [sean@localhost ~]$ >\
2 echo date

bash中“\”用于命令上下换行,所以,实际执行的是:

1 [sean@localhost ~]$ >echo date

bash中“>”符号是输出重定向,这里是把标准输出重定向到echo文件。date是我们费劲心思让它执行的命令。执行结果就是执行了date命令,输出重定向到echo文件

怎么绕过补丁

补丁判断如果不是函数定义,或者命令(command)超过一个。

在代码中,”(){“表示了这是函数定义命令只有一个,因为分号只有一个。这就绕过补丁的检测。

能想到这个攻击的真的是个变态,连语法出错都用上了。

转载请注明:旅途@KryptosX » ShellShock漏洞原理分析

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
数据库
使用钉钉扫一扫加入圈子
+ 订阅

分享数据库前沿,解构实战干货,推动数据库技术变革

其他文章