被封禁来源访问任何路径时,原来只回 13 字节的 "403 Forbidden":既没有信息量, 人肉访问(尤其是共享出口被连累的误封)时看着像站点挂了;扫描/爆破脚本也常靠 读这行明文确认「自己已被拦」。 改为返回一张 256×256 的 webp(约 8.6 KB,画面写着 403 Forbidden),状态码不变: - 图片用 //go:embed 编进二进制,不依赖外部 URL、不占公开路径; - 显式给 Content-Length(8 KB 超过 Go 的 2 KB 写缓冲,不设会退回 chunked); - Cache-Control: no-store —— 中间层缓存 403 没有意义,还会让解封后的响应继续命中; - HEAD 只回头不写 body; - 放在 internal/route/assets/ 而不是 static/:static 下的东西有可公开访问的 URL, 等于多一个指纹入口。 注意链接:给的 R2 预签名地址 X-Amz-Expires=604800(7 天),热链到期就会挂, 所以是把字节取回来打包,而不是重定向或代理。 测试: - TestWriteBanned:状态码 403、image/webp、Content-Length 与内嵌字节数一致、 body 前 12 字节是 RIFF/WEBP(防止被换成 HTML 或明文这类回归); - TestWriteBannedHead:HEAD 无 body 但有 Content-Length; - 原 TestHoneypotBansEvenWhenAutoBanDisabled 里断言 "403 Forbidden" 明文的用例 改为断言 image/webp 与体积。 变异验证:把 writeBanned 改回写明文 + text/plain,两条用例均按预期变红。
21 lines
992 B
Go
21 lines
992 B
Go
// Package assets 存放「必须由服务端直接吐出」的二进制资源。
|
||
//
|
||
// 为什么不放 static/:static 下的东西由静态文件中间件按 URL 提供,
|
||
// 而这里的图片只该出现在 403 响应体里 —— 不该有一个能直接访问的公开路径
|
||
// (否则被 ban 的人可以先抓一张「解禁图」,也等于多一个指纹入口)。
|
||
package assets
|
||
|
||
import _ "embed"
|
||
|
||
// BannedImage 是被封禁来源访问任何路径时返回的图(256×256 webp,约 8.6 KB)。
|
||
//
|
||
// 换成图片的动机:被封之后每次请求都只拿到 13 字节的 "403 Forbidden",
|
||
// 既没有信息量,也让人误以为是站点挂了。这张图明确写着 403 Forbidden,
|
||
// 人一看就懂,而状态码仍然是 403 —— 语义没变,只换了 body。
|
||
//
|
||
// 想替换:直接覆盖同目录的 403.webp 后重新编译(go:embed 是编译期打包的,
|
||
// 不需要重启之外的操作)。
|
||
//
|
||
//go:embed 403.webp
|
||
var BannedImage []byte
|