用 Go 優雅地清理 HTML 并抵御 XSS——Bluemonday

1、背景與動機

只要你的服務接收并回顯用戶生成內容(UGC)——論壇帖子、評論、富文本郵件正文、Markdown 等——就必須考慮 XSS(Cross?Site Scripting)攻擊風險。瀏覽器在解析 HTML 時會執行腳本;如果不做清理,惡意用戶可插入 <script>onerror 等內容竊取 Cookie 或劫持會話。

Bluemonday 的使命就是 “先 allowlist,后輸出”:只留下安全允許的元素與屬性,其余一律剝離,保證最終字符串對瀏覽器“無害”。

2、XSS 攻擊原理與防線簡述

攻擊類型觸發機理常見載體
反射型惡意片段拼進 URL,服務器原樣輸出搜索框、重定向鏈接
存儲型惡意腳本寫入數據庫,其他用戶瀏覽時觸發論壇帖子、評論
DOM 型前端 JS 對外部數據拼接 innerHTMLJSONP、模板拼接

綜合防御思路

  1. 輸入校驗:長度、格式、白名單字符集。
  2. 輸出編碼/清理:HTMLEscape、CSP、Bluemonday 等。
  3. 最小權限:Cookie HttpOnly/SameSite、前后端分離降低風險。

3、認識 Bluemonday

3.1 主要特性

  • 純 Go 實現;零 CGo 依賴,可用在任意平臺。
  • 基于允許列表(Allowlist);默認拒絕一切未知元素,安全系數高。
  • 多種預置策略StrictPolicyUGCPolicyNewPolicy() 自定義。
  • 線程安全:Policy 對象是只讀,可被多個 goroutine 并發復用。
  • 支持 string/[]byte/io.Reader 三種輸入形式,便于流式處理。citeturn0search1

3.2 安裝與版本管理

go get github.com/microcosm-cc/bluemonday@latest

Bluemonday 發布節奏相對穩定,建議在 go.mod 中鎖定次版本號(v1.x.y)以避免潛在破壞性變更。

4、快速上手:StrictPolicy 一鍵剝離標簽

最“硬核”的策略就是全部移除標簽,僅保留文本

policy := bluemonday.StrictPolicy()
clean  := policy.Sanitize(`<p>Hello <strong>世界</strong>!<script>alert(1)</script></p>`)
// clean == "Hello 世界!"

該策略實現了 “硬刪除腳本 + 去掉所有屬性” 的極致安全;在 Markdown?>HTML 渲染前做一次清理,可有效殺死腳本注入。citeturn0search0

5 、進階使用:UGCPolicy 與自定義策略

5.1 UGCPolicy——保留安全富文本

StrictPolicy 雖安全,卻也太“干凈”。當你需要 保留 <a><em><ul> 等常用排版元素 時,可以使用官方預設的 UGCPolicy()

p   := bluemonday.UGCPolicy()
out := p.Sanitize(`<a href="http://evil.com" onclick="steal()">點我</a>`)
fmt.Println(out)
// <a href="http://evil.com" rel="nofollow">點我</a>

重點:

  • 允許 <a>、自動加 rel="nofollow"
  • 過濾一切 JS?相關屬性onclick, onerror 等)。
  • 自動修正不完整/畸形標簽,防跳脫閉合漏洞。citeturn0search2

5.2 手寫自定義 Policy

如果業務場景需要 保留特定 CSS class、data-* 自定義屬性,可通過 NewPolicy() 自定義:

p := bluemonday.NewPolicy()// 允許 block/inline 常用元素
p.AllowElements("p", "ul", "li", "strong", "em")// 允許 <a>,但僅開放 href=https://* 并加 rel="noopener"
p.AllowAttrs("href").Matching(bluemonday.UrlRegexp).OnElements("a")
p.RequireNoFollowOnLinks(false)
p.AddTargetBlankToFullyQualifiedLinks(true)// 允許 <span class="highlight">
p.AllowAttrs("class").Matching(regexp.MustCompile(`^(highlight)$`)).OnElements("span")// …更多顆粒度設置

Bluemonday 的 API 鏈式可讀性高;官方文檔中每個 Allow* 方法都帶示例,可參考并組合。

6、性能、并發與內存開銷

  • 并發安全:Policy 初始化后為只讀結構體,可在全局變量緩存并被 goroutine 復用。
  • 基于標準庫 html Tokenizer:解析成本≈ O(n),常規博文(~5 KB)清理耗時 < 100 μs。
  • 零分配策略SanitizeBytes 復用切片,避免多余 copy。

性能調優要點:

  1. 單例化 Policy,避免每次請求都 NewPolicy()
  2. 對高并發流量可用 sync.Pool 復用臨時緩沖區,減輕 GC 壓力。

7、與 Gin 集成的落地示例

// middleware/htmlsan.go
var htmlPolicy = bluemonday.UGCPolicy()func SanitizeHTML() gin.HandlerFunc {return func(c *gin.Context) {if c.Request.Method == http.MethodPost || c.Request.Method == http.MethodPut {// 讀 bodyraw, _ := io.ReadAll(c.Request.Body)clean := htmlPolicy.SanitizeBytes(raw)// 重寫 body 并繼續鏈路c.Request.Body = io.NopCloser(bytes.NewReader(clean))c.Request.ContentLength = int64(len(clean))}c.Next()}
}

注冊:

r := gin.Default()
r.Use(middleware.SanitizeHTML())
  • 適用于 富文本編輯器上傳評論接口 等寫操作。
  • 若上傳為 JSON,可在綁定結構體前使用 json.Decoder + htmlPolicy.Sanitize() 逐字段過濾。

8、單元測試與安全基線

func TestStripXSS(t *testing.T) {cases := []struct{in, out string}{{`<img src=x onerror=alert(1)>`, ``},{`<a href="javascript:alert(1)">x</a>`, `<a rel="nofollow">x</a>`},}p := bluemonday.UGCPolicy()for _, c := range cases {if got := p.Sanitize(c.in); got != c.out {t.Errorf("want %q, got %q", c.out, got)}}
}
  • 建議將常見 XSS Payload 集合(OWASP Cheat Sheet)納入回歸測試。
  • 關注安全通報:例如 Go?2022?0588 提及自定義策略允許 style 時可能觸發 CSS XSS,應盡量避免。

9、常見陷阱 & 優化建議

場景潛在風險建議
盲目 AllowElements("style")CSS 注入繞過使用 CSP 并限制 style
文件上傳富文本嵌入 <img src="data:…">內嵌惡意 SVG限制 src 協議為 https
前端再拼接 innerHTML += …DOM XSS前端使用 textContent 或框架安全輸出
大文件批量清理內存飆升使用 SanitizeReader 流式處理

10、 結語

Bluemonday 把復雜的 XSS 防御落地成本降到了“引一個包 + 三行代碼”
然而安全永遠不是“一招鮮”:

  • 輸入校驗、輸出清理、瀏覽器 CSP 缺一不可。
  • 單元測試 & 安全掃描 持續跟進新 Payload。
  • 合理選擇 StrictPolicy / UGCPolicy / 自定義混合策略,平衡 用戶體驗安全強度

把好內容入口關,讓你的 Go Web 服務擁有“消毒后”的純凈輸入!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/78007.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/78007.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/78007.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Redis SCAN 命令的詳細介紹

Redis SCAN 命令的詳細介紹 以下是 Redis SCAN? 命令的詳細介紹&#xff0c;結合其核心特性、使用場景及底層原理進行綜合說明&#xff1a; 工作原理圖 &#xff1a; ? 一、核心特性 非阻塞式迭代 通過游標&#xff08;Cursor&#xff09; 分批次遍歷鍵&#xff0c;避免一次…

SpringBoot3集成MyBatis-Plus(解決Boot2升級Boot3)

總結&#xff1a;目前升級僅發現依賴有變更&#xff0c;其他目前未發現&#xff0c;如有發現&#xff0c;后續會繼續更新 由于項目架構提升&#xff0c;以前開發的很多公共的組件&#xff0c;以及配置都需要升級&#xff0c;因此記錄需要更改的配置&#xff08;記錄時間&#…

基于mybatis與PageHelper插件實現條件分頁查詢(3.19)

實現商品分頁例子 需要先引入mybatis與pagehelper插件&#xff0c;在pom.xml里 <!-- Mybatis --> <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3&l…

Spring Bean 全方位指南:從作用域、生命周期到自動配置詳解

目錄 1. Bean 的作用域 1.1 singleton 1.2 prototype 1.3 request 1.4 session 1.5 application 1.5.1 servletContext 和 applicationContext 區別 2. Bean 的生命周期 2.1 詳解初始化 2.1.1 Aware 接口回調 2.1.2 執行初始化方法 2.2 代碼示例 2.3 源碼 [面試題…

C++ (非類型參數)

模板除了定義類型參數之外&#xff0c;也可以在模板內定義非類型參數 非類型參數不是類型&#xff0c;而是值&#xff0c;比如&#xff1a;指針&#xff0c;整數&#xff0c;引用 非類型參數的用法&#xff1a; 1.整數常量&#xff1a;非類型參數最常見的形式是整數常量&…

短視頻+直播商城系統源碼全解析:音視頻流、商品組件邏輯剖析

時下&#xff0c;無論是依托私域流量運營的品牌方&#xff0c;還是追求用戶粘性與轉化率的內容創作者&#xff0c;搭建一套完整的短視頻直播商城系統源碼&#xff0c;已成為提升用戶體驗、增加商業變現能力的關鍵。本文將圍繞三大核心模塊——音視頻流技術架構、商品組件設計、…

5.QT-常用控件-QWidget|enabled|geometry|window frame(C++)

控件概述 實現圖形化界面的程序. Qt中已經給我們提供了很多的“控件" 就需要學習和了解這些控件&#xff0c;學會如何使用這些控件 編程講究的是“站在巨人的肩膀上”&#xff0c;而不是“從頭發明輪子" 一個圖形化界面上的內容&#xff0c;不需要咱們全都從零去實…

2025-04-22| Docker: --privileged參數詳解

在 Docker 中&#xff0c;--privileged 是一個運行容器時的標志&#xff0c;它賦予容器特權模式&#xff0c;大幅提升容器對宿主機資源的訪問權限。以下是 --privileged 的作用和相關細節&#xff1a; 作用 完全訪問宿主機的設備&#xff1a; 容器可以訪問宿主機的所有設備&am…

高性能服務器配置經驗指南1——剛配置好服務器應該做哪些事

文章目錄 安裝ubuntu安裝必要軟件設置用戶遠程連接安全問題ClamAV安裝教程步驟 1&#xff1a;更新系統軟件源步驟 2&#xff1a;升級系統&#xff08;可選但推薦&#xff09;步驟 3&#xff1a;安裝 ClamAV步驟 4&#xff1a;更新病毒庫步驟 5&#xff1a;驗證安裝ClamAV 常用命…

直流絕緣監測解決方案:保障工業與新能源系統的安全運行

一、引言 隨著工業自動化和新能源技術的快速發展&#xff0c;直流供電系統在光伏發電、儲能電站、電動汽車充電樁等領域的應用日益廣泛。然而&#xff0c;直流系統的正負極不接地&#xff08;IT系統&#xff09;特性&#xff0c;使得絕緣故障可能導致漏電、短路甚至設備損毀等…

VSCode 用于JAVA開發的環境配置,JDK為1.8版本時的配置

插件安裝 JAVA開發在VSCode中&#xff0c;需要安裝JAVA的必要開發。當前安裝只需要安裝 “Language Support for Java(TM) by Red Hat”插件即可 安裝此插件后&#xff0c;會自動安裝包含如下插件&#xff0c;不再需要單獨安裝 Project Manager for Java Test Runner for J…

C++入門語法

C入門 首先第一點&#xff0c;C中可以混用C語言中的語法。但是C語言是不兼容C的。C主要是為了改進C語言而創建的一門語言&#xff0c;就是有人用C語言用不爽了&#xff0c;改出來個C。 命名空間 c語言中會有如下這樣的問題&#xff1a; 那么C為了解決這個問題就整出了一個命名…

輸入框僅支持英文、特殊符號、全角自動轉半角 vue3

需求&#xff1a;封裝一個輸入框組件 1.只能輸入英文。 2.輸入的小寫英文自動轉大寫。 3.輸入的全角特殊符號自動轉半角特殊字符 效果圖 代碼 <script setup> import { defineEmits, defineModel, defineProps } from "vue"; import { debounce } from "…

Uniapp:創建項目

目錄 一、前提準備二、創建項目三、項目結構四、運行測試 一、前提準備 首先要創建uniapp項目&#xff0c;需要先下載HBuilderX&#xff0c;HBuilderX是一款開箱即用的工具&#xff0c;下載完畢之后&#xff0c;解壓到指定的目錄即可使用&#xff0c;需要注意的是最好路徑里面…

ESM 內功心法:化解 require 中的奪命一擊!

前言 傳聞在JavaScript與TypeScript武林中,曾有兩大絕世心法:CommonJS與ESM。兩派高手比肩而立,各自稱霸一方,江湖一度風平浪靜。 豈料,時局突變。ESM逐步修成陽春白雪之姿,登堂入室,成為主流正統。CommonJS則漸入下風,功力不濟,逐漸退出主舞臺。 話說某日,一位前…

【STL】unordered_set

在 C C C 11 11 11 中&#xff0c; S T L STL STL 標準庫引入了一個新的標準關聯式容器&#xff1a; u n o r d e r e d _ s e t unordered\_set unordered_set&#xff08;無序集合&#xff09;。功能和 s e t set set 類似&#xff0c;都用于存儲唯一元素。但是其底層數據結…

go語言八股文

1.go語言的接口是怎么實現 接口&#xff08;interface&#xff09;是一種類型&#xff0c;它定義了一組方法的集合。任何類型只要實現了接口中定義的所有方法&#xff0c;就被認為實現了該接口。 代碼的實現 package mainimport "fmt"// 定義接口 type Shape inte…

kafka auto.offset.reset詳解

在 Kafka 中&#xff0c;auto.offset.reset latest 的含義及行為如下&#xff1a; 1. ??核心定義?? 當消費者組??首次啟動??或??無法找到有效的 offset??&#xff08;例如 offset 過期、被刪除或從未提交&#xff09;時&#xff0c;消費者會從分區的??最新位置…

深度學習-損失函數

目錄 1. 線性回歸損失函數 1.1 MAE損失 1.2 MSE損失 2. CrossEntropyLoss 2.1 信息量 2.2 信息熵 2.3 KL散度 2.4 交叉熵 3. BCELoss 4. 總結 1. 線性回歸損失函數 1.1 MAE損失 MAE&#xff08;Mean Absolute Error&#xff0c;平均絕對誤差&#xff09;通常也被稱…

第六篇:linux之解壓縮、軟件管理

第六篇&#xff1a;linux之解壓縮、軟件管理 文章目錄 第六篇&#xff1a;linux之解壓縮、軟件管理一、解壓和壓縮1、window壓縮包與linux壓縮包能否互通&#xff1f;2、linux下壓縮包的類型3、打包與壓縮 二、軟件管理1、rpm1、什么是rpm&#xff1f;2、rpm包名組成部分3、如何…