真正控制存储过程执行权限需分两层:一是限制谁可调用(execute),二是限定过程内能做什么(如仅select特定表);必须避免授予调用者直接表权限、启用trustworthy或忽略所有权链,而应使用execute as绑定专用用户并显式授权其最小所需权限。

只给 EXECUTE 权限远远不够——用户能跑通过程,不代表过程里干的事受控。真正控制执行权限,得拆成两层:谁被允许调用它,以及它被允许干什么。
只授 EXECUTE,别碰表权限
常见错误是授权时顺手加了表级权限,比如:GRANT EXECUTE ON dbo.usp_GetOrder TO app_user 后又跟一句 GRANT SELECT ON dbo.Orders TO app_user。这等于把后门焊死了。
- 用户一旦有直接查表权,就能绕过过程逻辑,拼接恶意条件或批量导出
- 审计日志里看到
app_user频繁访问sys.tables或sys.columns,大概率是过程用了SELECT *且用户有元数据权限 - 正确做法:确认该用户不在
db_datareader、db_datawriter角色里,再显式DENY INSERT, UPDATE, DELETE ON SCHEMA :: dbo TO app_user
用 EXECUTE AS 锁定过程运行身份
不加 EXECUTE AS,过程默认以调用者身份执行,权限随用户走;设成 EXECUTE AS OWNER 又太危险(owner 往往是 dbo)。
- 建一个无登录专用用户:
CREATE USER proc_executor WITHOUT LOGIN - 只授过程真正需要的权限,比如仅查一张日志表:
GRANT SELECT ON dbo.RequestLog TO proc_executor - 创建过程时绑定:
CREATE PROCEDURE usp_LogRequest WITH EXECUTE AS 'proc_executor' AS ... - 这样无论谁调用,过程都只用
proc_executor的权限干活,调用者连这张表的SELECT权都不用有
跨库引用必须三段名 + 目标库单独授权
过程如果写 OtherDB.dbo.Config,光在本库授 EXECUTE 没用。SQL Server 会逐跳检查权限链。
- 目标库不能开
TRUSTWORTHY ON(默认就是OFF,别动) - 必须进
OtherDB执行:USE OtherDB; GRANT SELECT ON dbo.Config TO proc_executor - 过程内引用必须带完整三段名:
OtherDB.dbo.Config,不能省略库名 - 漏掉任一环,执行就报错:
The server principal "proc_executor" is not able to access the database "OtherDB" under the current security context.
DML 操作要靠运行时拦截补防
权限控制是第一道防线,但开发阶段可能误写 UPDATE,上线后也得主动拦住。
- 别信正则匹配 SQL 文本(
t.text LIKE '%INSERT%'),易误判、性能差、绕过方式多 - 更稳妥的是在关键逻辑外层加
TRY...CATCH,并在CATCH中检查ERROR_NUMBER()是否为 3902(事务被回滚)或 3621(影响行数异常) - 如果过程依赖视图,得一层层验清视图底层触达的表和权限链——视图本身没写权限,不代表它 JOIN 的表也没
最易被忽略的是所有权链断裂和跨 schema 引用:哪怕所有权限都配对了,只要过程里调用另一个没加 EXECUTE AS 的子过程,或者引用了 other_schema.TableA 却没给 proc_executor 授对应 schema 权限,整条链就失效。










