您现在的位置是:网站首页 > SQL注入防护文章详情
SQL注入防护
陈川
【
Node.js
】
36652人已围观
5652字
SQL注入攻击原理
SQL注入是一种常见的Web安全漏洞,攻击者通过在用户输入中插入恶意SQL代码,欺骗后端数据库执行非预期的命令。当应用程序未对用户输入进行充分验证和过滤时,就可能发生这种攻击。
典型的SQL注入攻击方式包括:
- 通过表单输入注入
- 通过URL参数注入
- 通过HTTP头注入
- 通过Cookie注入
// 危险示例:直接拼接SQL查询
const username = req.body.username;
const password = req.body.password;
const query = `SELECT * FROM users WHERE username='${username}' AND password='${password}'`;
db.query(query, (err, result) => {
// 处理结果
});
攻击者可以输入 admin' --
作为用户名,注释掉密码检查部分,从而绕过认证。
Node.js中的防护措施
使用参数化查询
参数化查询是最有效的防护手段之一,它确保用户输入始终被当作数据而非代码处理。
// 安全示例:使用参数化查询
const username = req.body.username;
const password = req.body.password;
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(query, [username, password], (err, result) => {
// 处理结果
});
主流Node.js数据库库都支持参数化查询:
- mysql2
- pg (PostgreSQL)
- sequelize (ORM)
- typeorm
ORM框架的使用
ORM(Object-Relational Mapping)框架自动处理SQL注入防护,是更高级的解决方案。
// 使用Sequelize示例
const User = require('./models/user');
async function authenticate(username, password) {
const user = await User.findOne({
where: {
username: username,
password: password
}
});
return user;
}
输入验证和过滤
即使使用参数化查询,输入验证仍是必要步骤。
// 输入验证示例
const Joi = require('joi');
const schema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$'))
});
const { error, value } = schema.validate(req.body);
if (error) {
// 处理验证错误
return res.status(400).json({ error: error.details[0].message });
}
最小权限原则
数据库用户应遵循最小权限原则:
- 应用账户不应有DROP、ALTER等危险权限
- 为不同操作使用不同账户
- 限制账户只能访问必要的表
// 配置数据库连接时使用有限权限用户
const pool = mysql.createPool({
host: 'localhost',
user: 'app_readonly', // 只读用户
password: 'securepassword',
database: 'app_db'
});
高级防护技术
预编译语句
预编译语句(Prepared Statements)是参数化查询的底层实现。
// mysql2预编译语句示例
const mysql = require('mysql2/promise');
async function getUser(id) {
const conn = await mysql.createConnection({/* 配置 */});
const [rows] = await conn.execute('SELECT * FROM users WHERE id = ?', [id]);
return rows[0];
}
存储过程
使用存储过程可以进一步隔离SQL逻辑。
// 调用存储过程示例
const getUser = async (userId) => {
const [rows] = await db.query('CALL sp_get_user_by_id(?)', [userId]);
return rows[0];
};
定期安全审计
- 使用sqlmap等工具测试应用
- 代码审查重点关注SQL查询
- 监控异常数据库查询
// 审计日志中间件示例
app.use((req, res, next) => {
const originalQuery = db.query;
db.query = function(sql, params) {
// 记录所有SQL查询
auditLog.log({
sql: sql,
params: params,
timestamp: new Date(),
endpoint: req.originalUrl
});
return originalQuery.apply(this, arguments);
};
next();
});
常见误区与最佳实践
常见误区
- 认为前端验证足够(前端验证可被绕过)
- 仅过滤特定字符(如单引号)
- 依赖黑名单而非白名单
- 忽视错误信息的泄露
最佳实践
- 始终使用参数化查询或ORM
- 实施深度防御策略
- 定期更新数据库驱动和库
- 禁用详细错误信息
// 生产环境错误处理
app.use((err, req, res, next) => {
// 不向客户端泄露数据库错误详情
if (err.code === 'ER_PARSE_ERROR') {
return res.status(500).json({ error: 'Internal server error' });
}
next(err);
});
实战案例分析
登录功能防护
// 安全的登录实现
const login = async (req, res) => {
try {
const { username, password } = req.body;
// 输入验证
if (!username || !password) {
return res.status(400).json({ error: 'Missing credentials' });
}
// 参数化查询
const [users] = await db.execute(
'SELECT id, username FROM users WHERE username = ? AND password = ?',
[username, password]
);
if (users.length === 0) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// 生成令牌等后续操作
// ...
} catch (err) {
console.error('Login error:', err);
res.status(500).json({ error: 'Login failed' });
}
};
搜索功能防护
// 安全的搜索实现
const searchProducts = async (req, res) => {
try {
const { keyword, minPrice, maxPrice } = req.query;
// 构建安全查询
let query = 'SELECT * FROM products WHERE 1=1';
const params = [];
if (keyword) {
query += ' AND name LIKE ?';
params.push(`%${keyword}%`);
}
if (minPrice) {
query += ' AND price >= ?';
params.push(parseFloat(minPrice));
}
if (maxPrice) {
query += ' AND price <= ?';
params.push(parseFloat(maxPrice));
}
const [products] = await db.execute(query, params);
res.json(products);
} catch (err) {
console.error('Search error:', err);
res.status(500).json({ error: 'Search failed' });
}
};
性能与安全的平衡
查询缓存
// 使用缓存的参数化查询
const getProductById = async (id) => {
const cacheKey = `product:${id}`;
const cached = await cache.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
const [products] = await db.execute(
'SELECT * FROM products WHERE id = ?',
[id]
);
if (products.length > 0) {
await cache.set(cacheKey, JSON.stringify(products[0]), 'EX', 3600);
return products[0];
}
return null;
};
批量操作防护
// 安全的批量插入
const createUsers = async (userList) => {
// 构建参数化查询
let query = 'INSERT INTO users (username, email) VALUES ';
const params = [];
const placeholders = [];
userList.forEach((user, index) => {
placeholders.push('(?, ?)');
params.push(user.username, user.email);
});
query += placeholders.join(', ');
try {
const [result] = await db.execute(query, params);
return result;
} catch (err) {
console.error('Batch insert failed:', err);
throw err;
}
};