0x00. 引言

眼下,与Python相关的安全问题愈发引起人们的注意,本文以最为常用的外部程序调用库(也可称为子进程库)的subprocess库为例,分析其若干函数中的数组传参时引发的安全问题,详文如下。

0x01. 先来看看非数组形式传参时可能引发的命令注入


subprocess库作为Python中最为流行的子进程库,是替代老旧的os.system函数和commands等库的首选,其功能强大,灵活性强的特性愈发受到程序员们的喜爱。


在开发的过程中,如果没有对subprocess库中等函数的不当使用可能引发的安全问题有足够的了解,很可能会对业务产生严重后果。


举个例子:

subprocess.popen(args, shell=True),如果args 没有过滤,直接传入业务代码中执行,可能会引起任意代码执行漏洞,比如:


参数args为:ls;id


1538291140540.png


程序员们的期望是让用户执行ls的命令,但是结果却也执行了分号后面的id


这个漏洞的原因是因为用户传入的命令中含有';' 这在shell环境下被识别为命令分割符,而不是ls的

参数,从而导致执行了两个命令,对于这种类型的漏洞,python 中已经内置了一个过滤库pipes (对于Python3则是shlex 库)


安全的编码应该是如下这样: 按照一定约定将命令与参数分割出来,比如以空格


command = args.split(' ', 1)[0]

argument = pipes.quote(args.split(' ', 1)[1])

s = subprocess.Popen(command + ' ' argument  , shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)

安全开发之subprocess库若干函数中以数组形式传参的安全性分析 https://www.secpulse.com/archives/76192.html

这样就不会有命令注入了,与Popen有类似问题的函数还有call,check_output等函数


 0x02. 数组传参时可能引发的命令注入


这时有人会有疑问,上述例子中';' 被识别为命令分割符,是因为上述命令是在shell环境下执行的,


如果不在Shell环境下执行就没这个问题了,比如直接用subprocess.Popen([xxxx,xxxx,xxx], shell=Flalse),直接执行命令,不通过shell环境调用, 比如:


s=subprocess.Popen(['ping', '-c', '1', host], shell=False, stderr=subprocess.PIPE, stdout=subprocess.PIPE)


假设这里面 host是可控的,如果传入:


www.baidu.com;echo 1

1538291968804.png


结果是 www.baidu.com;echo 1 不可解析,因为www.baidu.com;echo 1被认为是一个整体,是不会执行echo 1的


诚然,这里面不会有命令注入,也正是因为如此,才会使得许多程序员误认为这种用法很安全。


如果将这里的ping 命令替换成其他命令比如tcpdump,则还是可能引发命令注入,因为tcpdump 


支持参数表达式,是可以自行组装命令参数后再次进行解析的,这个过程中就会导致命令注入


且看下面的例子:


s=subprocess.Popen(['tcpdump', '-i', 'ens33', args], shell=False, stderr=subprocess.PIPE, stdout=subprocess.PIPE)


args是可控的,作为过滤数据包的条件, 如果args 传入的是以下内容,则引起命令注入获取反弹shell

-G1 -w 1.txt -z /root/evil.sh


evil.sh 代码如下


1538293764523.png


执行命令:


1538293609477.png

在另外一侧监听的nc 已经获取shell


1538293624518.png


这里要解释下 为啥能够命令注入


主要是因为tcpdump 的 -z 参数


1538293840200.png


-z 与-G 等参数一起使用的时候,会执行-z所指定的程序,这个程序所需的参数是 -w 参数指定的,


不过上述那个evil.sh是不需要传入参数的。


所以这个例子证明,不要以为以数组形式传参给Popen、call、check_output等函数的时候就是安全的


与此例子相似的还有P牛文章:https://www.leavesongs.com/PENETRATION/escapeshellarg-and-parameter-injection.html 中提到的利用'git grep -i --line-number '--open-files-in-pager=id;' master ' 执行命令注入的成功经验。


那么问题来了,怎么防护呢?


有人说利用pipe.quote()过滤嘛,不是能防止命令注入吗,其实不尽然,对于上面的例子是有效的,但不一定对其他例子有效,pipe.quote的原理是类似于escapeshellarg,就是防止参数值变成参数选项,一般会将参数值用引号括起来,

但是引号并不是区分参数选项的标记,比如上面提到的P牛文章中的git grep -i --line-number '--open-files-in-pager=id;' master  中的payload--open-files-in-pager=id加上引号照样被认为是参数选项,还是能够命令执行。


所以最好的办法是在可控的参数前面加上 -- 或者 -e 将可控参数始终认为是值,这样才不会有命令注入的发生。


更改成如下形式则不会有命令注入:

s=subprocess.Popen(['tcpdump', '-i', 'ens33', ‘--’, args], shell=False, stderr=subprocess.PIPE, stdout=subprocess.PIPE)


or

s=subprocess.Popen(['tcpdump', '-i', -e, 'ens33', args], shell=False, stderr=subprocess.PIPE, stdout=subprocess.PIPE)



补充 --- 和 -e选项的说明:


1538295674048.png

0×03. 参考资料


https://docs.python.org/2/library/subprocess.html

https://www.leavesongs.com/PENETRATION/escapeshellarg-and-parameter-injection.html


【安全开发之subprocess库若干函数中以数组形式传参的安全性分析 https://www.secpulse.com/archives/76192.html 】




源链接

Hacking more

...