一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

如何解决在存储过程中执行EXEC(@sql)产生的二次注入问题?

时间:2026-07-01 09:44:57 编辑:袖梨 来源:一聚教程网

EXEC(@sql)是二次注入高危入口,因其直接执行字符串而不区分结构与数据,即使从数据库读取的字段含恶意代码也会被执行;必须改用sp_executesql并确保模板无值、类型显式声明、参数带等号传入,动态对象名需白名单校验,读库数据须重检。

为什么 EXEC(@sql) 是二次注入的高危入口

因为 EXEC 会把整个字符串当作 SQL 命令直接执行,数据库不区分“结构”和“数据”。哪怕你从数据库里取出的是一个看似普通的用户名字段,只要它含 ' OR 1=1 -- 这类内容,拼进 @sql 后就立刻变成可执行代码。

典型错误写法:EXEC('SELECT * FROM users WHERE name = ''' + @name + '''') —— 这里 @name 来自数据库查询结果,而你没做任何隔离处理。

  • 哪怕 @name 是从 sys.tables 查出来的,也不能默认可信
  • QUOTENAME() 只防单引号闭合,挡不住 ]; DROP TABLE x; -- 这种结尾注入
  • 所有拼接进 @sql 的变量,无论来源(用户输入、数据库读取、配置表),都必须视为不可信

必须改用 sp_executesql,且参数绑定要完整

sp_executesql 不是“用了就行”,它只在三要素齐全时才真正起作用:SQL 模板不含值、参数类型显式声明、参数值独立传入。

正确写法分三段:

DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM users WHERE status = @status AND created_at > @since';DECLARE @params NVARCHAR(MAX) = N'@status TINYINT, @since DATETIME2(0)';EXEC sp_executesql @sql, @params, @status = 1, @since = '2024-01-01';
  • @sql 中不能出现任何变量值,只允许 @param 占位符
  • @params 字符串必须写全类型,@status TINYINT 不能简写成 @status
  • 第三个参数列表必须带等号,@status = 1,不是 1@status
  • 如果参数是字符串,长度也要约束,比如 @name NVARCHAR(50),避免 MAX 类型绕过校验

动态对象名(表名/列名)必须白名单校验

SQL Server 不允许把表名、列名当参数传,硬拼就是唯一路径——但这条路必须加锁。

错误做法:SET @sql = 'SELECT * FROM ' + QUOTENAME(@table_name) —— QUOTENAME 只转义括号和引号,不验证合法性。

  • 查系统视图确认存在且归属预期 schema:IF NOT EXISTS (SELECT 1 FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE s.name = 'dbo' AND t.name = @table_name)
  • 排序字段等走硬编码白名单:IF @sort_col NOT IN ('id', 'created_at', 'status') THROW 50000, 'Invalid sort column', 1
  • 别用 OBJECT_ID(@table_name) 单独判断——它不校验 schema,恶意输入如 users; DROP TABLE logs; -- 可能被截断后误判为合法

从数据库读出的数据再进查询前,必须重校验

二次注入的核心陷阱,就是开发者认为“已入库 = 已消毒”。但数据库只是存储容器,不负责语义安全。

  • 哪怕数据来自内部管理后台录入,也要按输入规则重新校验:长度、字符集、正则模式(如 @name NOT LIKE '%[^a-zA-Z0-9_]%'
  • 数字类参数开头加范围检查:IF @user_id < 1 OR @user_id > 999999 RETURN
  • 禁止在过程里做 CAST(@input AS NVARCHAR)CONVERT 后再拼接——转换失败报错,成功后可能已失真或绕过前期校验
  • 如果业务逻辑确实需要拼接(如动态 WHERE 条件),应拆成多个预定义分支,而不是靠字符串拼接兜底

最危险的不是不知道要防,而是以为用了 sp_executesql 就万事大吉。参数类型太宽、对象名校验缺失、读库数据未经重检——任何一个环节松动,都等于给二次注入留了后门。

热门栏目