您现在的位置是:网站首页 > SQL注入防护文章详情

SQL注入防护

SQL注入攻击原理

SQL注入是一种常见的Web安全漏洞,攻击者通过在用户输入中插入恶意SQL代码,欺骗后端数据库执行非预期的命令。当应用程序未对用户输入进行充分验证和过滤时,就可能发生这种攻击。

典型的SQL注入攻击方式包括:

  1. 通过表单输入注入
  2. 通过URL参数注入
  3. 通过HTTP头注入
  4. 通过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 });
}

最小权限原则

数据库用户应遵循最小权限原则:

  1. 应用账户不应有DROP、ALTER等危险权限
  2. 为不同操作使用不同账户
  3. 限制账户只能访问必要的表
// 配置数据库连接时使用有限权限用户
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];
};

定期安全审计

  1. 使用sqlmap等工具测试应用
  2. 代码审查重点关注SQL查询
  3. 监控异常数据库查询
// 审计日志中间件示例
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();
});

常见误区与最佳实践

常见误区

  1. 认为前端验证足够(前端验证可被绕过)
  2. 仅过滤特定字符(如单引号)
  3. 依赖黑名单而非白名单
  4. 忽视错误信息的泄露

最佳实践

  1. 始终使用参数化查询或ORM
  2. 实施深度防御策略
  3. 定期更新数据库驱动和库
  4. 禁用详细错误信息
// 生产环境错误处理
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;
  }
};

上一篇: XSS防护

下一篇: 依赖安全扫描

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步