一、命令执行漏洞分析
利用这个漏洞,需要两个条件:
- 服务器收到请求后,会执行bash
- 执行bash的时候,会将UserAgent等设置成环境变量
同时满足这两点的,一般是使用CGI的服务器,所以受到影响的服务器份额(我语文不好)不是很大。
对它进行一下简单的说明:
下面是乌云上的测试脚本:
$ curl -A '() { :; }; /bin/cat /etc/passwd > dumped_file' http://192.168.0.1/poc.cgi
poc.cgi 是一个开头为 #! /bin/bash 的cgi脚本
在服务器端会发生的事情:
- 收到HTTP请求,此请求带有Head: “User-Agent: () { :; }; /bin/cat /etc/passwd >
dumped_file” - 服务器将User-Agent设置为环境变量,并执行bash
- bash由于解析漏洞,执行了如下脚本$ /bin/cat /etc/passwd > dumped_file
由于解析漏洞使得环境变量被运行的原理如下(图片来源见篇尾):
大家可以用自己的bash运行下面的代码尝试:
env User-Agent='() { :;}; echo hehe' bash -c "echo test" hehe
Linux(大)部分发行版都已经更新了bash,这个命令应该已经没用了,不过OS X的用户还是可以试一下的,Apple没有发布新的安全更新(Cook: 你们一定不会用它当服务器的)
二、原理分析
在Bash Shell下执行以下代码:
env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
如果输出:
vulnerable this is a test
则表示存在漏洞。
漏洞原理:
Shell里可以定义变量,POC中定义了一个命名为x的变量,内容是一个字符串:
() { :;}; echo vulnerable
而根据漏洞信息得知,这个漏洞产生于Shell在处理函数定义时,执行了函数体之后的命令。但这里x的值是个字符串,它是怎么转变成函数的呢。
实际这个和Bash实现有关,在Bash中定义一个函数,格式为:
function function_name() { body; }
当Bash在初始化环境变量时,语法解析器发现小括号和大括号的时候,就认为它是一个函数定义:
[lu4nx@lx-pc ~]$ say_hello='() { echo hello world; }' [lu4nx@lx-pc ~]$ export say_hello [lu4nx@lx-pc ~]$ bash -c 'say_hello' hello world
上面代码在新的Bash进程中,say_hello成了新环境中的一个函数,它的演变过程如下:
1、新的bash在初始时,扫描到环境变量say_hello出现小括号和大括号,认定它是一个函数定义
2、bash把say_hello作为函数名,其值作为函数体
typeset命令可以列出当前环境中所有变量和函数定义,我们用typeset看看这个字符串怎么变成函数的。继续上面定义的say_hello函数:
[lu4nx@lx-pc ~]$ bash -c 'typeset' | fgrep -A 10 say_hello say_hello () { echo hello world }
这里新启动了个Bash进程,然后执行了typeset,typeset会返回当前环境(新的环境)中所有定义,这里清楚看到say_hello被变成函数了。
——————————————————————————————————-
而这个漏洞在于,Bash把函数体解析完了之后,去执行了函数定义后面的语句,为啥会这样呢。
通过结合补丁,我对Bash的源码简单分析了下,Bash初始化时调用了builtins/evalstring.c里的parse_and_execute函数。是的,就等于Bash初始化环境时调用了类似其他高级语言中的eval函数,它负责解析字符串输入并执行。
继续看parse_and_execute的源码,关键点在这里:
else if (command = global_command) { struct fd_bitmap *bitmap;
它判断命令是否是一个定义成全局的,新的bash进程启动后,say_hello不仅被解析成函数了,还变成全局的了:
[lu4nx@lx-pc data]$ bash -c 'typeset -f' say_hello () { echo hello world } declare -fx say_hello
declare命令是Bash内置的,用来限定变量的属性,-f表示say_hello是一个函数,-x参数表示say_hello被export成一个环境变量,所以这句话的意思是让say_hello成了全局有效的函数。
其实Bash本身其实是想在启动时初始环境变量以及定义一些函数,而初始的方式就是去把 变量名=值 这样的赋值语句用eval去执行一次,如果出现了函数定义,就把它转变成函数,除此之外就不想让它干其他的了,可偏偏它在扫描到函数定义时,把它转变成函数的过程中不小心执行了后面的命令,这其实不是eval的错,这是做语法解析时没考虑严格,所以补丁加了这么一句话来判断函数体合法性:
if ((flags & SEVAL_FUNCDEF) && command->type != cm_function_def)
补充
另外,很多人疑惑POC里{ :; }这句中的冒号和分号,分号作为结束符,而冒号的意思是什么也不做,类似Python里的pass,具体看Bash官方文档。
而bash命令执行漏洞
三、补丁绕过
现在的情况:
因为我只有Debian的环境,所以我只能说上面的漏洞在Debian源更新的bash已经解决了。
//就当它已经解决了吧
但是不小心又看到了下面的漏洞
$ env User-Agent='() {(a)=>\' bash -c 'output date'; cat output
// 从小bash没学好,一路兵败如山倒
// 我不会说我看不懂的
试了一下,确实会输出执行的date,但是原理是?
先看一下下面的图:
我试着在bash里输入:
$ >\ # 然后bash的显示 > # 输入 > output date # ls一下,发现当前目录有叫output的文件
嗯,应该是>符号把date的输出重定向到output里了
这个漏洞在Debian源里更新的bash还没有解决…..(9月26日上午3:50)
关于可以利用它做什么:
首先要反对一下目前排名第一的答案,有了bash不等于有一切,要看是什么权限运行的bash。
- cgi服务器进程一般不会是root权限
- 如果是的话,那个网站的网(yun)管(wei)就可以开了又开了。
- 所以拿到的很大几率是一个像”www_data”这样的用户权限
拿到www_data可以做什么呢?
可以看网站的配置文件,里面可能有数据库密码,然后就可以进数据库了。(真的这么简单么)
// 看起来还是有危险的,下面是解决方案
# debian/ubuntu 用户 $ sudo apt-get update $ sudo apt-get install bash # centos 用户 $ sudo yum update bash # osx 用户 # 等Apple更新吧 # windows 用户 # 别凑热闹了亲
// 这里是一段结束语
// 这个漏洞我觉得是cgi服务器的问题(误),把用户的输入放到env里真心大丈夫?
Update:
- Debian源里的bash已经修复第二个漏洞
- OS X修复方法参考 security – How do I recompile Bash to avoid Shellshock
(the remote exploit CVE-2014-6271 and CVE-2014-7169)?
图片截屏自:
CVE-2014-6271 : Remote code execution through bash : netsec
参考链接:
- CVE-2014-6271 — WooYun(白帽子技术社区) — 网络安全资讯、讨论,跨站师,渗透师,结界师聚集之地又一个有意思的地方
- CVE-2014-6271: Bash lets you do bad things. (Shellshock)
转自:http://segmentfault.com/blog/justjavac/1190000000697334
四、补丁绕过分析
官方提供的第一个补丁主要修改了:
1、参数类型和个数的限制,从注释中即可看出:
#define SEVAL_FUNCDEF 0x080 /* only allow function definitions */ #define SEVAL_ONECMD 0x100 /* only allow a single command */
2、给builtins/evalstring.c文件里的parse_and_execute加入了类型判断:
if ((flags & SEVAL_FUNCDEF) && command->type != cm_function_def) { 不合法,不是函数定义 break; } ... // 逻辑为真就表明参数不合法 if (flags & SEVAL_ONECMD) break;
从上面即可看出补丁思路:如果不是函数定义、命令(command)超过一个就判为不合法。什么才算合法呢,Bypass POC给出了答案:
env X='() { (x)=>\' ./bash -c 'my echo hello'
只要函数体满足() {打头就行了。并且这条POC也满足单个命令(command),因为没出现“;”。
Bash Shell在eval的时候遇到语法问题(x)=被忽略了。接着就来到重点了,新的bash进程执行了这条命令:
>\my echo hello
然后在路径下生成了my文件,内容为hello。
Bash语法极其怪异,让我们逐一分析。
字符\是个转移字符,当会保留后面跟的文本,\my实际等于字符串my,如果没有\,新的bash进程会把my当作是命令。因为如果你在终端只输入\并回车,当前bash进程会阻塞等待你输入,在POC里,“输入”的就是my。
字符>就是传说中的重定向,假设要把进程A的输出写入到文件B中,就写成如下:
A > B
其实你写成> B A形式也可以,不信试试:
[lu4nx@lx-pc /tmp]$ > hi date [lu4nx@lx-pc /tmp]$ cat hi 2014年 09月 27日 星期六 01:06:06 CST
这种前缀写法我也是头一次见到,这次分析Shell源码,看得出它的设计极其像一个Lisp解析器,我以为这种写法是照顾Lisper,因为 Bash结构基本上就是一个交互式(REPL)和eval,而Lisp解析器的核心就是eval,直到我看了Shell的Yacc语法分析 (parse.y)后,我才恍然大悟。重定向的语法定义如下:
redirection: '>' WORD { redir.filename = $2; $$ = make_redirection (1, r_output_direction, redir); }
这里表示,输出的文件是取自$2,$2在这段表示参数WORD,如果输入的语句是> A B,那么WORD的实参就是A;如果输入的语句是A > B,那么WORD的实参就是B。
所以POC的思路就是定义一个语法不合法的函数体,绕过函数定义的检测代码,然后执行了后面的命令,最终让Bash在初始化的时候执行了>\my echo hello。
Update:
- Debian源里的bash已经修复第二个漏洞
- OS X修复方法参考 security – How do I recompile Bash to avoid Shellshock
(the remote exploit CVE-2014-6271 and CVE-2014-7169)?
参考资料:
1、http://blog.knownsec.com/2014/09/bash_3-0-4-3-command-exec-analysis/
2、http://blog.knownsec.com/2014/09/bash_3-0-4-3-command-exec-patch-bypass-analysis/