最近在我们在bash fuzz项目中,发现诸多问题,现在公布其中的3处本地拒绝服务漏洞的分析。该漏洞已经报告给bash的发开者,并且在最新版中得到了修复。
漏洞描述:
bash-4.3libreadlinesavestring.c ( 或者bash-1.14.7libreadlinereadline.c )文件的savestring()函数存在NULL指针引用,对该漏洞的成功利用会造成本地拒绝服务。
危害等级:
低危-本地拒绝服务
漏洞验证:
bash-c ‘>$(ss=111 declare -i ss )’
或者
bash -c ‘vvv=1 unset vvv >>`declare -i vvv`’
修复方法:
---savestring.c 2010-08-0314:08:04.000000000 +0800 +++ savestring.c.new 2014-12-03 19:38:11.459000532 +0800 @@ -35,7 +35,7 @@ { char *ret; - ret = (char *)xmalloc(strlen (s) + 1); + ret = (char *)xmalloc (NULL == s ? 1 : strlen (s) + 1); strcpy (ret, s); return ret; }
详细分析
对该漏洞的理解分为三部分,如下:
1.神奇的NULL指针
根据调试信息,我们在declare_internal()中找到最终传入NULL指针的代码,如图1。
在NULL传输之前,我们需要构造诸多变量的值,如var,offset等。我们暂时把注意力放在NULL指针的传递上。NULL指针分别流经 bind_variable()函数(图2),bind_tempenv_variable()函数(图3),最后到savestring()由于 strlen(NULL)引发崩溃(图4)。在bind_tempenv_variable()函数(图3)中,我们注意到在进入 savestring()之前,在temporary_env中查询了是否存在name变量(对应poc中的变量ss),代码如下:
var = temporary_env ? hash_lookup (name, temporary_env) :(SHELL_VAR *)NULL;
结果是找到了相关的值。于是在进入savestring()之后,作者显然未加验证,bash不出意料地发生崩溃。
现在我们已经了解是NULL导致程序崩溃,回想整个过程,我们猜想作者传入NULL的逻辑是有更大漏洞,下文将证明,这一推断是正确的。
图1
图2
图3
图4
2.为什么传NULL指针
现在让我们来寻找作者传入NULL的逻辑,在declare_internal()(见图1)中我们看到关键变量有两个,第一个是
if (offset)
offset的取值代码见图5,取决于assignment(),见图6,可以看出,类似”ss=111”这种形式,offset即为0。
接着我们看第二个关键变量,
var = mkglobal ? find_global_variable (name) : find_variable (name);
if (var == 0)
mkglobal的值一般为0,name值对应poc中的ss,为什么find_variable(“ss”)值会为空呢?我们知道在第一步分析中,在崩溃之前,bind_tempenv_variable()使用了如下查询的代码,并且返回非空。
var = temporary_env ? hash_lookup (name, temporary_env) :(SHELL_VAR *)NULL;
为什么之前找不到的ss变量,之后就找到了呢?
带着这个疑问,让我们看find_variable()函数(见图7)。可以看到,我们需要使find_variable_internal()返回0;如图8,我们发现find_variable_internal()的返回值可以由两种方式得到,分别是
var = hash_lookup (name, temporary_env);
var = var_lookup (name, vc);
即可以查询两张不同的表产生结果,分别是temporary_env和vc表!还记得bind_tempenv_variable()只能查询 temporary_env吗?因此,显而易见的,poc代码之所以能造成崩溃,是因为前后查询不通的变量表,完整走出一条崩溃之路。
现在让我们看看这一条“崩溃之路“是如何实现的。
更进一步地查看find_variable()(图7)和find_variable_internal()(图8)代码,我们发现这里的逻辑存在微妙的错误,可以注意到find_variable_internal()的第二个参数的产生式为:
expanding_redir == 0 && (assigning_in_environment|| executing_builtin)
而选择查询哪张表的的关键参数search_tempenv的赋值式为:
search_tempenv = force_tempenv || (expanding_redir == 0&& subshell_environment);
可以注意到两个产生式都包含expanding_redir。如果expanding_redir == 1,其实就走人错误的查询表的方式!见图9,事实确实是expanding_redir的值为1。现在,我们确认bash作者在处理重定向的变量表查询逻 辑时存在错误!但是,故事仍未结束。
图5
图6
图7
图8
图9
3.后续彩蛋
现在我们已经可以控制bash的运行逻辑,那我们继续深挖相关漏洞。我们暂时发现两处。
第一处:与本文原理相同但是攻击路径不同
bash-4.3/builtins/setattr.def
var = bind_variable (name, (char*)NULL, 0);
第二处:与本文原理相同但是攻击点不同
bash-4.3/builtins/declare.def
staticint declare_internal (list, local_var)
var = mkglobal ?bind_global_variable (name, (char *)NULL, 0) : bind_variable (name, (char*)NULL, 0);
/bash-4.3/variables.c文件中的
char *make_variable_value (var, value, flags)函数
else if (*value)会导致读0