• code-breaking picklecode中对signed_cookies引擎分析


    最近做了 ph 牛的 code-breaking,在做 picklecode 这一题时,没有搞懂那个 django 的 signed_cookies 引擎对 session 的操作,就 debug 了一下,做个总结,算是做了个代码审计吧

    原文 : https://xz.aliyun.com/t/6265

    0x01 获取 session_auth_hash

    • 题目 : https://github.com/phith0n/code-breaking/tree/master/2018/picklecode

    • django 使用的 SESSION_ENGINE 为 django.contrib.sessions.backends.signed_cookies

    • pycharm 开启 debug 模式,username 为 peri0d,password 为 123456

    • 入口文件在 views.py,第 34 行新建了用户并对密码进行了加密。第 35 行调用 auth_login() 函数,跳转到 auth\__init__.pylogin() 方法

      pickle_8

    • 第 97 行,调用 user 类的 get_session_auth_hash() 方法来获取 session_auth_hash 的值,跟进 get_session_auth_hash()

      pickle_9

    • key_salt 赋值后调用 salted_hmac(key_salt, self.password) 生成 session_auth_hash,这里的 password 是经过加密的,跟进 salted_hmac()

      pickle_10

    • 在第 39 行对 key_salt + secret 进行 sha1 加密并以 byte 类型返回给 key。这里的 value 是经过加密后的 password。然后调用 hmac.new() 返回一个 sha1 模式的 hmac 对象

      pickle_11

    • 流程梳理

      key_salt = '***'
      # SECRET_KEY
      secret = '******'
      key = hashlib.sha1(key_salt + secret).digest()
      sha1_obj = hmac.new(key, msg=password_enc, digestmod=hashlib.sha1)
      session_auth_hash = sha1_obj.hexdigest()
      

    0x02 初始化 sessionid

    • 获取 session_auth_hash 后,单步调试,进入 base.py 执行 __contains__() 函数,参数为 _auth_user_id

      pickle_22

    • 单步调试,然后执行 _get_session() 函数,返回缓存 session,是一个空字典

      pickle_23

    • 在第 108 行执行 cycle_key(),使用新密钥保存相同的数据,调用 save(),它在请求结束时自动保存一个带有新密钥的 cookie 。

      pickle_12

      pickle_13

    • 跟进 save(),在第 41 行执行 _get_session_key() ,生成一个 base64 编码后的字符串作为 session key,继续跟进,它又调用了 signing.dumps()

      pickle_14

      pickle_15

    • 然后单步调试进入到 _get_session() 方法获取 self._session,从缓存中加载 session,此时为一个空字典,即 self._session == {}

      pickle_16

    • 然后分别给 compress,salt,serializer 赋值,然后调用 signing.dumps() ,继续跟进,传入的参数 obj = {}, salt = 'django.contrib.sessions.backends.signed_cookies', compress = True

      pickle_17

    • signing.dumps() 中对序列化之后的数据进行压缩,然后进行 base64 编码,再 decode() 为一个 Unicode 的 base64d,其值为 'gAN9cQAu' ,最后调用 TimestampSigner 类的 sign() 方法,继续跟进

    • TimestampSigner 类继承自 Signer 类,先调用它的 __init__ 方法进行初始化,key = 'zs%o-mvuihtk6g4pgd+xpa&1hh9%&ulnf!@9qx8_y5kk+7^cvm', sep = ':', salt = 'django.contrib.sessions.backends.signed_cookies'

    pickle_18

    • 然后调用 TimestampSigner 类的 sign() 方法,根据 value='gAN9cQAu'septimestamp()value 进行重新赋值,其值为 'gAN9cQAu:1i5q6e' ,然后再次在 Signer.sign() 中重新赋值,得到最后结果 'gAN9cQAu:1i5q6e:wjJR2MUONx_wmPA3m8zYqTj5uCQ

      pickle_19

      pickle_20

    • 回到 save() ,继续单步调试,调用了 base.py 中第 170 行的 _set_session_key() 方法,将 value 赋值给 session_key_session_key

      pickle_21

    • 回到 save() ,完成赋值,回到 cycle_key(),再回到 auth\__init__.pylogin() 方法的第 108 行,这时可以在变量列表看到设置的 session 信息了

      pickle_24

    • 后面的代码是 django 对用户的持久化处理以及对 CSRF token 的验证等等,值得注意的是在第 126 行到 128 行,进行了 session 设置

      pickle_25

      pickle_29

    • 流程梳理

      _session = {}
      # SECRET_KEY
      secret = '******'
      salt='****'
      
      data = serializer().dumps(_session)
      compressed = zlib.compress(data)
      base64d = b64_encode(data).decode()
      
      session_key = TimestampSigner(SECRET_KEY, salt=salt).sign(base64d)
      

    0x03 response 写入 session

    • 然后看它如何在 response 中设置 cookie 的,继续调试,在 contribsessionsmiddleware.py 中发现其对 cookie 的操作,从 44 行开始是设置 cookie 的存活时间,在第 58 行看到了 save() 函数,进行 cookie 的保存,单步调试进入

      pickle_26

      pickle_27

    • 在 save() 函数中,调用 _get_session_key() 函数,剩下的反序列化和前面的相同,只是 session 的值发生了改变,从空字典变为含有 3 个元素的字典,然后就是将 cookie 设置在返回包中,这就完成了 cookie 设置的分析

      pickle_28

      pickle_30

      pickle_29

    0x04 总结

    • 总结一下,它对 session 处理的核心机制在于 django.core.signing.dumps() 函数,其具体代码如下,可以看到,data 为 pickle 序列化之后的 byte 对象,我们只要将 data 改为构造好的 evil pickle code 即能实现任意的代码执行

      def dumps(obj, key=None, salt='django.core.signing', serializer=JSONSerializer, compress=False):
      
          data = serializer().dumps(obj)
      
          is_compressed = False
      
          if compress:
              compressed = zlib.compress(data)
              if len(compressed) < (len(data) - 1):
                  data = compressed
                  is_compressed = True
          base64d = b64_encode(data).decode()
          if is_compressed:
              base64d = '.' + base64d
          return TimestampSigner(key, salt=salt).sign(base64d)
      
  • 相关阅读:
    Arduino学习笔记 (五) -- 红外接收与解码
    Arduino学习笔记 (四) -- 常用函数
    VSCode 基础必备插件
    Arduino学习笔记 (三) -- 语言基本概念
    Arduino学习笔记 (二) -- 常见板型
    Arduino学习笔记 (一) -- 概述
    一个好玩的东西:页面点击鼠标左键显示浮动文字
    Python多线程
    Nginx配置转发
    阿里云(CentOs)搭建SVN服务
  • 原文地址:https://www.cnblogs.com/peri0d/p/11512393.html
Copyright © 2020-2023  润新知