第一章:Go语言在浏览器运行的安全本质与合规基线
Go 语言原生不支持直接在浏览器中执行,因其编译目标为本地机器码(如 linux/amd64 或 darwin/arm64),而浏览器仅接受经验证的、沙箱化的 JavaScript 或 WebAssembly(Wasm)字节码。因此,“Go 在浏览器运行”实质是通过 GOOS=js GOARCH=wasm 构建为 WebAssembly 模块,再由浏览器 Wasm 运行时加载执行——这一过程严格受限于 Web 平台安全模型。
WebAssembly 的沙箱边界
Wasm 模块默认无权访问 DOM、网络、文件系统或任意内存地址。所有 I/O 必须经由 JavaScript 主机环境显式桥接(如 syscall/js 提供的 Global, Invoke, Wrap 等 API),形成强制的“能力门控”(Capability-based Access)。例如:
// main.go —— 必须显式注册 JS 函数才能调用
package main
import (
"syscall/js"
)
func main() {
js.Global().Set("add", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
return args[0].Float() + args[1].Float() // 仅暴露加法能力
}))
select {} // 阻塞主 goroutine,防止退出
}
构建并启用需两步:
GOOS=js GOARCH=wasm go build -o main.wasm- 将
main.wasm与wasm_exec.js(Go SDK 提供)一同部署,通过<script>加载并初始化。
合规基线核心约束
| 维度 | 强制要求 |
|---|---|
| 内存隔离 | Wasm 线性内存独立于 JS 堆,不可越界读写 |
| 权限最小化 | 所有外部调用需经 js.Value.Call() 显式授权 |
| 源码透明性 | .wasm 文件可反编译为 WAT,但无符号表与源码映射 |
任何绕过 syscall/js 直接调用宿主 API 的尝试(如尝试 unsafe.Pointer 转 JS 对象)将触发运行时 panic 或被浏览器拒绝加载。安全边界不由 Go 运行时定义,而由 Wasm 标准与浏览器引擎共同强制实施。
第二章:CSP策略绕过风险的深度剖析与防御实践
2.1 CSP指令语义解析与Go WASM上下文适配
CSP(Communicating Sequential Processes)模型在Go中通过chan原语体现,而WASM运行时缺乏原生协程调度能力,需将Go的channel操作映射为可序列化的指令流。
指令语义抽象层
CSP核心指令被抽象为三类原子操作:
SEND(chan_id, value):阻塞式发送,触发WASM内存写入与事件通知RECV(chan_id, ptr):从通道读取并写入线性内存指定地址SELECT([...cases]):编译为轮询+条件跳转的WebAssembly control flow
Go Runtime上下文桥接
;; 示例:RECV指令生成的WAT片段(简化)
(func $recv (param $chan_id i32) (param $dst_ptr i32)
local.get $chan_id
call $go_chan_peek ;; 调用Go导出函数检查缓冲区
if
local.get $dst_ptr
local.get $chan_id
call $go_chan_pop ;; 原子弹出并写入线性内存
end)
该函数依赖Go导出的$go_chan_pop,其参数$dst_ptr指向WASM线性内存中预留的8字节接收缓冲区,确保跨运行时内存安全。
指令执行状态映射表
| 指令类型 | WASM内存偏移 | Go runtime关联字段 |
|---|---|---|
| SEND | 0x1000 + chan_id * 16 |
runtime.hchan.sendq |
| RECV | 0x2000 + chan_id * 16 |
runtime.hchan.recvq |
graph TD
A[CSP指令流] --> B{指令分发器}
B -->|SEND| C[Go runtime sendq入队]
B -->|RECV| D[线性内存写入+notify]
C & D --> E[WASM event loop唤醒]
2.2 Go生成HTML/JS时内联脚本的自动剥离机制
Go 的 html/template 包默认对 <script> 内容执行上下文感知的转义,但不主动剥离内联脚本——剥离需显式干预。
安全策略驱动的剥离逻辑
当模板中出现 {{.JSCode}} 且 .JSCode 类型为 template.JS 时,渲染器仅做 HTML 转义,不执行剥离;若使用自定义 SafeJS 类型配合预处理器,则可触发剥离:
func StripInlineScripts(s string) string {
re := regexp.MustCompile(`(?i)<script\b[^>]*>(.*?)</script>`)
return re.ReplaceAllString(s, "")
}
此正则匹配所有
<script>标签(含属性与闭合变体),替换为空字符串。注意:不处理 CDATA 或注释包裹的脚本,属基础防护层。
剥离时机对比表
| 阶段 | 是否剥离 | 说明 |
|---|---|---|
html/template 渲染 |
否 | 仅转义,保留标签结构 |
| 中间件预处理 | 是 | 可集成 CSP nonce 注入逻辑 |
典型流程
graph TD
A[原始HTML模板] --> B{含<script>?}
B -->|是| C[调用StripInlineScripts]
B -->|否| D[直出]
C --> E[注入nonce属性]
E --> F[输出安全HTML]
2.3 非标准资源加载路径(如Go embed FS)的CSP白名单动态生成
传统 CSP 白名单依赖静态 script-src 或 style-src 路径,但 Go 的 embed.FS 将资源编译进二进制,无 HTTP 路径暴露,导致 sha256-... 或 'unsafe-inline' 成为常见妥协。
动态哈希注入机制
构建时遍历嵌入文件,计算内容哈希并注入 HTML 模板:
// 生成 CSP nonce + 内联脚本哈希
fs, _ := fs.Sub(assets, "static/js")
entries, _ := fs.ReadDir(".")
for _, e := range entries {
data, _ := fs.ReadFile(e.Name())
hash := fmt.Sprintf("sha256-%s", base64.StdEncoding.EncodeToString(
sha256.Sum256(data).Sum(nil)))
cspDirectives = append(cspDirectives, fmt.Sprintf("script-src 'self' %s", hash))
}
逻辑分析:
fs.Sub定位子目录;ReadDir获取文件列表;ReadFile提取原始字节;sha256.Sum256生成确定性哈希;Base64 编码适配 CSP 格式。参数assets为 embed 声明的变量名,"static/js"是嵌入路径前缀。
支持的资源类型与哈希策略
| 类型 | 策略 | 示例 directive |
|---|---|---|
| JS | 内容哈希(SHA256) | script-src 'self' sha256-... |
| CSS | 内容哈希(SHA256) | style-src 'self' sha256-... |
| 图片 | 路径白名单 | img-src 'self' data: |
流程概览
graph TD
A[go:embed assets] --> B[构建时扫描 FS]
B --> C[逐文件计算 SHA256]
C --> D[生成 CSP header 指令]
D --> E[注入 HTTP 响应头或 meta 标签]
2.4 基于Go HTTP Handler的CSP Header自动化注入与策略审计
CSP(Content Security Policy)是防御XSS与数据注入的关键防线。在Go Web服务中,手动设置Content-Security-Policy响应头易遗漏或配置冲突,需通过中间件统一管控。
自动化注入Handler实现
func CSPMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
policy := "default-src 'self'; script-src 'self' https:; img-src * data:;"
w.Header().Set("Content-Security-Policy", policy)
next.ServeHTTP(w, r)
})
}
该中间件在请求链路入口处注入标准化策略:default-src 'self'限制基础资源加载域;script-src显式允许内联脚本被禁用(因未含 'unsafe-inline');img-src * data:兼容外部图床与Base64图像。策略字符串可动态构建,支持环境变量或配置中心驱动。
策略合规性审计维度
| 审计项 | 检查逻辑 | 风险等级 |
|---|---|---|
unsafe-inline |
检测是否出现在script-src/style-src |
高 |
*通配符使用 |
统计img-src/connect-src中*出现频次 |
中 |
report-uri缺失 |
验证是否配置违规上报端点 | 中 |
审计流程示意
graph TD
A[HTTP Request] --> B[CSP Middleware]
B --> C{策略注入}
C --> D[响应头写入]
D --> E[审计器扫描Header]
E --> F[日志告警/指标上报]
2.5 实战:绕过检测工具(CSP Evaluator + custom Go fuzzing harness)构建
为什么静态评估不够?
CSP Evaluator 可识别常见策略缺陷(如 unsafe-inline),但无法捕获动态绕过路径,例如:
nonce值在服务端被重复使用strict-dynamic与unsafe-hashes的组合副作用- 通过
data:或blob:URL 触发的隐式执行链
自定义 Go Fuzzing Harness 设计要点
func FuzzCSPBypass(f *testing.F) {
f.Add("script-src 'self'; object-src 'none'") // seed corpus
f.Fuzz(func(t *testing.T, policy string) {
p := csp.Parse(policy)
if p == nil { return }
// 模拟浏览器 CSP 解析器行为(含 nonce/strict-dynamic 语义)
if p.AllowsEval() || p.AllowsDataURI() {
t.Fatal("bypass found: eval or data: allowed")
}
})
}
逻辑分析:该 fuzzing harness 将策略字符串输入解析为结构化策略对象,重点校验
AllowsEval()和AllowsDataURI()—— 这两类能力常被用于绕过strict-dynamic保护。f.Add()提供初始语料,Go Fuzz 引擎自动变异生成边缘策略变体。
关键绕过模式对比
| 绕过类型 | 触发条件 | 检测工具盲区 |
|---|---|---|
nonce 重用 |
同一 nonce 在多个 <script> 中生效 |
CSP Evaluator 不模拟运行时上下文 |
strict-dynamic + unsafe-hashes |
存在可预测哈希的内联脚本 | 静态哈希计算缺失动态注入路径 |
流程:从策略到绕过验证
graph TD
A[原始 CSP Header] --> B[CSP Evaluator 静态扫描]
B --> C{发现 low-risk warning?}
C -->|Yes| D[输入 Go fuzzing harness]
C -->|No| E[仍需动态验证]
D --> F[生成变异策略]
F --> G[模拟 Chrome 115+ CSP 解析逻辑]
G --> H[触发 eval/data: 执行?]
H -->|Yes| I[确认绕过路径]
第三章:WASM沙箱逃逸的底层机制与加固方案
3.1 Go WASM运行时内存模型与WebAssembly Spec边界对齐分析
Go 的 WASM 运行时将 runtime.mem 映射为单一线性内存(wasm.Memory),严格遵循 WebAssembly Core Spec v1.0 的 64KiB 页面粒度与 32 位地址空间约束。
内存布局对齐关键点
- Go heap 起始于
0x10000(避开保留的前 64KiB) syscall/js桥接层通过mem.UnsafePointer实现零拷贝视图- 所有
[]byte切片底层指向wasm.Memory.Bytes()
数据同步机制
// 将 Go 字符串安全写入 WASM 线性内存
func writeStringToWasm(s string) uint32 {
ptr := wasm.Memory.Bytes() // 获取底层字节数组视图
offset := uint32(len(ptr)) // 动态分配偏移(简化示意)
copy(ptr[offset:], s) // 直接内存写入
return offset // 返回起始地址(符合 WASM i32 指针语义)
}
该函数规避了 js.ValueOf() 序列化开销;offset 必须 ≤ 0x100000000(32 位寻址上限),且 ptr 长度受 wasm.Memory.Grow() 动态扩展限制。
| 对齐维度 | Go WASM 运行时 | WebAssembly Spec |
|---|---|---|
| 地址宽度 | 32-bit | Mandatory i32 |
| 页面大小 | 65536 bytes | Exactly 64KiB |
| 最大内存页数 | 65536 (4GiB) | Optional limit |
graph TD
A[Go runtime.alloc] --> B[Linear Memory grow?]
B -->|Yes| C[wasm.Memory.Grow(1)]
B -->|No| D[write at offset]
D --> E[JS side: new Uint8Array(mem.buffer, offset, len)]
3.2 syscall/js桥接层中的指针越界与类型混淆漏洞复现与修复
漏洞触发场景
Go WebAssembly 运行时通过 syscall/js 将 Go 函数暴露给 JavaScript,若未严格校验 js.Value 类型及底层内存边界,易引发越界读写。
复现代码片段
func exposeVuln() {
js.Global().Set("unsafeCopy", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
src := args[0].Int() // ❌ 未校验是否为有效内存地址
dst := args[1].Int()
for i := 0; i < args[2].Int(); i++ {
*(*byte)(unsafe.Pointer(uintptr(src + i))) =
*(*byte)(unsafe.Pointer(uintptr(dst + i))) // ⚠️ 无长度/权限检查
}
return nil
}))
}
逻辑分析:
args[0].Int()直接转为uintptr,绕过 Go 内存安全机制;unsafe.Pointer强制解引用任意整数地址,导致跨 ArrayBuffer 边界访问或覆盖 runtime 元数据。参数src/dst应为js.Value中的ArrayBufferView偏移,而非裸整数。
修复策略对比
| 方案 | 安全性 | 性能开销 | 适用场景 |
|---|---|---|---|
js.Value.Call("get", i) |
高(沙箱内) | 高(JS调用开销) | 小数据量 |
js.CopyBytesToGo() + bounds check |
中高(需手动校验) | 低 | 大块二进制同步 |
WASI syscalls(via wazero) |
最高(OS级隔离) | 中 | 生产环境 |
数据同步机制
graph TD
A[JS ArrayBuffer] -->|TypedArray.slice| B[Go []byte]
B --> C{Bounds Check}
C -->|OK| D[memcpy via runtime·memmove]
C -->|Fail| E[panic: invalid memory access]
3.3 Go 1.22+ WASM GC机制与非托管内存泄漏导致的沙箱逃逸链验证
Go 1.22 起,WASM 运行时引入 分代式 GC 钩子注入点,允许在 syscall/js 回调中延迟释放 JS 引用,但未覆盖 unsafe.Pointer 转换路径。
关键泄漏路径
js.Value.Call()返回对象被unsafe.Pointer持有后未注册Finalizer- WASM 线性内存中
malloc分配的缓冲区未通过runtime.SetFinalizer关联 JS 对象生命周期
// 示例:隐蔽泄漏构造
ptr := C.malloc(1024)
js.Global().Set("leakRef", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
return (*[1024]byte)(ptr)[:1024] // 非托管切片逃逸至 JS 全局作用域
}))
// ❗ ptr 无 Finalizer,GC 不知其关联 JS 引用
此代码使
ptr在 JS 侧长期持有 WASM 内存地址,当 Go GC 回收该内存后,JS 仍可读写——触发越界访问与沙箱逃逸。
GC 可见性对比(Go 1.21 vs 1.22+)
| 版本 | JS 引用跟踪 | unsafe.Pointer 关联 |
沙箱逃逸风险 |
|---|---|---|---|
| 1.21 | ✅ | ❌ | 中 |
| 1.22+ | ✅✅(增强) | ❌(仍缺失) | 高 |
graph TD
A[Go 函数 malloc] --> B[ptr = unsafe.Pointer]
B --> C[js.Global.Set leakRef]
C --> D[JS 保留引用]
D --> E[Go GC 释放 ptr]
E --> F[JS 读写已释放内存 → 沙箱逃逸]
第四章:DOM交互引发XSS的Go特有模式与防护体系
4.1 Go模板引擎在客户端渲染场景下的自动转义失效边界分析
Go模板的html/template包默认对变量插值执行HTML转义,但该机制仅作用于服务端渲染上下文。当模板输出被JavaScript动态注入DOM(如innerHTML = tmplResult)时,转义结果可能被浏览器二次解析,导致XSS风险。
客户端注入破坏转义链
// 服务端:看似安全的转义输出
t := template.Must(template.New("safe").Parse(`{{.Content}}`))
buf := new(bytes.Buffer)
t.Execute(buf, map[string]interface{}{"Content": "<script>alert(1)</script>"})
// 输出:<script>alert(1)</script>
逻辑分析:template.HTML类型可绕过转义,而json.Marshal序列化后的字符串若被innerHTML直接赋值,浏览器会将<还原为<,重新激活脚本。
失效边界清单
- 使用
innerHTML/outerHTML插入模板输出 - 模板内容经
JSON.parse()后拼接进HTML字符串 template.JS类型与eval()或Function构造器组合
安全边界对比表
| 场景 | 转义是否生效 | 原因 |
|---|---|---|
fmt.Println(tmpl.Execute(...)) |
✅ | 纯文本输出,无HTML解析 |
document.write(tmplOutput) |
❌ | 浏览器主动解析HTML实体 |
el.textContent = tmplOutput |
✅ | 文本节点不触发HTML解析 |
graph TD
A[Go模板执行] --> B[HTML转义]
B --> C{客户端如何使用?}
C -->|innerHTML/insertAdjacentHTML| D[浏览器实体解码→执行]
C -->|textContent/setAttribute| E[保持转义字符原样]
4.2 syscall/js.Value.Call跨域DOM操作中的事件监听器注入风险建模
问题根源:跨域上下文中的隐式信任链
syscall/js.Value.Call 允许 Go 代码在浏览器中调用 JavaScript 函数,但当目标 DOM 节点来自跨域 iframe 或 document.domain 已被修改的上下文时,this 绑定与事件代理权可能失控。
风险触发路径
// 恶意场景:向跨域 iframe 的 button 注入监听器
btn := js.Global().Get("iframe").Get("contentDocument").Call("getElementById", "submit")
btn.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// 此回调在跨域 iframe 的执行上下文中运行,可窃取其 DOM 数据
js.Global().Get("console").Call("log", "Stolen context:", this)
return nil
}))
逻辑分析:
btn是跨域 iframe 中的原生 DOM 元素,addEventListener调用虽成功(因同源策略不阻塞注册),但回调函数在 iframe 的全局作用域中执行,this指向 iframe 的window,且可访问其document.cookie、localStorage等敏感 API。Go 侧无法审计或沙箱化该 JS 执行环境。
防御维度对比
| 措施 | 是否阻断注入 | 是否影响功能 | 备注 |
|---|---|---|---|
CSP: script-src 'self' |
❌ | ✅ | 无法限制 addEventListener 内联回调 |
sandbox="allow-scripts" |
❌ | ✅ | 默认允许事件监听器注册 |
postMessage 代理层 |
✅ | ⚠️ | 需重写所有 DOM 交互为消息通道 |
安全建模关键节点
graph TD
A[Go 调用 js.Value.Call] --> B{目标 DOM 是否跨域?}
B -->|是| C[回调执行上下文 = 跨域 iframe 全局]
B -->|否| D[执行上下文 = 当前页面]
C --> E[监听器可读取跨域 document.cookie]
C --> F[监听器可调用跨域 window.API]
4.3 Go生成的Web Component属性绑定与innerHTML误用场景识别
属性绑定的安全边界
Go模板生成Web Component时,若直接将结构化数据注入 innerHTML,会绕过自定义元素的属性反射机制:
// ❌ 危险:动态拼接HTML字符串
html := fmt.Sprintf(`<my-card title="%s" content="%s"></my-card>`,
template.HTMLEscapeString(title),
template.HTMLEscapeString(content))
该写法未触发 attributeChangedCallback,且 content 中的 <script> 会被执行——违反Web Components规范。
innerHTML误用的典型模式
| 场景 | 风险等级 | 触发条件 |
|---|---|---|
| Go模板直接输出HTML | ⚠️ 高 | {{.RawHTML}} 未转义 |
| 属性值含HTML标签 | 🚫 严重 | title="Hello <b>World</b>" |
安全替代方案
- ✅ 使用
setAttribute()+customElements.define()的生命周期钩子 - ✅ 通过
props对象驱动渲染(如 LitElement 的@property())
// ✅ 正确:属性变更触发同步更新
class MyCard extends HTMLElement {
static get observedAttributes() { return ['title', 'content']; }
attributeChangedCallback(name, old, val) {
this.shadowRoot.querySelector(`.${name}`).textContent = val;
}
}
此方式确保属性变更经由标准管道,避免DOM注入漏洞。
4.4 基于Go AST扫描的前端代码安全合规检查工具链开发(含等保三级检查项映射)
核心架构设计
采用分层插件化架构:AST解析层 → 规则引擎层 → 合规映射层 → 报告生成层。各层通过接口契约解耦,支持动态加载等保三级检查规则(如GB/T 22239-2019中“8.2.2.3 输入验证”“8.2.2.5 XSS防护”)。
关键AST遍历逻辑
func (v *XSSVisitor) Visit(node ast.Node) ast.Visitor {
if call, ok := node.(*ast.CallExpr); ok {
if ident, ok := call.Fun.(*ast.Ident); ok &&
ident.Name == "eval" || strings.Contains(ident.Name, "innerHTML") {
v.issues = append(v.issues, Issue{
RuleID: "SEC-INPUT-03", // 映射等保三级“输入输出验证”
Severity: "HIGH",
Pos: call.Pos(),
})
}
}
return v
}
该访客遍历AST树,精准捕获危险API调用;RuleID 与等保三级检查项双向绑定,Pos() 提供源码定位能力,支撑CI/CD中自动修复建议。
等保三级检查项映射表
| 等保条款编号 | 检查项描述 | 对应AST模式 | 严重等级 |
|---|---|---|---|
| 8.2.2.3 | 输入数据未校验 | document.write, innerHTML= |
HIGH |
| 8.2.2.5 | 未过滤用户可控DOM操作 | eval(), setTimeout(string) |
CRITICAL |
扫描流程
graph TD
A[源码解析为Go AST] --> B[规则插件并行遍历]
B --> C{匹配等保三级规则?}
C -->|是| D[生成带条款编号的JSON报告]
C -->|否| E[跳过]
D --> F[对接Jenkins/SonarQube]
第五章:等保三级合规落地的关键挑战与演进方向
合规成本与业务敏捷性的结构性矛盾
某全国性股份制银行在2023年开展核心交易系统等保三级复测时,发现其微服务架构下的API网关日志审计模块无法满足《GB/T 22239-2019》中“安全审计”要求的“审计记录留存不少于180天”及“审计记录不可删改”。为满足要求,团队被迫回滚至单体架构日志中心,导致DevOps流水线交付周期从2.3小时延长至6.7小时。该案例凸显出传统审计机制与云原生环境间的适配断层——Kubernetes Pod生命周期短、Sidecar注入动态性强,直接套用等保二级日志方案将引发可观测性盲区。
跨云环境下的责任共担模型模糊地带
下表对比了主流云服务商在等保三级中对“入侵防范”控制点的实现差异:
| 控制项 | 阿里云(金融云) | AWS China(宁夏区域) | 华为云Stack | 是否满足等保三级基线 |
|---|---|---|---|---|
| 网络层DDoS防护 | 免费提供5Gbps基础防护 | 需额外购买Shield Advanced | 依赖客户自建Anti-DDoS设备 | 否(华为云Stack需客户自行部署) |
| 主机层RASP能力 | 支持Java/Python运行时插桩 | 仅支持WAF规则引擎 | 无原生RASP支持 | 部分满足(阿里云达标) |
安全能力内生化实践路径
某省级政务云平台采用“策略即代码(Policy-as-Code)”重构等保三级持续合规流程:
- 使用Open Policy Agent(OPA)校验K8s YAML模板是否含
hostNetwork: true等高危配置; - 将等保三级“剩余信息保护”要求编译为Rego策略,自动拦截未启用AES-256加密的ETCD备份任务;
- 每日凌晨执行
opa eval --data policy.rego --input k8s_resources.json "data.etc.policy.violations"生成合规报告。该机制使配置类问题修复时效从72小时压缩至15分钟内。
人员能力与组织流程的协同瓶颈
某央企能源集团在等保三级整改中暴露出典型能力错配:安全团队掌握ISO 27001体系但缺乏容器镜像签名验证实操经验,运维团队熟悉Ansible却不知晓《基本要求》中“可信验证”控制点的技术映射关系。最终通过建立“红蓝联合沙盒”——每月模拟勒索软件攻击链,强制要求安全工程师编写Falco规则、运维工程师部署Notary服务,三个月内完成127项控制项的闭环验证。
flowchart LR
A[等保三级测评启动] --> B{资产识别阶段}
B --> C[识别云上数据库实例]
B --> D[识别混合云API网关]
C --> E[自动调用云厂商API获取加密配置]
D --> F[抓取OpenAPI Spec并校验OAuth2.0 scope最小化]
E --> G[生成加密强度评估报告]
F --> G
G --> H[对接CMDB更新资产安全标签]
新型威胁驱动的控制项动态扩展
2024年某证券公司遭遇供应链投毒事件后,其等保三级整改新增三项增强控制:
- 对CI/CD流水线中npm/yarn包执行SBOM比对(使用Syft+Grype);
- 在K8s准入控制器中集成Sigstore Cosign验证镜像签名;
- 将Terraform Provider版本锁定策略写入GitLab CI模板。这些措施已纳入当地网信办发布的《金融行业等保三级补充指引(2024试行版)》附件B。
