编写含用户输入的SELECT查询时,如何防范数据变更风险?
动态SELECT查询的SQL注入威胁与防护方案
一、你可能遗漏的SQL注入威胁
除了篡改/删除数据,动态查询还面临这些常见风险:
- 敏感数据窃取:攻击者通过
UNION SELECT注入跨表查询,比如输入' UNION SELECT username, hashed_password FROM user_accounts--,就能获取其他表的隐私数据。 - 权限绕过/提升:注入
OR 1=1--让查询返回所有数据,绕过业务的权限校验;或者利用数据库内置函数获取高权限信息,比如SQL Server中注入SELECT * FROM sys.server_principals读取服务器权限列表。 - 业务逻辑破坏:注入
' AND 1=2--让查询返回空集,导致正常用户无法获取数据;或者输入' OR 'a'='a让LIKE条件永久为真,泄露全部客户信息。 - 服务器层面攻击:调用数据库系统函数执行恶意操作,比如MySQL的
LOAD_FILE('/etc/passwd')读取服务器文件,PostgreSQL的COPY FROM PROGRAM 'rm -rf /'执行系统命令(需高权限)。 - 过滤绕过:用注释符
--、/* */截断原有查询逻辑,或者将恶意代码转成十六进制、Unicode编码绕过简单的输入过滤。
二、针对动态查询的防护措施(适用于所有DBMS)
只读权限是基础防护,但针对查询本身还需做以下限制:
1. 强制使用输入白名单
- 对于动态列名、表名:绝对不能直接拼接用户输入,只能从你预先定义的合法列表中选择。比如允许用户查询的列只能是
['ID_CUSTOMER', 'NAME_CUSTOMER', 'EMAIL_CUSTOMER'],不在列表中的输入直接拒绝。 - 对于条件参数(如
customer_name):验证数据类型、长度和字符范围,比如字符串限制为字母、数字和常用符号,长度不超过50位。
2. 参数化查询(核心防护手段)
对于WHERE子句、LIKE条件中的变量值,必须用参数化语句(预处理语句),禁止字符串拼接。示例:
- Python(MySQL):
cursor.execute( "SELECT ID_CUSTOMER, NAME_CUSTOMER FROM CUSTOMER_MASTER WHERE NAME_CUSTOMER LIKE %s", ('%' + customer_name + '%',) ) - Java:
String sql = "SELECT ID_CUSTOMER, NAME_CUSTOMER FROM CUSTOMER_MASTER WHERE NAME_CUSTOMER LIKE ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, "%" + customer_name + "%"); ResultSet rs = pstmt.executeQuery();
注意:参数化无法直接用于列名、表名,这类场景必须结合白名单。
3. 安全转义标识符(仅用于无法参数化的场景)
如果必须动态拼接列名/表名,使用数据库官方提供的转义函数包裹标识符:
- MySQL:用
mysql_real_escape_string()或者反引号包裹 - PostgreSQL:用
pg_escape_identifier()或者双引号包裹 - SQL Server:用
QUOTENAME()函数
示例(SQL Server):
DECLARE @colName NVARCHAR(50) = 'NAME_CUSTOMER'; DECLARE @sql NVARCHAR(MAX) = 'SELECT ' + QUOTENAME(@colName) + ' FROM CUSTOMER_MASTER'; EXEC sp_executesql @sql;
4. 限制查询行为
- 给查询添加行数限制:比如MySQL用
LIMIT 100,SQL Server用TOP 100,防止攻击者一次性窃取大量数据。 - 禁止危险关键字:在查询构建前检查输入,若包含
UNION、INSERT、DELETE、DROP、EXEC等危险关键字,直接拒绝请求(注意:此方法仅作为补充,不能替代白名单和参数化)。
5. 谨慎使用存储过程
若需动态构建查询,可将逻辑放在存储过程中,但必须在存储过程内做白名单验证,且用参数传递条件值,避免直接拼接用户输入。
内容的提问来源于stack exchange,提问作者Frontline
相关产品推荐
相关产品推荐

