P0(高危): - 新增全局 CSRF 中间件(同步器令牌),覆盖全部 30 个表单与 AJAX 请求 - 修复附件上传/列表/删除越权(IDOR),增加 admin/上传者/文章作者所有权校验 - 登录/注册成功后会话轮换,修复会话固定 - 会话密钥改用 crypto/rand 生成,配置缺失 secret 时拒绝启动 P1(中危): - session 与 comment_uid cookie 增加 Secure/SameSite 标志 - 新增安全响应头:CSP、X-Content-Type-Options、X-Frame-Options、HSTS 等 - 新增 web.trusted_proxies 配置,修复 X-Forwarded-For 伪造 - 修复浏览量记录 goroutine 访问已回收 gin.Context 的数据竞争 补充 17 个安全回归测试(middleware/handlers),go test -race 全绿
60 lines
1.9 KiB
Go
60 lines
1.9 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// newClientIPRouter mirrors the production trusted-proxy configuration:
|
|
// only loopback is trusted (the Caddy/nginx host).
|
|
func newClientIPRouter() *gin.Engine {
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
if err := r.SetTrustedProxies([]string{"127.0.0.1", "::1"}); err != nil {
|
|
panic(err)
|
|
}
|
|
r.GET("/ip", func(c *gin.Context) {
|
|
c.String(http.StatusOK, c.ClientIP())
|
|
})
|
|
return r
|
|
}
|
|
|
|
func TestClientIPSpoofingBlocked(t *testing.T) {
|
|
r := newClientIPRouter()
|
|
|
|
// A direct (untrusted) client sending a forged X-Forwarded-For must not
|
|
// be able to change the recorded IP.
|
|
req := httptest.NewRequest(http.MethodGet, "/ip", nil)
|
|
req.RemoteAddr = "203.0.113.5:12345"
|
|
req.Header.Set("X-Forwarded-For", "6.6.6.6")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if got := w.Body.String(); got != "203.0.113.5" {
|
|
t.Errorf("direct client with forged XFF: got %q, want 203.0.113.5", got)
|
|
}
|
|
|
|
// A trusted proxy (loopback) forwarding a real chain: the rightmost
|
|
// untrusted entry wins, earlier (client-supplied) entries are ignored.
|
|
req = httptest.NewRequest(http.MethodGet, "/ip", nil)
|
|
req.RemoteAddr = "127.0.0.1:54321"
|
|
req.Header.Set("X-Forwarded-For", "6.6.6.6, 198.51.100.42")
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if got := w.Body.String(); got != "198.51.100.42" {
|
|
t.Errorf("proxy-forwarded chain: got %q, want 198.51.100.42 (client-supplied entry must be ignored)", got)
|
|
}
|
|
|
|
// A trusted proxy forwarding a single entry: that entry is the client.
|
|
req = httptest.NewRequest(http.MethodGet, "/ip", nil)
|
|
req.RemoteAddr = "127.0.0.1:54321"
|
|
req.Header.Set("X-Forwarded-For", "198.51.100.42")
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if got := w.Body.String(); got != "198.51.100.42" {
|
|
t.Errorf("proxy-forwarded single entry: got %q, want 198.51.100.42", got)
|
|
}
|
|
}
|