一、XSS攻击的前世今生

想象一下,你正在浏览一个论坛,突然发现评论区里有人发了一段奇怪的代码,点开后你的账号自动转发了广告——这就是典型的XSS(跨站脚本攻击)。攻击者通过注入恶意脚本,让其他用户的浏览器执行这些代码,轻则弹广告,重则盗取Cookie甚至控制账户。

XSS主要分三种类型:

  1. 存储型:恶意脚本被保存到服务器(比如评论区)
  2. 反射型:通过URL参数即时返回攻击代码
  3. DOM型:完全在浏览器端完成攻击

举个存储型XSS的例子(假设用Node.js+Express技术栈):

// 危险!未过滤的评论存储
app.post('/comment', (req, res) => {
  const { content } = req.body;
  // 直接存入数据库(这就是漏洞所在!)
  db.run('INSERT INTO comments VALUES (?)', [content]); 
});

// 渲染评论时直接输出HTML
app.get('/comments', (req, res) => {
  db.all('SELECT content FROM comments', (err, rows) => {
    res.send(`<div>${rows.map(row => row.content).join('')}</div>`);
  });
});

如果用户提交<script>alert('hacked')</script>,这段代码就会在所有访问者的浏览器执行。

二、防御的三大护法

1. 输入消毒(Input Sanitization)

就像给食物消毒一样,我们要过滤掉危险字符。以Java Spring为例:

// 使用Spring的HtmlUtils
import org.springframework.web.util.HtmlUtils;

@PostMapping("/comment")
public String addComment(@RequestParam String content) {
    // 转义HTML特殊字符
    String sanitized = HtmlUtils.htmlEscape(content);
    commentRepository.save(sanitized);
    return "redirect:/comments";
}

这样<script>会被转义成&lt;script&gt;,浏览器就不会执行它。

2. 输出编码(Output Encoding)

即使数据入库时没过滤,展示时还有补救机会。以PHP为例:

// 使用htmlspecialchars函数
$comments = fetchCommentsFromDB();
foreach ($comments as $comment) {
    echo '<div>' . htmlspecialchars($comment['content'], ENT_QUOTES) . '</div>';
}
// ENT_QUOTES表示连单双引号都转义

3. CSP(内容安全策略)

这是最后的防线,通过HTTP头告诉浏览器哪些资源可以加载:

# Nginx配置示例
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted.cdn.com";

这个策略表示:

  • 默认只允许加载同源资源
  • 脚本仅允许来自本站和指定的CDN

三、实战中的组合拳

现代前端框架如React/Vue已经内置XSS防护,但仍有需要注意的细节。以Vue为例:

// 安全做法:使用v-text而非v-html
<template>
  <div v-text="userContent"></div>  <!-- 自动转义 -->
</template>

// 危险场景:必须使用v-html时
<div v-html="sanitizedContent"></div>

// 需要先用DOMPurify处理
import DOMPurify from 'dompurify';
export default {
  computed: {
    sanitizedContent() {
      return DOMPurify.sanitize(this.rawContent);
    }
  }
}

对于富文本编辑器(如TinyMCE),需要白名单过滤:

// 使用sanitize-html库
const clean = sanitizeHtml(dirtyHtml, {
  allowedTags: ['b', 'i', 'em', 'strong', 'a'],
  allowedAttributes: {
    'a': ['href']
  },
  allowedIframeHostnames: ['www.youtube.com']
});

四、特殊场景的攻防

1. JSON接口的XSS

即使API返回JSON,如果前端直接innerHTML也会中招:

// 错误示范
fetch('/user-data').then(res => res.json()).then(data => {
  document.getElementById('name').innerHTML = data.name;
});

// 正确做法:使用textContent而非innerHTML
document.getElementById('name').textContent = data.name;

2. Cookie安全

通过HttpOnly和Secure标志保护Cookie:

// Express设置Cookie
res.cookie('sessionID', '12345', {
  httpOnly: true,  // 禁止JS读取
  secure: true,    // 仅HTTPS传输
  sameSite: 'strict' // 禁止跨站发送
});

3. DOM型XSS防御

避免直接操作DOM:

// 危险!使用location.hash直接插入DOM
document.write(location.hash.slice(1));

// 安全方案:校验+编码
const safeHash = encodeURIComponent(location.hash.slice(1));
document.textContent = safeHash;

五、企业级解决方案

对于大型应用,建议采用分层防御:

  1. WAF(Web应用防火墙)
    在Nginx中集成ModSecurity:

    location / {
      ModSecurityEnabled on;
      ModSecurityConfig /etc/nginx/modsec/main.conf;
    }
    
  2. 自动化扫描
    使用OWASP ZAP进行渗透测试:

    zap-cli quick-scan -s xss http://example.com
    
  3. 监控系统
    通过ELK收集异常日志:

    // 日志告警规则示例
    {
      "query": {
        "match": { "message": "<script>" }
      },
      "alert": { "email": "security@example.com" }
    }
    

六、总结与最佳实践

防御黄金法则:

  1. 所有输入都是不可信的
  2. 在数据流动的每个环节做防护
  3. 最小权限原则(如CSP策略)

技术选型建议:

  • 新项目:直接用React/Vue等现代框架
  • 传统项目:引入DOMPurify+Content-Security-Policy
  • 企业级:WAF+定期安全扫描

最后记住:没有100%的安全,但通过层层防护,能让攻击成本高到让黑客放弃。就像给家门装防盗锁,虽然不能防住专业小偷,但能挡住大多数 opportunistic attackers(机会主义攻击者)。