bash代码注入的安全漏洞
很多人或许对上半年发生的安全问题“心脏流血”(Heartbleed Bug)事件记忆颇深,这两天,又出现了另外一个“毁灭级”的漏洞——Bash软件安全漏洞。这个漏洞由法国GNU/Linux爱好者Stéphane Chazelas所发现。随后,美国电脑紧急应变中心(US-CERT)、红帽以及多家从事安全的公司于周三(北京时间9月24日)发出警告。 关于这个安全漏洞的细节可参看美国政府计算安全的这两个漏洞披露:CVE-2014-6271 和 CVE-2014-7169。
这个漏洞其实是非常经典的“注入式攻击”,也就是可以向 bash注入一段命令,从bash1.14 到4.3都存在这样的漏洞。我们先来看一下这个安全问题的症状。
目录
Shellshock (CVE-2014-6271)
下面是一个简单的测试:
$ env VAR='() { :;}; echo Bash is vulnerable!' bash -c "echo Bash Test"
如果你发现上面这个命令在你的bash下有这样的输出,那你就说明你的bash是有漏洞的:
Bash is vulnerable! Bash Test
简单地看一下,其实就是向环境变量中注入了一段代码 echo Bash is vulnerable。关于其中的原理我会在后面给出。
很快,CVE-2014-6271的官方补丁出来的了——Bash-4.3 Official Patch 25。
AfterShock – CVE-2014-7169 (又叫Incomplete fix to Shellshock)
但随后,马上有人在Twitter上发贴——说这是一个不完整的fix,并给出了相关的攻击方法。
也就是下面这段测试代码(注意,其中的sh在linux下等价于bash):
env X='() { (a)=>\' sh -c "echo date"; cat echo
上面这段代码运行起来会报错,但是它要的就是报错,报错后会在你在当前目录下生成一个echo的文件,这个文件的内容是一个时间文本。下面是上面 这段命令执行出来的样子。
$ env X='() { (a)=>\' sh -c "echo date"; cat echo sh: X: line 1: syntax error near unexpected token `=' sh: X: line 1: `' sh: error importing function definition for `X' Sat Sep 27 22:06:29 CST 2014
这段测试脚本代码相当的诡异,就像“天书”一样,我会在后面详细说明这段代码的原理。
原理和技术细节
要说清楚这个原理和细节,我们需要从 bash的环境变量开始说起。
bash的环境变量
环境变量大家知道吧,这个不用我普及了吧。环境变量是操作系统运行shell中的变量,很多程序会通过环境变量改变自己的执行行为。在bash中要定义一个环境变量的语法很简单(注:=号的前后不能有空格):
$ var="hello world"
然后你就可以使用这个变量了,比如:echo $var什么的。但是,我们要知道,这个变量只是一个当前shell的“局部变量”,只在当前的shell进程中可以访问,这个shell进程fork出来的进程是访问不到的。
你可以做这样的测试:
$ var="hello coolshell" $ echo $var hello coolshell $ bash $ echo $var
上面的测试中,第三个命令执行了一个bash,也就是开了一个bash的子进程,你就会发现var不能访问了。
为了要让shell的子进程可以访问,我们需要export一下:
$ export var="hello coolshell"
这样,这个环境变量就会在其子进程中可见了。
如果你要查看一下有哪些环境变量可以在子进程中可见(也就是是否被export了),你可使用env命令。不过,env命令也可以用来定义export的环境变量。如下所示:
$ env var="hello haoel"
有了这些基础知识还不够,我们还要知道一个基础知识——shell的函数。
bash的函数
在bash下定义一个函数很简单,如下所示:
$ foo(){ echo "hello coolshell"; } $ foo hello coolshell
有了上面的环境变量的基础知识后,你一定会想试试这个函数是否可以在子进程中调用,答案当然是不行的。
$ foo(){ echo "hello coolshell"; } $ foo hello coolshell $ bash $ foo bash: foo: command not found
你看,和环境变量是一样的,如果要在子进程中可以访问的话,那么,还是一样的,需要export,export有个参数 -f,意思是export一个函数。如:
$ foo(){ echo "hello coolshell"; } $ foo hello coolshell $ export -f foo $ bash $ foo hello coolshell
好了,我讲了这么半天的基础知识,别烦,懂了这些,你才会很容易地理解这两个漏洞是怎么回事。
好,现在要进入正题。
bash的bug
从上面我们可以看到,bash的变量和函数用了一模一样的机制,如果你用env命令看一下export出来的东西,你会看到上面我们定义的变量和函数都在,如下所示(我省略了其它的环境变量):
$ env var=hello coolshell foo=() { echo "hello coolshell" }
原来,都用同样的方式啊——无论是函数还是变量都是变量啊。于是,看都不用看bash的源代码,聪明的黑客就能猜得到——bash判断一个环境变量是不是一个函数,就看它的值是否以”()”开始。于是,一股邪念涌上心头。
黑客定义了这样的环境变量(注:() 和 { 间的空格不能少):
$ export X='() { echo "inside X"; }; echo "outside X";'
env一下,你会看到X已经在了:
$ env X=(){ echo "inside X"; }; echo "outside X";
然后,当我们在当前的bash shell进程下产生一个bash的子进程时,新的子进程会读取父进程的所有export的环境变量,并复制到自己的进程空间中,很明显,上面的X变量的函数的后面还注入了一条命令:echo “outside X”,这会在父进程向子进程复制的过程中被执行吗?(关于fork相关的东西你可以看一下我以前写的《fork的一个面试题》)
答案是肯定的。
$ export X='() { echo "inside X"; }; echo "outside X";' $ bash outside X
你看,一个代码注入就这样完成了。这就是bash的bug—— 函数体外面的代码被默认地执行了。
我们并不一定非要像上面那样创建另一个bash的子进程,我们可以使用bash -c的参数来执行一个bash子进程命令。就像这个安全漏洞的测试脚本一样:
env VAR='() { :;}; echo Bash is vulnerable!' bash -c "echo Bash Test"
其中,() { :;} 中的冒号就相当于/bin/true,返回true并退出。而bash -c其实就是在spawn一个bash的echo的子进程,用于触发函数体外的echo命令。所以,更为友好一点的测试脚本应该是:
env VAR='() { :;}; echo Bash is vulnerable!' bash -c "echo 如果你看到了vulnerable字样说明你的bash有安全问题"
OK,你应该明白这个漏洞是怎么一回事了吧。
bash漏洞的影响有多大
在网上看到好多人说这个漏洞不大,还说这个事只有那些陈旧的执行CGI脚本的网站才会有,现在已经没有网站用CGI了。我靠,这真是无知者无畏啊。
我举个例子,如果你的网站中有调用操作系统的shell命令,比如你用PHP执行个exec之类的东西。这样的需求是有的,特别是对于一些需要和操作系统交互的重要的后台用于系统管理的程序。于是就会开一个bash的进程来执行。
我们还知道,现在的HTTP服务器基本上都是以子进程式的,所以,其中必然会存在export 一些环境变量的事,而有的环境变量的值是从用户端来的,比如:HTTP_USER_AGENT这样的环境变量,只由浏览器发出的。其实这个变量你想写成什么就写成什么。
于是,我可以把这个HTTP_USER_AGENT的环境变量设置成上述的测试脚本,只不过,我会把echo Bash is vulnerable!这个东西换成别的更为凶残的命令。呵呵。
关于这个漏洞会影响哪些已有的系统,你可以自己Google,几乎所有的报告这个漏洞的文章都说了(比如:这篇,这篇),我这里就不复述了。
注:如果你要看看你的网站有没有这样的问题,你可以用这个在线工具测试一下:‘ShellShock’ Bash Vulnerability CVE-2014-6271 Test Tool。
现在,你知道这事可能会很大了吧。还不赶快去打补丁。(注,yum update bash 把bash版本升级到 4.1.2-15.el6_5.2 , )
关于 AfterShock – CVE-2014-7169 测试脚本的解释
很多同学没有看懂下面这个测试脚本是什么意思,我这里解释一下。
env X='() { (a)=>\' sh -c "echo date"; cat echo
- X='() { (a)=>\’ 这个不用说了,定义一个X的环境变量。但是,这个函数不完整啊,是的,这是故意的。另外你一定要注意,\’不是为了单引号的转义,X这个变量的值就是 () { (a)=>\
- 其中的 (a)=这个东西目的就是为了让bash的解释器出错(语法错误)。
- 语法出错后,在缓冲区中就会只剩下了 “>\”这两个字符。
- 于是,这个神奇的bash会把后面的命令echo date换个行放到这个缓冲区中,然后执行。
相当于在shell 下执行了下面这个命令:
$ >\ echo date
如果你了解bash,你会知道 \ 是用于命令行上换行的,于是相当于执行了:
$ >echo date
这不就是一个重定向么?上述的命令相当于:
$ date > echo
于是,你的当前目录下会出现一个echo的文件,这个文件的内容就是date命令的输出。
能发现这个种玩法的人真是个变态,完全是为bash的源代码量身定制的一个攻击。
(全文完)
(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)
《bash代码注入的安全漏洞》的相关评论
是的, 相当佩服想出后面那个测试代码的人, 绝对是把源码看透了的
耗子的每一篇文章都看,收获不少……
微博上看到的,基本是简单说一下的,没有人来详解。看到这篇文章才理清了。
牛! 顺便占座
安全漏洞防不胜防的感觉。
耗子哥写得相当清楚明白,也相当佩服想出第二种攻击方式的大牛
讲的很清楚,只是有一个问题,“>echo date”这个和 “date > echo”是怎么等价起来的,第一种方法没有用过,试验了一下确实如此,这个原理是什么,还有其他类似用法吗?
写得真好,学习了
我顺手做了一个DHCP攻击版的,影响着用root跑的linux dhcp-client,效果拔群哈哈哈
https://github.com/mengzhuo/dhcp-CVS-2014-6271
RMS这么喜欢lisp,懂了吧@暴力山楂
coolshell 出品的文章,总是那么经典!
那么现在的补丁已经把两个漏洞都补了吗?
@hahaha
不好意思,看漏了,第二个还没补。
果然浅显易懂。。。
赞一下,写得真好。
小白表示十分感谢
httpd 服务器只要不调bash 应该是可以认为不受影响的 当然升级还是要做的
看不出第二个漏洞怎么利用,只是在环境变量中做了重定向,但是不能控制子shell中执行的命令啊。
文章到了 更为友好一点的测试脚本应该是:
env VAR='() { :;}; echo 你的bash有安全问题!’ bash -c “echo 测试结束”
不是更友好吗?
我这里看到贴的代码乱码了..
@Rect
应该好了。刚才在编辑中。
小白都看懂了,感谢分享。
有人说漏洞是代码和数据混合的必然结果 @bnuhero
2014-7169 的漏洞好像在 CentOS 上面修了,bash 版本是 bash-4.1.2-15.el6_5.2,之前 2014-6271 的漏洞修复 bash 版本是 bash-4.1.2-15.el6_5.1,现在 2014-7169 修复后执行结果是
$ env X='() { (a)=>\’ sh -c ‘echo date’; cat echo
date
cat: echo: No such file or directory
感谢大牛分享这么容易懂的文章。
果然浅显易懂,感谢感谢!
把上面的命令都敲了一遍~终于搞懂了~
@暴力山楂
大于号表示输出重定向,并不是一定要跟在某个命令的后面。“> echo”本身就是一条命令(你可以单独执行它试试,会生成一个空文件),它的意思是临时把stdout改到当前目录的echo文件,紧接着执行的date命令的输出就会放到echo文件中。
比如你还可以执行:
1>/tmp/test,这个表示把stdout改到/tmp/test下
ubuntu上两个漏洞都已经补上了
王垠对“脚本语言”的批判:http://www.yinwang.org/blog-cn/2013/03/29/scripting-language/
感谢分享
发现这个漏洞的人,果然是一变态···········
把注入漏洞怪罪到程序语言或者执行工具
你认为这是一个合格的程序员该做的吗?
轻易的人云亦云 追风赶马 你认为是高智商网名的表现吗?
这洞只能说深度够 但是被很多人误导 广度也不是一般人敢应用的 所以基本上 就那样 一惊一乍的 某些”互联网安全大牛“ 我只能呵呵
@Leo
我也有看,虽然觉得他说的很有道理,但是现实是很多文件都使用脚本语言,很难一下子都改变!
皓叔能不能帮忙解释一下:为什么在fork子进程时,复制父进程的环境变量的过程中会执行到注入的语句呢?我看了相关的blog,没弄明白。还是说,可以不深究这个问题,只要知道他就是会运行的?
讲的太好了。
感谢 基本上懂了 有一点 执行之前为什么要加上env呢 不加上env 有问题的脚本一样可以执行啊?
说到底都是linux把文本作为唯一的传输数据的错,什么定义一个函数都可以直接使用字符串作为内部存储方式,然后直接把字符串复制拷贝到子程序。
赞,赞,赞
还有一个问题就是,parse文本出现了问题,ast结构应该就是已经出了问题,报错后还要继续解释这个文本就是一个大问题了,问题就是要及早揭露和dump。
楼主分析的很详细,先谢谢啦。
但是后面所说的php中使用exec函数也会受到影响,这个貌似是误导吧。
我刚刚做了测试,即使php中调用exec函数,bash漏洞也不会被非法执行。
楼主可以自己做下测试验证下。
我的测试结果是目前只有bash cgi写的web程序会被bash漏洞攻击。
最后那个攻击方法实在是…………
最后那个攻击方法实在是…………
@zjhzxhz
我早上看完的没评论, 中午再来评论激增啊 – -|||
顶起!楼主太赞了!
这个地方是否有笔误?
不过,env命令也可以用来定义export的环境变量。如下所示:
$ env $var=”hello haoel”
应为:
$ env var=”hello haoel”
谢谢更正!
语法出错后,在缓冲区中就会只剩下了 “>\”这两个字符。
为啥会留下这两个字符呢, 不是已经读进去了么?
写的深入浅出,不错啊。
@hahaha
第二个有的平台下面已经补了
@Hydroxyl
是已经读了, 但是\加上末尾一个结束字符会进入另一个执行路径, 重新读取到>, 这也是这个测试代码的人的变态的地方.