# 漏洞总结:Nuclei 模板沙箱绕过漏洞 ## 漏洞概述 Nuclei 模板引擎中存在一个安全漏洞,允许攻击者在沙箱环境中访问本地文件系统。该漏洞源于 `require()` 函数在特权执行期间缓存了模块状态,导致后续受限执行时仍可使用已缓存的模块。 ## 影响范围 - **组件**:Nuclei 模板引擎的 JavaScript 执行环境 - **风险**:攻击者可通过构造恶意模板绕过沙箱限制,访问服务器本地文件 - **严重程度**:高(可读取敏感配置文件、系统文件等) ## 修复方案 1. **重置模块缓存**:每次执行模板前重建 `require` 注册表,避免模块状态跨执行保留 2. **路径规范化**:使用 `protocolstate.NormalizePath()` 统一处理路径,防止通过符号链接或路径规范化绕过限制 3. **严格路径检查**:确保所有文件访问都在 `nuclei-templates` 目录内进行 ## POC 代码 ```go func TestRequireDoesntReusePrivilegedModuleCacheAcrossExecutions(t *testing.T) { modulePath := writeModuleFile(t, t.TempDir(), "outside.js", module.exports = { value: "outside-ok" }) script := fmt.Sprintf(`var helper = require(%q); ExportAs("value", helper.value); true;`, modulePath) program, err := goja.Compile("", script, false) require.NoError(t, err) allowExecutionID := "allow-" + t.Name() denyExecutionID := "deny-" + t.Name() protocolstate.SetAllowedOptions(types.Options{Executions: allowExecutionID, AllowLocalFileAccess: true}) protocolstate.SetAllowedOptions(types.Options{Executions: denyExecutionID, AllowLocalFileAccess: false}) runtime := createNewRuntime() firstValue, err := executeWithRuntime(runtime, program, NewExecuteArgs(), &ExecuteOptions{ ExecutionID: allowExecutionID, Context: context.Background(), }) require.NoError(t, err) require.Equal(t, "outside-ok", firstValue.Export()) _ = executeWithRuntime(runtime, program, NewExecuteArgs(), &ExecuteOptions{ ExecutionID: denyExecutionID, Context: context.Background(), }) require.Error(t, err) require.Contains(t, err.Error(), "-lfs is not enabled") } ```