From b2ddd9e7e386d374ddde358101c46a32f201e44e Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Tue, 28 Jul 2026 09:19:43 +0800 Subject: [PATCH] =?UTF-8?q?security:=20waf=20=E8=BE=B9=E7=BC=98=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E6=B3=A8=E5=85=A5=E5=AE=89=E5=85=A8=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E5=A4=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 外部扫描发现站点缺少安全响应头。在 waf 反代/403/502 三个出站路径统一注入 XFO/nosniff/Referrer-Policy/Permissions-Policy/HSTS/CSP,下游同名头一律覆盖避免重复;static 补上漏调用的 apply_security_headers 做纵深防御。 --- static/fsys.py | 2 ++ waf/internal/proxy/proxy.go | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/static/fsys.py b/static/fsys.py index 9c5bdc7..934890d 100644 --- a/static/fsys.py +++ b/static/fsys.py @@ -13,6 +13,7 @@ import sys # pymsg 日志客户端(集中式日志 → dbv2 Logs 表) sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from log_client import logs # noqa: E402 +from security_middleware import apply_security_headers # noqa: E402 # 基准目录:编译后取可执行文件所在目录,开发时取脚本所在目录 # 解决 Nuitka onefile 解压到 /tmp 后相对路径失效的问题 @@ -38,6 +39,7 @@ args = parser.parse_args() app = Flask(__name__) CORS(app, origins=["https://qsont.me", "https://yhdet.top", "http://localhost:9200"]) +apply_security_headers(app) # ==================== 配置文件加载 ==================== CONFIG_FILE = args.config diff --git a/waf/internal/proxy/proxy.go b/waf/internal/proxy/proxy.go index 861a9a7..d5abe66 100644 --- a/waf/internal/proxy/proxy.go +++ b/waf/internal/proxy/proxy.go @@ -48,11 +48,33 @@ func New(upstream string, waf *engine.Engine) (*Proxy, error) { rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { logclient.Error("waf", fmt.Sprintf("upstream_error path=%s err=%v", r.URL.Path, err), map[string]any{"ip": r.RemoteAddr}) + setSecurityHeaders(w.Header()) http.Error(w, "upstream unavailable", http.StatusBadGateway) } + // 统一注入安全响应头:在边缘单点覆盖所有出站响应。 + // Header.Set 覆盖下游同名头,不会产生重复字段 + rp.ModifyResponse = func(res *http.Response) error { + setSecurityHeaders(res.Header) + return nil + } return p, nil } +// setSecurityHeaders 设置安全响应头基线(与 static/security_middleware.py 保持一致)。 +// CSP 暂保留 unsafe-inline:forgot.html 与 games/*.html 含内联脚本。 +// HSTS 半年期、不含子域,站点全量 HTTPS 后可按需收紧。 +func setSecurityHeaders(h http.Header) { + h.Set("X-Frame-Options", "DENY") + h.Set("X-Content-Type-Options", "nosniff") + h.Set("X-XSS-Protection", "1; mode=block") + h.Set("Referrer-Policy", "strict-origin-when-cross-origin") + h.Set("Permissions-Policy", "camera=(), microphone=(), geolocation=()") + h.Set("Strict-Transport-Security", "max-age=15552000") + h.Set("Content-Security-Policy", + "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; "+ + "img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'") +} + // ServeHTTP 实现 http.Handler。 // 流程:ProcessRequest → 命中则 403 → 否则转发 → ProcessResponseHeaders。 func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -126,6 +148,7 @@ func (p *Proxy) block(w http.ResponseWriter, r *http.Request, ruleID int, start "ua": r.UserAgent(), "cost_ms": time.Since(start).Milliseconds(), }) + setSecurityHeaders(w.Header()) w.WriteHeader(http.StatusForbidden) _, _ = w.Write([]byte("blocked by WAF\n")) }