mysql存储过程动态sql安全写法是:prepare→execute→using链,用户输入仅限using绑定的@变量;表名列名等标识符须经information_schema白名单校验,禁用字符串拼接。

存储过程本身不防SQL注入,安全与否完全取决于你怎么写它。用错语法、拼错字符串、绕过参数化,漏洞照出。
MySQL 存储过程中 PREPARE+EXECUTE 怎么写才安全
动态 SQL 在 MySQL 里必须走 PREPARE → EXECUTE → USING 这条链,且所有用户输入只能进 USING,不能进 CONCAT 构建的语句体。
- 危险写法:
SET @sql = CONCAT('SELECT * FROM users WHERE name = ''', in_name, '''');—— 单引号一闭合,in_name传' OR 1=1 --就穿透 - 安全写法:
SET @sql = 'SELECT * FROM users WHERE name = ?'; PREPARE stmt FROM @sql; EXECUTE stmt USING @in_name;——?占位符由数据库引擎绑定,不参与语法解析 - 注意:
USING后面的变量必须是用户变量(@xxx),不能是存储过程参数名(如in_name),否则会报错或静默失败 - 如果要拼表名/列名(无法参数化),必须用
INFORMATION_SCHEMA查白名单校验,不能只靠QUOTE()或REPLACE()
SQL Server 存储过程里 sp_executesql 参数怎么对齐
sp_executesql 是唯一可信赖的参数化执行方式,EXEC(@sql) 等价于裸奔。参数声明和传值顺序错一位,就可能把类型当值、把值当类型。
- 危险写法:
DECLARE @sql NVARCHAR(MAX) = 'SELECT * FROM users WHERE id = ' + @id;—— 字符串拼接,@id传'1; DROP TABLE users; --'直接执行 - 安全写法:
DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM users WHERE id = @id'; EXEC sp_executesql @sql, N'@id INT', @id = @id; - 关键点:第二个参数是参数签名(
N'@id INT'),第三个起才是实际值;签名里类型必须显式声明(比如INT而不是SQL_VARIANT),长度也要匹配(VARCHAR(50)不要写成VARCHAR(MAX)) - 字符串类参数务必加长度限制和内容过滤:
IF LEN(@name) > 50 OR @name LIKE '%[^a-zA-Z0-9_]%' THROW 50000, 'Invalid name format', 1;
Oracle/PostgreSQL 动态执行时 EXECUTE 和 USING 的坑
Oracle 的 EXECUTE IMMEDIATE 和 PostgreSQL 的 EXECUTE 都支持 USING,但用错就等于没用——尤其容易在多参数、复合类型、NULL 处理上翻车。
- PostgreSQL 示例:
EXECUTE 'SELECT * FROM ' || quote_ident(table_name) || ' WHERE status = $1' USING input_status;—— 表名用quote_ident()安全,状态值走USING安全;但若写成EXECUTE 'SELECT * FROM ' || table_name || ' WHERE status = ' || input_status,就全废了 - Oracle 注意:
EXECUTE IMMEDIATE ... USING中,IN参数不能是表达式(如UPPER(:name)),必须是变量;OUT参数需提前声明并传地址 - NULL 值陷阱:PostgreSQL 中
USING NULL会被当作字面量NULL,不是空值;Oracle 中NULL可能触发隐式转换,建议显式判断后跳过执行 - 白名单机制必须独立存在:表名/列名/排序字段这些语法成分,绝不能依赖“过滤掉分号”这种弱规则,得查
pg_tables或all_tables确认真实存在
真正难的不是写对一行 USING,而是整条调用链上没人敢动上游传参逻辑——比如应用层把 JSON 字段直接塞进 @json_input,存储过程再用 JSON_VALUE 提取后拼进 SQL,那前面所有参数化都白搭。安全边界永远在数据第一次落地的地方,不是在存储过程门口。










