防止 NoSQL 注入的前端措施
NoSQL 注入的基本原理
NoSQL 注入是一种针对非关系型数据库的攻击方式,攻击者通过构造恶意输入来破坏查询逻辑或获取未授权数据。与传统SQL注入不同,NoSQL注入可能涉及JSON注入、操作符滥用或类型混淆等特性。例如在MongoDB中,攻击者可能利用$where
或$ne
等操作符绕过验证:
// 危险示例:直接拼接用户输入
const query = {
username: req.body.username,
password: req.body.password
};
db.users.find(query);
当攻击者提交{ "$ne": null }
作为密码时,可能绕过身份验证。前端虽不能完全阻止此类攻击,但可以通过以下措施显著降低风险。
输入验证与类型检查
严格的客户端验证是第一道防线。应确保输入符合预期格式和类型:
function validateLogin(input: { username: string, password: string }) {
// 检查是否为字符串且非空
if (typeof input.username !== 'string' || input.username.trim() === '') {
throw new Error('无效用户名');
}
// 密码复杂度验证
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
if (!passwordRegex.test(input.password)) {
throw new Error('密码需包含字母数字且至少8位');
}
// 防止JSON对象注入
if (typeof input.password === 'object') {
throw new Error('非法输入格式');
}
}
对于数字类型字段,应显式转换并验证范围:
function sanitizePageNumber(rawInput) {
const num = Number.parseInt(rawInput, 10);
if (Number.isNaN(num) || num < 1 || num > 100) {
return 1; // 默认值
}
return num;
}
数据序列化与编码
在将数据发送到后端前,应对特殊字符进行转义处理:
function escapeMongoOperators(input) {
if (typeof input !== 'string') return input;
const operatorPattern = /(\$[a-z]+|\$exists|\$type|\$not)/gi;
return input.replace(operatorPattern, (match) => {
return `\\${match}`;
});
}
// 使用示例
const safeInput = escapeMongoOperators(userInput);
对于GraphQL接口,应使用变量而非字符串拼接:
# 错误方式
query {
users(filter: "${unsafeFilter}") { ... }
# 正确方式
query($filter: String!) {
users(filter: $filter) { ... }
}
内容安全策略(CSP)配置
通过CSP限制内联脚本和外部资源加载,减少XSS导致NoSQL注入的可能性:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-eval';
connect-src api.example.com;
object-src 'none'">
前端ORM的安全实践
使用类型安全的查询构建器可自动防御注入:
// 使用Mongoose示例
import mongoose from 'mongoose';
const UserSchema = new mongoose.Schema({
username: { type: String, required: true },
password: { type: String, select: false }
});
UserSchema.statics.safeFind = async function(filters) {
// 自动转义特殊操作符
const sanitized = Object.entries(filters).reduce((acc, [key, value]) => {
if (typeof value === 'object' && value !== null) {
throw new Error('不允许直接传递查询对象');
}
acc[key] = value;
return acc;
}, {});
return this.find(sanitized);
};
实时输入监控与防御
在输入过程中进行动态检测:
const suspiciousPatterns = [
/\$[a-z]+\(/i,
/["']\s*:\s*["']/,
/^\s*\{.*\}\s*$/
];
inputElement.addEventListener('input', (e) => {
const value = e.target.value;
if (suspiciousPatterns.some(pattern => pattern.test(value))) {
showWarning('检测到可疑输入字符');
e.target.classList.add('input-warning');
}
});
错误信息处理
避免在客户端暴露数据库错误详情:
fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
})
.then(response => {
if (!response.ok) {
// 统一错误消息而非原始错误
throw new Error('认证失败,请检查凭证');
}
return response.json();
})
.catch(error => {
showToast(error.message);
});
安全头设置
确保API请求包含安全头:
fetch('/api/data', {
headers: {
'Content-Type': 'application/json',
'X-Content-Type-Options': 'nosniff',
'X-XSS-Protection': '1; mode=block'
}
});
前端存储安全
避免在localStorage存储原始查询数据:
// 不安全
localStorage.setItem('queryParams', JSON.stringify(rawFilters));
// 较安全方式
const safeFilters = {
page: Number(rawFilters.page) || 1,
search: String(rawFilters.search || '').substring(0, 100)
};
localStorage.setItem('queryParams', JSON.stringify(safeFilters));
测试与自动化检测
在CI流程中加入安全测试:
// 使用Jest测试用例
describe('NoSQL注入防御', () => {
test('应拒绝包含操作符的输入', () => {
const maliciousInput = { username: 'admin', password: { $ne: '' } };
expect(() => validateLogin(maliciousInput)).toThrow();
});
test('应转义Mongo操作符', () => {
expect(escapeMongoOperators('$where')).toBe('\\$where');
});
});
开发环境安全配置
在项目模板中预置安全规则:
// .eslintrc.json
{
"rules": {
"no-eval": "error",
"security/detect-object-injection": "warn",
"security/detect-non-literal-regexp": "error"
}
}
框架特定的防御措施
React示例:使用受控组件并自动转义:
function SearchForm() {
const [input, setInput] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
const safeInput = input.replace(/[${}]/g, '');
fetchResults(safeInput);
};
return (
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
pattern="[^$}{]+"
title="不允许包含特殊字符"
/>
</form>
);
}
性能与安全的平衡
对于高频输入字段可采用防抖验证:
import { debounce } from 'lodash';
const validateInput = debounce((value) => {
if (value.includes('$')) {
highlightSecurityWarning();
}
}, 300);
inputElement.addEventListener('input', (e) => {
validateInput(e.target.value);
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益,请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:防止 SQL 注入的前端措施