文章目录
一、python OS命令注入漏洞
1. 警惕subprocess.getstatusoutput OS命令注入漏洞
python直接执行代码漏洞_警惕OS命令注入漏洞 - python篇
参考URL: https://blog.csdn.net/weixin_39805851/article/details/111079766
问题描述
在后台开发过程中,经常会遇到需要执行一些shell命令,但是命令的参数需要通过Restful API传入,举个简单的例子:
后台实现了一个ping的功能,用户可以指定ping的目标,假设后台代码:
def ping(target):
return subprocess.getstatusoutput('ping -c 1 %s' % target)
如果用户正确传target值,那么没啥问题,比如用户想ping下百度,最终调用是:ping(“www.baidu.com”)
执行的shell命令是:ping -c 1 www.baidu.com
但是如果有一个用户,恶意的传了这么一个target: baidu.com; rm -rf /
,后台直接填充到ping命令中去,那么后台执行的命令就是:ping -c 1 baidu.com; rm -rf /
如果你是以root用户执行的话,那就只能赶紧跑路了。
如何防御
防御的办法有这么几个思路:对传入的值做安全检查,比如必须符合IP或域名的格式,尤其要警惕那些命令连接符号,比如:|,&&,$(),;等
尽量不要用root用户执行,对程序的权限做合理的控制。
使用带有安全机制的函数库,在上面那个例子中,subprocess.getstatusoutput
底层是调用的subprocess.check_output
并且参数中设置了shell=True
try:
data = check_output(cmd, shell=True, universal_newlines=True, stderr=STDOUT)
exitcode = 0
except CalledProcessError as ex:
data = ex.output
exitcode = ex.returncode
if data[-1:] == '\n':
data = data[:-1]
return exitcode, data
问题就出在shell=True上,设置为True就是直接把命令丢给shell处理的,就会出现上述问题。如果设置成False,再把subprocess接收的Command参数改成一个List,例如:[“ls”, “-l”],在运行时,subprocess会把Command参数List的第一个元素作为执行命令,后面的项都强制作为参数去处理,也就没有注入的风险了。
例如:
# lsblk -l |grep disk
sda 8:0 0 1000G 0 disk
sdb 8:16 0 500G 0 disk
使用getstatusoutput函数,并将shell参数设置为False,那么命令注入的风险会大大减小。当shell参数为False时,getstatusoutput将不会通过shell来执行命令,而是直接调用系统提供的执行命令的接口。
import subprocess
command = ['lsblk', '-l', '|', 'grep', 'disk']
suc, disk_info = subprocess.getstatusoutput(command, shell=False)
2. os.popen OS命令注入漏洞
os.popen基础
python调用Shell脚本,有两种方法:os.system()和os.popen(),
前者返回值是脚本的退出状态码,后者的返回值是脚本执行过程中的输出内容
os.popen() 方法用于从一个命令打开一个管道。
在Unix,Windows中有效。
os.popen(command):这种调用方式是通过管道的方式来实现,函数返回一个file对象,里面的内容是脚本输出的内容(可简单理解为echo输出的内容)
举例:像调用”ls”这样的shell命令,应该使用popen的方法来获得内容,对比如下:
>>> import os
>>> os.system("ls") #直接看到运行结果
Desktop Downloads Music Public Templates Videos
Documents examples.desktop Pictures systemExit.py test.sh
0 #返回值为0,表示命令执行成功
>>> n=os.system('ls')
Desktop Downloads Music Public Templates Videos
Documents examples.desktop Pictures systemExit.py test.sh
>>> n
0
>>> n>>8 #将返回值右移8位,得到正确的返回值
0
>>> f=os.popen('ls') #返回一个file对象,可以对这个文件对象进行相关的操作
>>> f
<open file 'ls', mode 'r' at 0x7f5303d124b0>
>>> f.readlines()
['Desktop\n', 'Documents\n', 'Downloads\n', 'examples.desktop\n', 'Music\n', 'Pictures\n', 'Public\n', 'systemExit.py\n', 'Templates\n', 'test.sh\n', 'Videos\n']
>>>
os.popen()可以实现一个“管道”,从这个命令获取的值可以继续被使用。因为它返回一个文件对象,可以对这个文件对象进行相关的操作。
1. 返回值是文件对象
注意:返回值是文件对象,既然是文件对象,使用完就应该关闭,对吧?!不信网上搜一下,一大把文章提到这个os.popen都是忘记关闭文件对象的。 所以,推荐的写法是:
with os.popen(command, "r") as p:
r = p.read()
至于with的用法就不多讲了,使用它,不需要显式的写p.close()。
2. 非阻塞
通俗的讲,非阻塞就是os.popen不会等cmd命令执行完毕就继续下面的代码了,
在某些应用场景,可能这并不是你期望的行为,那如何让命令执行完后,再执行下一句呢?
处理方法是使用read()或readlines()对命令的执行结果进行读操作。
本质上os.popen是非阻塞的,为了实现阻塞的效果,我们使用read()或readlines()对命令结果进行读,由此产生了阻塞的效果。但是,如果你的命令执行无法退出或进入交互模式,这种“读”将形成完全阻塞的情况,表现的像程序卡住了。
python os.popen OS命令注入漏洞
demo如下,使用 os.popen,并不能直接阻止注入漏洞,还是取决你传参拼接,它其实还是直接把命令抛给shell处理,如果用户正确传target值,那么没啥问题。
# coding=utf-8
import os
def print_hi():
target = "www.baidu.com;ls"
with os.popen('ping -c 2 %s' % target, "r") as p:
r = p.read()
print r
if __name__ == '__main__':
print_hi()
总结:使用os.popen 并不能直接杜绝os命令注入,如果使用时,请根据业务场景过滤。
二、python 过滤命令注入危险字符
python安全开发军规之一:防止shell注入
参考URL: https://baijiahao.baidu.com/s?id=1637242342359715510&wfr=spider&for=pc
防范命令入注我们一般都会建议过滤\n$&;|‘"()`等shell元字符
命令注入相关的特殊字符
防范命令入注我们一般都会建议过滤\n$&;|‘"()`等shell元字符
Python3命令注入防范
防范命令入注我们一般都会建议过滤\n$&;|'"()`等shell元字符,但自己实现一个个字符处理还是比较麻烦的,我们尽量寻求一种简单的方法最好是现成的函数或者库进行处理。
最后在这里看到说可以使用shlex.quote()进行处理:https://python-security.readthedocs.io/security.html#shell-command-
injection
import shlex
filename_para = 'somefile'
command = 'ls -l {}'.format(filename_para)
print("except result command:")
print("{}\n".format(command))
filename_para= 'somefile; cat /etc/passwd'
command = 'ls -l {}'.format(filename_para)
print("be injected result command:")
print("{}\n".format(command))
filename_para = 'somefile; cat /etc/passwd'
command = 'ls -l {}'.format(shlex.quote(filename_para))
print("be injected but escape result command:")
print("{}\n".format(command))
运行结果如下:
从原理上看,
shlex.quote()
所做的就是两件事,一是在字符串最外层加上单引号使字符串只能做为一个单一体出现,二是将字符串内的单引号用双引号引起来使其失去可能的闭合功能。