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被清理成&lt;script&gt;alert(1)&lt;/script&gt;


更多关于Golang中JSON XSS反序列化未按预期工作的问题如何解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

我在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)
}

关键点说明

  1. json.HTMLEscape 只在输出 JSON 时有效,用于防止 XSS 攻击,但不会修改原始数据
  2. 反序列化时会自动还原转义字符
  3. 如果需要在数据层面处理 HTML 转义,应该使用 html.EscapeStringhtml.UnescapeString
  4. 对于生产环境,建议使用专门的 HTML 清理库如 bluemonday

第一个方案是最直接的解决方案,根据你的具体需求选择合适的处理方式。

回到顶部