SecureJSON

JSON 劫持是一种安全风险,攻击者可以通过 script 标签获取敏感数据。Gin 提供了 SecureJSON 来防护。

什么是 JSON 劫持

攻击者可以在恶意网站中这样获取数据:

<script>
function steal(data) {
    // 发送数据到攻击者服务器
}
</script>
<script src="https://victim.com/api/user.json"></script>

如果返回的是数组 [{"name":"admin"}],某些旧浏览器会把它当作合法的 JS 执行。

SecureJSON 防护

Gin 的 SecureJSON 会在响应前添加 while(1);

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    
    r.GET("/secure", func(c *gin.Context) {
        c.SecureJSON(http.StatusOK, []gin.H{
            {"name": "张三", "email": "zhangsan@example.com"},
            {"name": "李四", "email": "lisi@example.com"},
        })
    })
    
    r.Run(":8080")
}

响应内容:

while(1);[{"email":"zhangsan@example.com","name":"张三"},{"email":"lisi@example.com","name":"李四"}]

攻击者通过 script 标签加载时,while(1); 会造成死循环,阻止数据被窃取。

前端处理

前端需要去掉前缀再解析:

fetch('/secure')
    .then(response => response.text())
    .then(text => {
        const json = text.replace(/^while\(1\);/, '');
        return JSON.parse(json);
    })
    .then(data => console.log(data));

自定义前缀

可以自定义防护前缀:

func main() {
    r := gin.New()
    
    r.GET("/custom", func(c *gin.Context) {
        c.SecureJSON(http.StatusOK, gin.H{
            "message": "hello",
        })
    })
    
    r.Run(":8080")
}

何时使用

SecureJSON 主要用于:

  1. 返回敏感数据的 API
  2. 返回数组类型的 JSON
  3. 需要防止 JSON 劫持的场景

对于普通 API,使用 c.JSON() 即可:

r.GET("/normal", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "message": "普通响应",
    })
})

其他安全响应方法

AsciiJSON

确保非 ASCII 字符被转义:

r.GET("/ascii", func(c *gin.Context) {
    c.AsciiJSON(http.StatusOK, gin.H{
        "name": "张三",
        "city": "北京",
    })
})

响应:

{"name":"\u5f20\u4e09","city":"\u5317\u4eac"}

PureJSON

不转义 HTML 特殊字符:

r.GET("/pure", func(c *gin.Context) {
    c.PureJSON(http.StatusOK, gin.H{
        "html": "<b>bold</b>",
        "script": "<script>alert(1)</script>",
    })
})

JSONP

支持跨域 JSONP 请求:

r.GET("/jsonp", func(c *gin.Context) {
    c.JSONP(http.StatusOK, gin.H{
        "message": "jsonp response",
    })
})

访问 /jsonp?callback=myCallback,返回:

myCallback({"message":"jsonp response"});

安全最佳实践

func main() {
    r := gin.Default()
    
    r.Use(func(c *gin.Context) {
        c.Header("X-Content-Type-Options", "nosniff")
        c.Header("X-Frame-Options", "DENY")
        c.Header("X-XSS-Protection", "1; mode=block")
        c.Next()
    })
    
    r.GET("/api/users", func(c *gin.Context) {
        users := []gin.H{
            {"id": 1, "name": "张三"},
            {"id": 2, "name": "李四"},
        }
        c.SecureJSON(http.StatusOK, users)
    })
    
    r.GET("/api/profile", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "name":  "张三",
            "email": "zhangsan@example.com",
        })
    })
    
    r.Run(":8080")
}

小结

SecureJSON 是防止 JSON 劫持的简单有效方法。对于返回敏感数据或数组类型的 API,建议使用 SecureJSON。配合其他安全响应头,可以更好地保护 API 安全。