[L-07][low] 监听与 HTTP 小问题合集:XFF 解析可伪造、每连接新建 tls.Config、/metrics 令牌非常量时间比较、重复创建 listener #26

Closed
opened 2026-09-30 13:56:56 +08:00 by nixevol · 1 comment
Owner

编号:L-07 严重级:low 工作线:监听与 HTTP(internal/listener、internal/httpx、serve 的 HTTP 装配) 来源:审查 P-20、P-21、P-26、A-17、P-24
依赖:无 被依赖:无

结论与统一方案

  1. XFF:两份实现合并为 httpx.ClientIP 一份,WS 也调用它;用 strings.Join(r.Header.Values("X-Forwarded-For"), ",") 合并多行;从右往左遇到无法解析的项就停下,回退到对端地址。(审查 P-20,身份区待核实第 2 条)
  2. TLS:tls.Config 只建一次并复用;证书检查间隔改为 1–5 分钟。(审查 P-21)
  3. /metrics 令牌:两边各取 SHA-256 后用 subtle.ConstantTimeCompare 比较;可选失败计入 LockAdminIP。(审查 A-17、P-26)
  4. 重复创建 listener:serve 先用 listener.ParseTrustedProxies 得到 ProxySet,只调用一次 listener.New。(审查 P-24)

改动文件

internal/httpx/clientip.go、metrics.go;internal/listener/proxy.go、tls.go、server.go;internal/broker/ws.go(改为调用 httpx.ClientIP);cmd/nixmsg/serve.go(listener 装配段)。

与其他问题的交互 / 冲突说明

ws.go 与 L-01 同文件,监听线内顺序合入;serve.go 只改 listener 装配段。

验收与测试

  • XFF:两行头、带端口的项、IPv6 方括号写法。
  • 两次连接之间 DidResume 为真;先换证书后换私钥,在短周期内生效。
  • 启动日志里 "tls certificate loaded" 只出现一次。

问题明细(各区审查原文,证据含文件与行号)

以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。解决方案以本 issue 上方的"结论与统一方案"为准;原文里的方案与之不一致时,按上方执行。

[P-20] X-Forwarded-For 只读第一行,且遇到无法解析的项会继续向左取

  • 严重级:low
  • 分类:安全
  • 现象与影响:
    • WS 用的 listener.ProxySet.ClientIP 和后台、注册用的 httpx.ClientIP 两份实现都用 Header.Get,只取第一行 X-Forwarded-For。
    • 两种情况下可被伪造:
      • 像 HAProxy option forwardfor 这类"另起一行"而不是合并到同一行的代理,第一行正是客户端自己伪造的值;
      • 代理追加的项如果带端口,解析失败后会继续往左取到伪造的值。
    • 后果是按 IP 计数的锁定可以被绕过,或者被嫁祸给别人的 IP。
    • 按文档用 1Panel/OpenResty 部署(合并成一行、只写 IP)不受影响。
  • 证据:listener/proxy.go:65-80;httpx/clientip.go:23-38。
  • 文档依据:DEVELOPMENT 4.5;PRD D18。
  • 为何不是故意设计:两份实现都是按单行头写的,属于实现遗漏。
  • 解决方案:用 strings.Join(r.Header.Values("X-Forwarded-For"), ",") 合并所有行;从右往左遇到无法解析的项就停下,回退到对端地址;两份实现合并成 httpx 一份,WS 也调用它。
  • 改动文件:internal/httpx/clientip.go、internal/listener/proxy.go、internal/broker/ws.go
  • 与其他模块的交互/冲突风险:无。
  • 需补测试:两行 XFF、带端口的项、IPv6 方括号写法。
  • 置信度:代码阅读确定

[P-21] TLS:每个连接新建一个 tls.Config;证书检查间隔 1 小时

  • 严重级:low
  • 分类:质量 / 性能
  • 现象与影响:
    • 每个连接都新建 tls.Config,每个 Config 有自己的会话票据密钥,客户端重连永远要做完整握手。
    • 证书每小时检查一次。1Panel 先写证书再写私钥时,如果恰好在两次写入之间检查,加载失败并保留旧证书(这一点是对的),但要再等 1 小时,超出了"1 小时内生效"。
  • 证据:listener/server.go:298;tls.go:62-75、110-117。
  • 文档依据:PRD F22「替换证书文件后,新连接在 1 小时内用上新证书」。
  • 为何不是故意设计:没有记录。
  • 解决方案:在 NewCertReloader 里只建一次 tls.Config 并复用;检查间隔改为 1 到 5 分钟(只是 stat 文件,开销可以忽略)。
  • 改动文件:internal/listener/tls.go、server.go
  • 与其他模块的交互/冲突风险:无。
  • 需补测试:两次连接之间 DidResume 为真;先换证书后换私钥,在短周期内生效。
  • 置信度:代码阅读确定

[P-26] /metrics 令牌用普通字符串比较

  • 严重级:low
  • 分类:安全
  • 现象与影响:令牌校验用的是 !=,比较耗时随匹配前缀变化,理论上存在时序侧信道。
  • 证据:httpx/metrics.go:17-21(auth[len(prefix):] != token)。
  • 文档依据:DEVELOPMENT 12(令牌比较用常量时间)。
  • 为何不是故意设计:其他令牌都用常量时间比较,这里是遗漏。
  • 解决方案:改用 subtle.ConstantTimeCompare。
  • 改动文件:internal/httpx/metrics.go
  • 与其他模块的交互/冲突风险:无。
  • 需补测试:沿用现有用例即可。
  • 置信度:代码阅读确定

[A-17] /metrics 令牌比较不是常量时间,也不限制尝试次数

  • 严重级:low
  • 分类:安全
  • 现象与影响:共用端口时,/metrics 令牌用 != 比较,可以不受限制地反复尝试。指标本身不含敏感明细,所以影响有限。
  • 证据:internal/httpx/metrics.go:17-19。
  • 文档依据:DEVELOPMENT 4.3、§12(约 1154 行)。
  • 为何不是故意设计:DEVIATIONS 没有相关条目。
  • 解决方案:对两边各取 SHA-256 后用 subtle.ConstantTimeCompare 比较;可选:失败计入 LockAdminIP。
  • 改动文件:internal/httpx/metrics.go。
  • 交互/冲突风险:无。
  • 需补测试:现有的 404/401 测试保持通过。
  • 置信度:代码阅读确定

[P-24] serve 调了两次 listener.New,泄漏一个证书重载 goroutine

  • 严重级:low
  • 分类:质量
  • 现象与影响:第一次 listener.New 已经启动了证书重载的 goroutine,这个实例随后被丢弃且从没关闭。之后每小时会重载两次、打两份日志;没配证书时"plaintext only"警告也打两遍。
  • 证据:serve.go:262-291;tls.go:26-41。
  • 文档依据:无对应条款(代码质量问题)。
  • 为何不是故意设计:第二次 New 只是为了拿 ProxySet,第一次的实例没被关闭,属于遗漏。
  • 解决方案:先用 listener.ParseTrustedProxies 得到 ProxySet,组装好路由后只调一次 listener.New。
  • 改动文件:cmd/nixmsg/serve.go
  • 与其他模块的交互/冲突风险:无。
  • 需补测试:启动日志里 "tls certificate loaded" 只出现一次。
  • 置信度:代码阅读确定

复审基线:main 4059a15(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。

**编号**:L-07 **严重级**:low **工作线**:监听与 HTTP(internal/listener、internal/httpx、serve 的 HTTP 装配) **来源**:审查 P-20、P-21、P-26、A-17、P-24 **依赖**:无 **被依赖**:无 ### 结论与统一方案 1. **XFF**:两份实现合并为 `httpx.ClientIP` 一份,WS 也调用它;用 `strings.Join(r.Header.Values("X-Forwarded-For"), ",")` 合并多行;从右往左遇到无法解析的项就停下,回退到对端地址。(审查 P-20,身份区待核实第 2 条) 2. **TLS**:`tls.Config` 只建一次并复用;证书检查间隔改为 1–5 分钟。(审查 P-21) 3. **/metrics 令牌**:两边各取 SHA-256 后用 `subtle.ConstantTimeCompare` 比较;可选失败计入 `LockAdminIP`。(审查 A-17、P-26) 4. **重复创建 listener**:serve 先用 `listener.ParseTrustedProxies` 得到 ProxySet,只调用一次 `listener.New`。(审查 P-24) ### 改动文件 `internal/httpx/clientip.go`、`metrics.go`;`internal/listener/proxy.go`、`tls.go`、`server.go`;`internal/broker/ws.go`(改为调用 `httpx.ClientIP`);`cmd/nixmsg/serve.go`(listener 装配段)。 ### 与其他问题的交互 / 冲突说明 ws.go 与 L-01 同文件,监听线内顺序合入;serve.go 只改 listener 装配段。 ### 验收与测试 - XFF:两行头、带端口的项、IPv6 方括号写法。 - 两次连接之间 `DidResume` 为真;先换证书后换私钥,在短周期内生效。 - 启动日志里 "tls certificate loaded" 只出现一次。 --- ### 问题明细(各区审查原文,证据含文件与行号) > 以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。**解决方案以本 issue 上方的"结论与统一方案"为准**;原文里的方案与之不一致时,按上方执行。 #### [P-20] X-Forwarded-For 只读第一行,且遇到无法解析的项会继续向左取 - **严重级**:low - **分类**:安全 - **现象与影响**: - WS 用的 `listener.ProxySet.ClientIP` 和后台、注册用的 `httpx.ClientIP` 两份实现都用 `Header.Get`,只取第一行 X-Forwarded-For。 - 两种情况下可被伪造: - 像 HAProxy `option forwardfor` 这类"另起一行"而不是合并到同一行的代理,第一行正是客户端自己伪造的值; - 代理追加的项如果带端口,解析失败后会继续往左取到伪造的值。 - 后果是按 IP 计数的锁定可以被绕过,或者被嫁祸给别人的 IP。 - 按文档用 1Panel/OpenResty 部署(合并成一行、只写 IP)不受影响。 - **证据**:`listener/proxy.go:65-80`;`httpx/clientip.go:23-38`。 - **文档依据**:DEVELOPMENT 4.5;PRD D18。 - **为何不是故意设计**:两份实现都是按单行头写的,属于实现遗漏。 - **解决方案**:用 `strings.Join(r.Header.Values("X-Forwarded-For"), ",")` 合并所有行;从右往左遇到无法解析的项就停下,回退到对端地址;两份实现合并成 httpx 一份,WS 也调用它。 - **改动文件**:`internal/httpx/clientip.go`、`internal/listener/proxy.go`、`internal/broker/ws.go` - **与其他模块的交互/冲突风险**:无。 - **需补测试**:两行 XFF、带端口的项、IPv6 方括号写法。 - **置信度**:代码阅读确定 #### [P-21] TLS:每个连接新建一个 tls.Config;证书检查间隔 1 小时 - **严重级**:low - **分类**:质量 / 性能 - **现象与影响**: - 每个连接都新建 `tls.Config`,每个 Config 有自己的会话票据密钥,客户端重连永远要做完整握手。 - 证书每小时检查一次。1Panel 先写证书再写私钥时,如果恰好在两次写入之间检查,加载失败并保留旧证书(这一点是对的),但要再等 1 小时,超出了"1 小时内生效"。 - **证据**:`listener/server.go:298`;`tls.go:62-75`、`110-117`。 - **文档依据**:PRD F22「替换证书文件后,新连接在 1 小时内用上新证书」。 - **为何不是故意设计**:没有记录。 - **解决方案**:在 `NewCertReloader` 里只建一次 `tls.Config` 并复用;检查间隔改为 1 到 5 分钟(只是 stat 文件,开销可以忽略)。 - **改动文件**:`internal/listener/tls.go`、`server.go` - **与其他模块的交互/冲突风险**:无。 - **需补测试**:两次连接之间 `DidResume` 为真;先换证书后换私钥,在短周期内生效。 - **置信度**:代码阅读确定 #### [P-26] /metrics 令牌用普通字符串比较 - **严重级**:low - **分类**:安全 - **现象与影响**:令牌校验用的是 `!=`,比较耗时随匹配前缀变化,理论上存在时序侧信道。 - **证据**:`httpx/metrics.go:17-21`(`auth[len(prefix):] != token`)。 - **文档依据**:DEVELOPMENT 12(令牌比较用常量时间)。 - **为何不是故意设计**:其他令牌都用常量时间比较,这里是遗漏。 - **解决方案**:改用 `subtle.ConstantTimeCompare`。 - **改动文件**:`internal/httpx/metrics.go` - **与其他模块的交互/冲突风险**:无。 - **需补测试**:沿用现有用例即可。 - **置信度**:代码阅读确定 #### [A-17] `/metrics` 令牌比较不是常量时间,也不限制尝试次数 - 严重级:low - 分类:安全 - 现象与影响:共用端口时,`/metrics` 令牌用 `!=` 比较,可以不受限制地反复尝试。指标本身不含敏感明细,所以影响有限。 - 证据:`internal/httpx/metrics.go:17-19`。 - 文档依据:DEVELOPMENT 4.3、§12(约 1154 行)。 - 为何不是故意设计:DEVIATIONS 没有相关条目。 - 解决方案:对两边各取 SHA-256 后用 `subtle.ConstantTimeCompare` 比较;可选:失败计入 `LockAdminIP`。 - 改动文件:`internal/httpx/metrics.go`。 - 交互/冲突风险:无。 - 需补测试:现有的 404/401 测试保持通过。 - 置信度:代码阅读确定 #### [P-24] serve 调了两次 listener.New,泄漏一个证书重载 goroutine - **严重级**:low - **分类**:质量 - **现象与影响**:第一次 `listener.New` 已经启动了证书重载的 goroutine,这个实例随后被丢弃且从没关闭。之后每小时会重载两次、打两份日志;没配证书时"plaintext only"警告也打两遍。 - **证据**:`serve.go:262-291`;`tls.go:26-41`。 - **文档依据**:无对应条款(代码质量问题)。 - **为何不是故意设计**:第二次 New 只是为了拿 ProxySet,第一次的实例没被关闭,属于遗漏。 - **解决方案**:先用 `listener.ParseTrustedProxies` 得到 ProxySet,组装好路由后只调一次 `listener.New`。 - **改动文件**:`cmd/nixmsg/serve.go` - **与其他模块的交互/冲突风险**:无。 - **需补测试**:启动日志里 "tls certificate loaded" 只出现一次。 - **置信度**:代码阅读确定 --- <sub>复审基线:main `4059a15`(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。</sub>
nixevol added the P3-lowlane/listenerreview-2026-09-30 labels 2026-09-30 13:56:56 +08:00
Author
Owner

已合入 origin/main 0c9b459。落地提交 7c926a0 fix: 修复 TLS ConnectionState、SPA 回退、健康检查与监听小问题 (#26)。

已合入 origin/main `0c9b459`。落地提交 `7c926a0` fix: 修复 TLS ConnectionState、SPA 回退、健康检查与监听小问题 (#26)。
Sign in to join this conversation.