docs(memory): 记录审计结论(CSP 默认 enforce、分享卡字体限制、复查新发现)

- topics/sec.md:两轮修复的结论、全局复查新发现(私聊 POST 越权、页面密码 next)、
  CSP 白名单口径、字体只能用 glyf 轮廓这条硬限制,以及复现脚本的两种「假红」
- MEMORY.md:安全小节补跳转白名单、GET/POST 校验不对称、CSP/umami、注册防枚举
- 2026-10-27.md / topics/build.md:本地 WSL 部署与验收的具体跑法
This commit is contained in:
tamakyi
2026-10-27 12:37:55 +08:00
parent e807dea218
commit 430c6ef6d8
6 changed files with 384 additions and 2 deletions
+77
View File
@@ -47,3 +47,80 @@
- 顺手修预览测试:`GrowthMoM` 原来写死 12.3,与 `monthly` 造的数据对不上,
而新句会同时印出两个月的条数 → 截图会显示「5620 比 4890 多了 12.3%」这种假证据。
改成从 `monthly[10]/monthly[9]` 算。
## 安全评估(AI 挖洞背景下的一次全量自检)
报告:`outputs/security-audit-2026-10-25.md`,脚本在 `outputs/sec-audit-2026-10-25/`。
结论:无 getshell / SQLi / 未授权读数据;但 2 个 P1 直击「匿名」承诺。
### P1-1 匿名提问可植入远程图片(去匿名化)
- `markdown.go` 的 `safeURL` 只校验协议,`<img src=https://任意域>` 能原样输出。
- 唯一挡住它的是审核「广告_引流类」维度,而该维度**只在 strict 档生效**
(`censor/policy.go`:standard 只有 Abuse/Contraband,lax 为空)。
- 实测:账号切 standard → `<img src="https://tb871870.example.org/standard.png">` 直接入库并渲染;
切回 strict 才被拒。→ 修法必须在 `markdown.go` 层限制 img src 为站内/白名单,不能依赖档位。
- 同时确认 8 种混淆(实体编码、`java\tscript:`、协议相对 `//`、反斜杠)都被审核识别。
### P1-2 访问密码可爆破 + 提问密码校验早于验证码
- `UpdatePagePassword`/`UpdateAskPassword` 只有 `maxlen:30`,**无 minlen**,可设 1 位。
- `page.go` 的 `New()` 第一件事就比对 AskPassword,验证码要到后面才校验 →
实测「错误提问密码 + `deadbeef:999999`」返回「提问密码错误」而不是验证码错误 →
**猜密码完全不消耗验证码**。限额 30/min(ask)、10/min(page)。
### 其余P2/P3
- 登录开放重定向:`to=/\evil.example` → `Location: /\evil.example`,浏览器按 WHATWG
把 `\` 当 `/` → `https://evil.example/`。`path.Clean` 不处理反斜杠。
- 注册接口枚举邮箱(返回「已注册」),与 forgot-password 的防枚举口径不一致。
- 无 CSP;`X-XSS-Protection: 1` 过时;`/user` 无 `Cache-Control: no-store`。
- 限流/自动封禁是进程内存态(多副本叠加、重启清零)。
- 匿名投票身份 = `vote_fp` cookie,清 cookie 即换身份,仅 200/min/IP 兜底。
- 登录 `to` 兜底是死代码:`path.Clean("/"+to)` 恒非空 → 登录后永远落 `/` 而非箱主页。
- JSON 响应无 `Content-Type`(实测 text/plain);429 无 `Retry-After`。
### 无法从外网确认的一项
`trust_proxy` 真实值。三种伪造头(XFF / X-Real-IP / EO-Client-IP)各 22 次都不被采信
(好的方向:无法伪造绕限流),但这既可能是 `trust_proxy=false`(→ 按 CF 边缘 IP 聚合,
限流与封禁会误伤整片区域),也可能是 true 且在 CF 侧删了 EO-Client-IP。需登服务器看 app.ini。
### 已验证没事(别重复怀疑)
CSRF 绑 uid(无 token POST → 400);18 条 XSS 载荷全灭;跨箱题目/投票越权全 302;
页面密码对主页/整箱分享图/单题分享图/API 四个出口都生效;pixel/service/ShareCard 无 SSRF;
`exec cwebp` 不接用户输入;SMTP 默认校验证书;`RegenerateID` + `pwd_ver`;TLS1.0/1.1 拒绝;
无 CORS 头;测试站蜜罐已开(`/.git/HEAD`、`/.env` 返回假后台)。
### 测试卫生
- 测试站账号 `tbunread1022181904`,脚本已把严格度恢复 strict、清空 page/ask 密码、
删除本轮 15 条测试提问(115–129)。下次复用前先确认这三项是干净的。
- 隔离 venv 里装了 `requests`(`~/.workbuddy/binaries/python/envs/default`),
比 urllib 顺手;`/api/captcha/generate` 限流 20/min,批量提交脚本必须带 429 重试。
## 安全评估(续):trust_proxy=true 引出的 P1-3
用户确认两套环境 `trust_proxy = true`。据此重做了 IP 采信那一块,发现比原来的推断严重:
### P1-3 双 CDN 下权威头优先次序反了
- `internal/shared/ip.go` 的 `ResolveClientIP` **无条件**先读 `CF-Connecting-IP`,再读 `EO-Client-IP`。
- 前提「权威头只能由 CDN 写入」只对 Cloudflare 成立:
- CF:强制覆盖 `CF-Connecting-IP`,客户端伪造直接 **403**(生产/测试站实测一致)。
- **EdgeOne:不剥离客户端传入的 `CF-Connecting-IP`**,`share.shiroko.one` 实测带该头返回 200
(响应头 `Server: edgeone makers` / `EO-LOG-UUID`)。
- 代码优先读 CF 那个头 → 走 EO 线路时客户端 IP 完全可控。后果:限流/IP 黑名单全失效,
且 `autoban.Record` 可被用来**栽赃任意 IP**(填别人的 IP 连打 10 次敏感路径 → 对方被封)。
- **今天打不通**:`box.shiroko.one` 境内递归(AliDNS DoH)只解析到 `cloudflare.182682.xyz` → 104.18.x/172.64.x,
文档里写的境内 `eo.dnse4.com` 线路已不在;`box.tama.guru` 301 跳新域名也走 CF。差一条 DNS 记录。
- 修法:权威头也套 `isTrustedProxy(remote)`(回环可用 cloudflared 本机场景),fail-closed;
EO 侧删 `CF-Connecting-IP`;日志同时记 `remote_addr` 与解析后 IP。
### 源站定位(外网已尽力,未找到)
- `box.shiroko.one` CNAME `cloudflare.182682.xyz`(第三方 CF SaaS 优选,zone 不在我们手里,
其 Transform/WAF 规则无法自查)。
- certspotter 枚举 `*.shiroko.one` 11 个、`*.337845818.best` 19 个子域,逐个解析全部落
Cloudflare / 腾讯 EdgeOne / Vercel(216.198.79.x、64.29.17.x),**无一条直连记录**。
- `status.shiroko.one`、`share.shiroko.one` → 43.174.246.43/247.43,用 box 的 SNI 访问回 418 Teapot
→ 是 EO 边缘不是源站。
- 结论:源站隐藏目前成功,但「找不到」≠「不可达」,仍需服务器侧 `security-selfcheck.sh local` 确认
`listen = 127.0.0.1:5877`。
### 顺带修正
上一版报告曾把「三种伪造头都不生效」推断为 `trust_proxy` 可能为 false —— 那是错的解读:
无论 true/false,限流键都落在我自己的真实 IP 上,外部不可分辨。已在报告第四节改正。