Golang中JSON XSS反序列化未按预期工作的问题如何解决
Golang中JSON XSS反序列化未按预期工作的问题如何解决 我有一个包含XSS注入的结构体。为了移除它,我先对其进行json.Marshal,然后运行json.HTMLEscape,接着再通过json.Unmarshal将其解析到新的结构体中。
问题是新结构体仍然存在XSS注入。
我实在想不出如何从结构体中移除XSS。虽然可以编写函数来处理字段,但考虑到有json.HTMLEscape并且我们可以将其解组回来,我期望它能正常工作,但事实并非如此。
type Person struct {
Name string `json:"name"`
}
func main() {
var p, p2 Person
// p.Name 包含XSS
p.Name = "<script>alert(1)</script>"
var tBytes bytes.Buffer
// 先进行序列化以便使用json.HTMLEscape
marshalledJson, _ := json.Marshal(p)
json.HTMLEscape(&tBytes, marshalledJson)
// 这里将其插入新结构体,遗憾的是p2结构体仍然包含XSS
err := json.Unmarshal(tBytes.Bytes(), &p2)
if err != nil {
fmt.Printf(err.Error())
}
fmt.Print(p2)
}
期望的结果是p2.Name被清理成<script>alert(1)</script>
更多关于Golang中JSON XSS反序列化未按预期工作的问题如何解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我在Go Playground中运行你的代码时得到了这样的结果…(<script>alert(1)</script>)
更多关于Golang中JSON XSS反序列化未按预期工作的问题如何解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
应该是<script>alert(1)</script>,因为上面执行了json.HTMLEscape
如果运行fmt.Print(tBytes.String()),它的XSS会被清理,然而p2却没有…
这让我感到非常奇怪
func main() {
fmt.Println("hello world")
}
不,不应该是 <script>alert(1)</script>;正如 json.Marshal 文档中所述,应该是 \u003cscript\u003ealert(1)\u003c/script\u003e(引用如下):
func Marshal
func Marshal(v interface{}) ([]byte, error)
Marshal 返回 v 的 JSON 编码。
Marshal 递归地遍历值 v。如果遇到的值实现了 Marshaler 接口且不是空指针,Marshal 会调用其 MarshalJSON 方法来生成 JSON。如果没有 MarshalJSON 方法但值实现了 encoding.TextMarshaler,Marshal 会调用其 MarshalText 方法并将结果编码为 JSON 字符串。空指针异常并非严格必要,但模拟了 UnmarshalJSON 行为中类似的必要异常。
否则,Marshal 使用以下依赖于类型的默认编码:
布尔值编码为 JSON 布尔值。
浮点数、整数和 Number 值编码为 JSON 数字。
字符串值编码为 JSON 字符串,强制转换为有效的 UTF-8,用 Unicode 替换符替换无效字节。尖括号 “<” 和 “>” 被转义为 “\u003c” 和 “\u003e”,以防止某些浏览器将 JSON 输出误解为 HTML。出于相同原因,与符号 “&” 也被转义为 “\u0026”。可以通过调用 SetEscapeHTML(false) 的编码器来禁用此转义。
问题在于你误解了 json.HTMLEscape 和 JSON 序列化/反序列化的工作方式。json.HTMLEscape 只会在 JSON 字符串值中对 HTML 特殊字符进行转义,但在反序列化时,json.Unmarshal 会自动将这些转义字符还原为原始字符。
如果你需要在结构体字段中存储转义后的 HTML 内容,你需要手动处理或者使用专门的 HTML 清理库。以下是解决方案:
方案1:手动处理转义和反转义
package main
import (
"bytes"
"encoding/json"
"fmt"
"html"
)
type Person struct {
Name string `json:"name"`
}
func main() {
var p, p2 Person
p.Name = "<script>alert(1)</script>"
// 手动对 HTML 进行转义
escapedName := html.EscapeString(p.Name)
// 创建新的 Person 结构体
p2.Name = escapedName
fmt.Printf("原始: %s\n", p.Name)
fmt.Printf("转义后: %s\n", p2.Name)
// 如果需要从转义字符串还原
unescaped := html.UnescapeString(p2.Name)
fmt.Printf("还原后: %s\n", unescaped)
}
方案2:使用自定义 JSON 序列化
package main
import (
"encoding/json"
"fmt"
"html"
)
type SafeString string
func (s *SafeString) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
// 在反序列化时自动转义 HTML
*s = SafeString(html.EscapeString(str))
return nil
}
type Person struct {
Name SafeString `json:"name"`
}
func main() {
var p Person
jsonData := `{"name":"<script>alert(1)</script>"}`
err := json.Unmarshal([]byte(jsonData), &p)
if err != nil {
fmt.Printf("错误: %s\n", err.Error())
return
}
fmt.Printf("转义后的名称: %s\n", p.Name)
}
方案3:使用专门的 HTML 清理库
package main
import (
"encoding/json"
"fmt"
"github.com/microcosm-cc/bluemonday"
)
type Person struct {
Name string `json:"name"`
}
func sanitizeHTML(input string) string {
p := bluemonday.UGCPolicy()
return p.Sanitize(input)
}
func main() {
var p Person
p.Name = "<script>alert(1)</script>正常文本<a href='http://example.com'>链接</a>"
// 使用 bluemonday 清理 HTML
sanitized := sanitizeHTML(p.Name)
fmt.Printf("原始: %s\n", p.Name)
fmt.Printf("清理后: %s\n", sanitized)
}
关键点说明
json.HTMLEscape只在输出 JSON 时有效,用于防止 XSS 攻击,但不会修改原始数据- 反序列化时会自动还原转义字符
- 如果需要在数据层面处理 HTML 转义,应该使用
html.EscapeString和html.UnescapeString - 对于生产环境,建议使用专门的 HTML 清理库如
bluemonday
第一个方案是最直接的解决方案,根据你的具体需求选择合适的处理方式。

