常见的执行命令模块和函数有
os
subprocess
pty -> 在Linux下使用
codecs
popen
eval
exec
执行某些需要系统命令参与的操作时,或者为了便于程序操作的时候。会直接调用某些系统命令库来执行,比如在CTF上常见的命令执行操作ping,为了达到这个想法,有采用系统模块操作的
os.system('ping -n 4 %s' %ip)
有自己实现ICMP协议来发送的,https://github.com/samuel/python-ping/blob/master/ping.py。原文是python2下的实现,后面提供一份python3下的修改版,或者直接使用`ping3`模块。
Python动态编程语言是能够从字符串执行代码,eval
执行一个字符串,还可以用来执行字符串转对象。可以使用的还有exec
。
关于eval的危险性:https://lucumr.pocoo.org/2011/2/1/exec-in-python/
def command():
if request.values.get('cmd'):
sys.stdout = io.StringIO()
cmd = request.values.get('cmd')
return Response('<p>输入的值为:%s</p>' %str(eval(cmd)))
# return Response('<p>输入的值为:%s</p>' %str(exec(cmd)))
else:
return Response('<p>请输入cmd值</p>')
重定向输出后,可以直接看到执行的命令结果。使用命令模块的场景
def COMMAND(request):
if request.GET.get('ip'):
ip = request.GET.get('ip')
cmd = 'ping -n 4 %s' %shlex.quote(ip)
flag = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE)
stdout = flag.stdout
return HttpResponse('<p>%s</p>' %str(stdout, encoding=chardet.detect(stdout)['encoding']))
else:
return HttpResponse('<p>请输入IP地址</p>')
当然python可以命令执行的并不是单一的模块,还有反序列化,格式化字符串,以及web框架模板的模板注入。
subprocess
是一个为了代替os其中的命令执行库而出现的,python3.5以后的版本,建议是使用subprocess.run
来操作,3.5之前的可以使用库中你认为合适的函数。不过库中的函数都是通过subprocess.Popen
的封装而实现,也可以执行使用subprocess.Popen
来执行较复杂的操作,在shell=False
的时候,第一个字符是列表,或者传入字符串。当使用shell=True
的时候,python会调用/bin/sh
来执行命令,届时会造成命令执行。
cmd = request.values.get('cmd')
s = subprocess.Popen('ping -n 4 '+cmd, shell=True, stdout=subprocess.PIPE)
stdout = s.communicate()
return Response('<p>输入的值为:%s</p>' %str(stdout[0], encoding=chardet.detect(stdout[0])['encoding']))
至于某些操作,可以使用其他模块或者函数来执行的尽量不采用命令模块执行。eval和exec是没必要使用的,虽然某些情况下很好用,但是用来处理输入参数还是太过分了。
比如需要探测系统存活,可以使用ping3。尝试端口的开放使用socket。
ping3.verbose_ping(ip)
如果某些必要的命令操作需要命令模块来执行,建议使用subprocess
,并且设置shell=False
。可以保护免受shell相关的命令执行。按照官方建议,然后跟 shlex.quote()
配合使用。
def COMMAND(request):
if request.GET.get('ip'):
ip = request.GET.get('ip')
cmd = 'ping -n 4 %s' %shlex.quote(ip)
flag = subprocess.Popen(cmd, shell=False, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
stdout, stderr = flag.communicate()
return HttpResponse('<p>%s</p>' %str(stdout)) #127.0.0.1&&whoami
else:
return HttpResponse('<p>请输入IP地址</p>')
这时候再使用127.0.0.1&&whoami
的时候就可以看到,其实是把这个参数当作一个字符串来处理。
ping -n 4 '127.0.0.1&&whoami'
Ping 请求找不到主机 '127.0.0.1&&whoami'。请检查该名称,然后重试。
要是想采用过滤或者上面的方式不合适,还可以使用过滤和白名单的形式。如果采用如下的方式,设置文件的id,通过id来操作,同时id是一个hash字段。
def COMMAND(request):
if request.GET.get('filte'):
id = request.GET.get('filte')
filename = File.objects.get(file_hash=id).filename # 代表文件的hash字段
os.system('rm %s' %filename)
return HttpResponse('<p>删除成功</p>')
else:
return HttpResponse('<p>请输入IP地址</p>')
这样看是不是也能达到避免命令执行的效果?实际上,保存的filenam要看是不是后台自动生成的,如果传入一个这样的文件名,还是会存在风险。
aaa;whoami;.jsp
如果是不想依赖第三方模块,又要使用命令执行库,就要考虑怎么处理输入字段。简而言之,进入命令执行的字段一定是处理过的,最好是不可被前端预期的值。
搭建一个命令执行的环境,可以尝试这个项目,还有现成的脚本使用:https://github.com/sethsec/PyCodeInjection