code-breaking picklecode中对signed_cookies引擎分析
Posted peri0d
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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__.py
的login()
方法第 97 行,调用
user
类的get_session_auth_hash()
方法来获取session_auth_hash
的值,跟进get_session_auth_hash()
给
key_salt
赋值后调用salted_hmac(key_salt, self.password)
生成session_auth_hash
,这里的password
是经过加密的,跟进salted_hmac()
在第 39 行对
key_salt + secret
进行 sha1 加密并以 byte 类型返回给key
。这里的value
是经过加密后的password
。然后调用hmac.new()
返回一个 sha1 模式的hmac
对象流程梳理
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
单步调试,然后执行
_get_session()
函数,返回缓存 session,是一个空字典在第 108 行执行
cycle_key()
,使用新密钥保存相同的数据,调用save()
,它在请求结束时自动保存一个带有新密钥的 cookie 。跟进
save()
,在第 41 行执行_get_session_key()
,生成一个 base64 编码后的字符串作为session key
,继续跟进,它又调用了signing.dumps()
然后单步调试进入到
_get_session()
方法获取self._session
,从缓存中加载 session,此时为一个空字典,即self._session ==
然后分别给 compress,salt,serializer 赋值,然后调用
signing.dumps()
,继续跟进,传入的参数obj = , salt = ‘django.contrib.sessions.backends.signed_cookies‘, compress = True
在
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‘
然后调用
TimestampSigner
类的sign()
方法,根据value=‘gAN9cQAu‘
,sep
和timestamp()
对value
进行重新赋值,其值为‘gAN9cQAu:1i5q6e‘
,然后再次在Signer.sign()
中重新赋值,得到最后结果‘gAN9cQAu:1i5q6e:wjJR2MUONx_wmPA3m8zYqTj5uCQ
回到
save()
,继续单步调试,调用了base.py
中第 170 行的_set_session_key()
方法,将value
赋值给session_key
和_session_key
回到
save()
,完成赋值,回到cycle_key()
,再回到auth\__init__.py
的login()
方法的第 108 行,这时可以在变量列表看到设置的 session 信息了后面的代码是 django 对用户的持久化处理以及对 CSRF token 的验证等等,值得注意的是在第 126 行到 128 行,进行了 session 设置
流程梳理
_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 的,继续调试,在
contrib\sessions\middleware.py
中发现其对 cookie 的操作,从 44 行开始是设置 cookie 的存活时间,在第 58 行看到了save()
函数,进行 cookie 的保存,单步调试进入在 save() 函数中,调用
_get_session_key()
函数,剩下的反序列化和前面的相同,只是 session 的值发生了改变,从空字典变为含有 3 个元素的字典,然后就是将 cookie 设置在返回包中,这就完成了 cookie 设置的分析
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)
以上是关于code-breaking picklecode中对signed_cookies引擎分析的主要内容,如果未能解决你的问题,请参考以下文章
Groovy闭包 Closure ( 闭包中调用 Groovy 脚本中的方法 | owner 与 delegate 区别 | 闭包中调用对象中的方法 )