python subprocess 命令注入_安全开发之subprocess库若干函数中以数组形式传参的安全性分析...

0x00. 引言

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

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

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

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

举个例子:

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

参数args为:ls;id

程序员们的期望是让用户执行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)

这样就不会有命令注入了,与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

结果是 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 代码如下

执行命令:

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

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

主要是因为tcpdump 的 -z 参数

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

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

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

与此例子相似的还有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选项的说明:

0×03. 参考资料

欢迎来安全脉搏查看更多的干货文章和我们一起交流互动哦!

你可能感兴趣的:(python,subprocess,命令注入)