前言

随着 Python 在 Web 开发、人工智能及网络安全领域的广泛应用,其代码安全性越来越受到开发者的重视。由于 Python 的动态特性和丰富的标准库,在带来开发便利的同时,也引入了许多潜在的安全风险。本文将全面盘点 Python 开发中常见的安全漏洞及防御手段,内容涵盖格式化字符串、反序列化、沙箱逃逸、框架漏洞、代码混淆以及高危 Sink 函数总结。

1. 格式化字符串漏洞

在 Python 中,主要有两种格式化字符串的方式:

  1. Python 2 时代的 % 操作符格式化:"this is a %s" % "test"

  2. 现代的 format() 方法格式化:"this is a {}".format('test')"this is a {test}".format(test='test')

安全隐患:

当格式化字符串的模板(template)由用户输入控制时,就会引发严重的安全问题。攻击者可以通过对象属性的向上层级引用,读取敏感配置甚至执行任意代码。

漏洞示例:

# 当用户可以控制 format 的字符串时
template = 'class of {0} is {0.__class__}'
print(template.format(42)) 
# 输出: class of 42 is <class 'int'>

从这个简单的例子可以看出,攻击者可以使用 __init____globals__ 等魔术属性读取上下文环境变量,甚至结合内置模块实现命令执行。

2. 反序列化漏洞

Python 的序列化机制(如 pickle)在处理不受信任的数据时极具危险性。pickle 在反序列化时会解析并执行特定的字节码,特别是当类中定义了 __reduce__ 魔术方法时。

2.1 Pickle RCE 演示

攻击者可以通过自定义 __reduce__ 函数返回一个可执行的系统命令(如 subprocess.Popen),在目标服务器反序列化该数据时触发执行。

import pickle
import pickletools
import subprocess

class A(object):
    a = 1
    b = 2
    
    def __reduce__(self):
        # 反序列化时执行系统命令弹计算器或反弹shell
        return (subprocess.Popen, (('cmd.exe', '/c', 'calc'),))

# 生成恶意 payload
data = pickle.dumps(A())
# 打印 opcode 指令序列
pickletools.dis(data)

# 目标服务器执行 pickle.loads(data) 时即被入侵

2.2 防御方案:重载 find_class

为了抵御此类攻击,可以通过重载 pickle.Unpicklerfind_class 方法来实施严格的白名单限制:

import builtins
import io
import pickle

# 定义允许反序列化的安全内置类型
safe_builtins = {
    'range', 'complex', 'set', 'frozenset', 'slice'
}

class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        # 仅允许 builtins 模块中 safe_builtins 指定的类
        if module == "builtins" and name in safe_builtins:
            return getattr(builtins, name)
        # 拦截其余一切反序列化请求
        raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))

def restricted_loads(s):
    """安全的反序列化辅助函数"""
    return RestrictedUnpickler(io.BytesIO(s)).load()

2.3 其他存在风险的序列化库

除了 pickle,以下库处理不可信数据时也存在代码执行或拒绝服务风险:

  • PyYAML (使用 yaml.load 时,应替换为 yaml.safe_load)

  • marshal

  • shelve

3. 沙箱逃逸 (Sandbox Escape)

在某些 CTF 题或在线代码评测系统(OJ)中,常常会提供一个受限的 Python 运行环境(沙箱)。攻击者的目标是突破限制,获取系统最高权限。

3.1 常用高危函数

  • 代码执行:eval / exec / compile

  • 反射与查看:dir / type / getattr / setattr

  • 作用域字典:globals / locals / vars

3.2 常见模块导入绕过

  • import os / from os import *

  • __import__("os") / importlib.import_module('os')

  • imp (Python2/3旧版本) / reload(os)

3.3 沙箱绕过思路 (Bypass)

  1. 查看内置模块: 使用 dir(__builtins__) 查看当前沙箱还存留哪些内置对象。

  2. 继承链与基类搜索(最核心的 payload): 通过寻找已加载模块的子类,定位到危险模块(如 os 中的 systempopen):

    ().__class__.__bases__[0].__subclasses__()

    或者利用全局作用域获取:

    __init__.__func__.__globals__

  3. 利用 Pickle: 若沙箱未禁用 pickle 模块,可直接用其反序列化执行任意代码。

  4. 利用底层的 C 库: 如果 Python 层面的模块全被删除,可以使用 ctypescffi 调用 libc 中的函数。

  5. 字符串拼接绕过限制: "__im" + "port__"

3.4 沙箱防御建议 (官方)

  • 使用 Jython 并利用 Java 平台的 SecurityManager 来锁定程序权限。

  • 使用 fakerootchroot 避免真实环境受损。

  • 结合底层系统技术(如 Docker、Cgroups、Namespace)部署严格的 Rootjail。

4. Web 框架安全

4.1 Django

历史典型漏洞:

  • CVE-2016-7401: CSRF Bypass 漏洞。

  • CVE-2017-7233/7234: 开放重定向漏洞 (Open redirect vulnerability)。

  • CVE-2017-12794: Debug 页面导致的信息泄露和 XSS 漏洞(生产环境切记关闭 DEBUG = False)。

配置错误导致的源码泄露:

当使用 Nginx 为 Django 做反向代理处理静态文件时,如果 location 配置缺少尾部斜杠,会导致目录穿越。

例如配置了 /static 映射到 /var/www/static,访问 /static../ 会由于 Nginx 的路径拼接缺陷,被代理读取到项目源码。

4.2 Flask

Flask 默认使用 客户端 Session (Client-side Session)。

它会将 session 数据使用 SECRET_KEY 签名后存储在用户的 Cookie 中。如果服务端的 SECRET_KEY 泄露(例如被硬编码在源码中传到了 Github),攻击者就可以使用该密钥随意伪造任意用户的 Session(比如修改 {"user_id": 1, "is_admin": true}),从而完全接管网站后台。

5. 代码混淆与保护

在安全对抗中,防守方常需对 Python 源码进行保护,防止逆向分析或源码泄露;攻击方也会对木马代码进行混淆以绕过杀软检测。常见方式包括:

  • 基于 AST (抽象语法树) 变换: 改变代码结构但保持逻辑一致(如重命名变量、插入死代码)。

  • 编译为 .pyc 文件: 虽然可以被 uncompyle6 还原,但能防御最基础的查看。

  • Pyinstaller 打包: 将解释器和依赖打包成二进制 ELF/EXE 文件。

  • PyArmor 工具: 对函数和模块级进行加密,运行时动态解密,保护强度较高。

  • 自定义 AES 加密引擎: 更改 Python 解释器源码(如 CPython),读取被加密为 .pye 格式的文件。

6. Sink (常见危险函数触发点总结)

在进行 Python 代码安全审计时,重点审查以下 Sink 函数(数据最终流入并触发漏洞的地方):

6.1 命令执行

  • os.system / os.popen / os.spawn* / os.exec* / os.fork

  • subprocess.Popen / subprocess.run

  • asyncio.subprocess / asyncio.get_event_loop().subprocess_exec

  • commands.getstatusoutput (Python 2)

  • pty.spawn / pty.os

  • platform.popen

  • eval / exec / execfile (Python 2)

  • input (在 Python 2 中相当于 eval(raw_input))

  • ctypes.CDLL (调用动态链接库中的系统命令)

6.2 文件读取 / 目录遍历

  • open / os.open

  • urllib.request.urlopen('file://...')

  • codecs.open

  • fileinput.input

  • types.FileType (仅 Python 2)

6.3 危险第三方库及组件

  • Template (Jinja2/Mako 等模板注入 SSTI)

  • subprocess32

6.4 反序列化 / 数据解析

  • pickle / cPickle (Python 2)

  • PyYAML (yaml.load)

  • marshal

  • shelve

  • PIL (Image Math 等组件历史漏洞)

结语:

Python 的安全开发在于“防患于未然”。坚决不信任任何外部输入、在生产环境关闭一切 Debug 模式、谨慎使用反序列化和执行函数,是保障 Python 代码安全的铁律。希望本文能为大家在日常开发和安全审计时提供一份有力的参考。

需要学习更多或者获取更多资料查看:【有道云笔记】资料领取

更多推荐