安全编码实践
安全编码从开发源头规避漏洞,比事后修补更高效可靠。
变量与作用域安全
避免全局变量污染
JavaScript
// 不安全:全局变量污染
var userData = {};
function processUser() {}
// 安全:使用IIFE或模块
const UserModule = (function() {
const userData = {}; // 私有变量
return {
processUser() {
// 处理逻辑
}
};
})();
// 更佳:ES模块
// userModule.js
const userData = new WeakMap();
export function processUser(user) {
// 模块作用域内安全访问
}
变量提升陷阱
JavaScript
// 危险:var提升导致意外行为
function init() {
if (false) {
var sensitive = 'data'; // 仍被提升
}
console.log(sensitive); // undefined,变量已存在
}
// 安全:使用let/const
function init() {
if (false) {
const sensitive = 'data'; // 块级作用域
}
console.log(sensitive); // ReferenceError
}
敏感数据保护
JavaScript
// 不安全:敏感信息暴露
const API_KEY = 'sk-1234567890'; // 可被遍历访问
// 安全:使用闭包保护
const createAPI = (function() {
const key = process.env.API_KEY; // 环境变量
return {
call(endpoint) {
return fetch(endpoint, {
headers: { 'Authorization': `Bearer ${key}` }
});
}
};
})();
// 更佳:WeakMap存储私有数据
const privateData = new WeakMap();
class SecureObject {
constructor(secret) {
privateData.set(this, { secret });
}
getSecret() {
return privateData.get(this).secret;
}
}
函数安全实践
参数验证
JavaScript
// 不安全:无参数验证
function transfer(from, to, amount) {
accounts[from] -= amount;
accounts[to] += amount;
}
// 安全:严格参数验证
function transfer(from, to, amount) {
// 类型检查
if (typeof from !== 'string' || typeof to !== 'string') {
throw new TypeError('账户必须是字符串');
}
// 数值安全检查
if (!Number.isFinite(amount) || amount <= 0) {
throw new RangeError('金额必须为正数');
}
// 边界检查
if (amount > MAX_TRANSFER) {
throw new Error('超出转账限额');
}
// 执行操作
accounts[from] -= amount;
accounts[to] += amount;
}
避免隐式类型转换
JavaScript
// 危险:隐式转换导致安全问题
if (userInput == 'admin') { // '123' == 'admin' 为 false,但 1 == '1' 为 true
grantAccess();
}
// 安全:严格相等
if (userInput === 'admin') {
grantAccess();
}
// 更安全:类型先验证
if (typeof userInput === 'string' && userInput === 'admin') {
grantAccess();
}
防止原型污染
JavaScript
// 危险:原型污染
function merge(target, source) {
for (const key in source) {
target[key] = source[key];
}
}
merge({}, JSON.parse('{"__proto__":{"admin":true}}'));
// 所有对象被污染
// 安全:Object.create(null)或hasOwnProperty检查
function safeMerge(target, source) {
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
return target;
}
// 更佳:使用Object.assign或展开运算符
const merged = { ...target, ...source };
异步代码安全
错误处理
JavaScript
// 不安全:未处理Promise拒绝
async function fetchData() {
const response = await fetch(url);
return response.json(); // 可能抛出异常
}
// 安全:完整错误处理
async function fetchData() {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('请求失败:', error.message);
throw error; // 或返回默认值
}
}
竞态条件防护
JavaScript
// 危险:竞态条件
let lastResult = null;
async function search(query) {
const results = await fetch(`/search?q=${query}`);
lastResult = results; // 可能被后续请求覆盖
render(lastResult);
}
// 安全:请求标识或取消
let searchId = 0;
async function search(query) {
const currentId = ++searchId;
const results = await fetch(`/search?q=${query}`);
if (currentId === searchId) {
render(results); // 只渲染最新请求
}
}
// 更佳:AbortController取消
const controller = new AbortController();
async function search(query) {
controller.abort(); // 取消前一个请求
try {
const results = await fetch(`/search?q=${query}`, {
signal: controller.signal
});
render(results);
} catch (error) {
if (error.name !== 'AbortError') {
throw error;
}
}
}
安全配置与工具
CSP内容安全策略
JavaScript
// Express设置CSP头
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self' 'nonce-abc123'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https:;"
);
next();
});
环境变量管理
JavaScript
// 不安全:硬编码配置
const dbPassword = 'password123';
// 安全:环境变量
const dbPassword = process.env.DB_PASSWORD;
// 更安全:配置验证
const config = {
db: {
host: process.env.DB_HOST || 'localhost',
password: (() => {
if (!process.env.DB_PASSWORD) {
throw new Error('DB_PASSWORD未配置');
}
return process.env.DB_PASSWORD;
})()
}
};
依赖安全检查
Bash
# 检查已知漏洞
npm audit
# 自动修复
npm audit fix
# 使用更严格的包管理器
npm ci # 使用lockfile确保一致性
代码审查清单
| 检查项 | 安全要求 |
|---|---|
| 变量声明 | 使用const/let,避免var |
| 全局变量 | 最小化,使用模块封装 |
| 参数验证 | 类型、范围、边界检查 |
| 错误处理 | try-catch覆盖所有异步操作 |
| 敏感数据 | 不硬编码,使用环境变量 |
| 原型操作 | 禁止修改Object.prototype |
| 正则表达式 | 避免ReDoS,限制回溯 |
要点总结
核心原则:
- 最小权限原则:变量、函数、模块最小暴露
- 深度防御:多层验证,不依赖单一防护
- 安全默认:默认拒绝,显式允许
- 输入不信任:所有外部数据验证后才使用
实践要点:
- 使用const/let替代var
- 所有函数参数必须验证
- 敏感配置使用环境变量
- 异步代码必须有错误处理
- 定期进行依赖安全审计
📝 发现内容有误?点击此处直接编辑