防止存储过程写入数据库

Joh*_*ren 7 sql-server permissions t-sql

我是一名数据库开发人员,主要编写 SSRS 报告。我想创建存储过程并向 DBA 保证这些存储过程无法写入生产数据库,而 DBA 不必仔细检查存储过程。

我在 DatabaseA 中创建存储过程。我需要存储过程来选择 DatabaseB 中的数据。

我希望任何用户都能够安全地执行 DatabaseA 中的存储过程,但无法使用该存储过程在 DatabaseB 中插入/更新/删除数据,无论用户可能拥有的任何其他权限如何。

DBA 能否在数据库或过程级别设置权限来强制执行此操作?

这是针对 SQL Sever 2016 的。

AMt*_*two 7

所有权链说“不”。

所有权链在 SQL Server 中的工作方式是,EXEC对存储过程进行授予隐式授予所有依赖对象的权限,以便存储过程可以在该数据库中执行其所需的任何操作。

对此的传统答案是您需要信任创建和更改存储过程的进程。如果您不信任该进程,那么您就不能信任向低权限用户授予执行权限。

快速演示

看看所有权链如何影响事物。在此示例中,在表上ReadOnlyUser显式拒绝,并在存储过程上显式授予 EXEC`(仅执行插入到该表中的操作)。INSERT

您将看到用户被拒绝直接插入的能力,但能够运行成功插入的存储过程。

USE [TestingPermissions]

CREATE TABLE dbo.ReadOnlyPlease (
    SomeColumn varchar(100)
    );
GO
INSERT INTO dbo.ReadOnlyPlease (SomeColumn)
VALUES ('One'),('Two'),('Three');

GO
CREATE PROCEDURE dbo.CanItWrite
AS
SET NOCOUNT ON;

INSERT INTO dbo.ReadOnlyPlease (SomeColumn)
SELECT CONVERT(varchar(100),GETDATE(),121);
GO

CREATE LOGIN ReadOnlyUser WITH PASSWORD = 'TheSecure1!';
CREATE USER ReadOnlyUser FROM LOGIN [ReadOnlyUser];
DENY UPDATE ON dbo.ReadOnlyPlease TO [ReadOnlyUser];
GRANT EXECUTE ON dbo.CanItWrite TO [ReadOnlyUser];
GO


EXECUTE AS LOGIN = 'ReadOnlyUser';
--This will fail
INSERT INTO dbo.ReadOnlyPlease (SomeColumn)
SELECT CONVERT(varchar(100),GETDATE(),121);
--this will succeed!
EXEC dbo.CanItWrite;

REVERT;

SELECT * FROM dbo.ReadOnlyPlease;
Run Code Online (Sandbox Code Playgroud)

但是等等,还有更多!

我之前说过:

存储过程可以在该数据库中做任何它想做的事情。

数据库内。

跨数据库所有权链接是完全独立的事情。默认情况下它是禁用的。这意味着您可以将存储过程放入不同的数据库中,并绕过所有权链隐式权限。

如果您仅为报告存储过程创建单独的数据库,则需要管理该过程访问的每个对象的每个权限(精细地或通过高级授予/角色成员身份,例如db_datareader)。

演示时间

在这里,我创建一个单独的数据库 ( ReportingProcedures),创建一个同义词以指向另一个数据库中的表,并创建完全相同的存储过程。我可以编辑该过程以使用由三部分组成的名称,但我喜欢同义词,所以我使用了它们。

权限方面:

  • 两个数据库都有一个用户 (TestingPermissionsReportingProcedures)
  • INSERT在表本身上被明确拒绝
  • EXEC授予两个数据库中相同的程序

您将看到 中的过程TestingPermissions成功,因为数据和代码位于同一个数据库中(感谢所有权链接!),但ReportingProcedures由于没有所有权链接,中的过程失败!

CREATE DATABASE ReportingProcedures;
GO
USE ReportingProcedures

CREATE SYNONYM dbo.ReadOnlyPlease FOR TestingPermissions.dbo.ReadOnlyPlease;
GO
CREATE PROCEDURE dbo.CanItWrite
AS
SET NOCOUNT ON;

INSERT INTO dbo.ReadOnlyPlease (SomeColumn)
SELECT CONVERT(varchar(100),GETDATE(),121);
GO

CREATE USER ReadOnlyUser FROM LOGIN [ReadOnlyUser];
GRANT EXECUTE ON dbo.CanItWrite TO [ReadOnlyUser];
GO

EXECUTE AS LOGIN = 'ReadOnlyUser';
--This is the one that worked; still works
EXEC TestingPermissions.dbo.CanItWrite;
--This one fails though
EXEC ReportingProcedures.dbo.CanItWrite;
Run Code Online (Sandbox Code Playgroud)

物以类聚,人以群分

就像我使用两个数据库作为“容器”来将代码与数据分开一样,您也可以使用具有不同所有者的不同模式。只要两个 schema 属于不同的用户,那么你也可以成功“打破”所有权链的权限继承


Dan*_*man 7

DBA 能否在数据库或过程级别设置权限来强制执行此操作?

是的,您的 DBA 可以使用证书签署报告存储过程,而不是拒绝对 DatabaseB 表的写入权限。DENY优先于调用者GRANT链不适用(跨数据库查询的默认行为,如 @AMtwo 详述)并且调用者不是特权用户(sysadmin 角色成员或 DatabaseB 的所有者),则优先于调用者权限。

如果允许跨数据库链接(DB_CHAINING数据库选项或cross db ownership chaining服务器选项),则需要关闭这些链接(或更改过程所有者)以便评估权限。

下面是权限证书方法的完整注释示例脚本DENY。创建证书后,您的 DBA 可以将签名添加到新的或更改的报告过程中。

CREATE DATABASE DatabaseA;
CREATE DATABASE DatabaseB;
GO

--create demo user to test permissions
CREATE LOGIN DemoUser WITH PASSWORD = 'S*39NOO7756sfv*&6';
GO

USE DatabaseB;
GO
CREATE USER DemoUser;
CREATE TABLE dbo.Table1(Col1 int);
--grant permissions to demo user
GRANT SELECT, INSERT, UPDATE, DELETE ON dbo.Table1 TO DemoUser;
GO
--create cert and user from cert
CREATE CERTIFICATE DenyWriteCertificate
   ENCRYPTION BY PASSWORD = 'S%^aap34577Hbw'
   WITH SUBJECT = 'Deny INSERT UPDATE DELETE';
CREATE USER DenyWriteCertificateUser FROM CERTIFICATE DenyWriteCertificate;
--deny writes to cert user
ALTER ROLE db_denydatawriter ADD MEMBER DenyWriteCertificateUser;
--copy cert to DatabaseA
DECLARE @cert_id int = cert_id('DenyWriteCertificate')
DECLARE @public_key  varbinary(MAX) = certencoded(@cert_id),
        @private_key varbinary(MAX) =
           certprivatekey(@cert_id,
              'S%^aap34577Hbw',
              'S%^aap34577Hbw')
DECLARE @sql nvarchar(MAX) =
      'CREATE CERTIFICATE DenyWriteCertificate
       FROM  BINARY = ' + convert(varchar(MAX), @public_key, 1) + '
       WITH PRIVATE KEY (BINARY = ' +
          convert(varchar(MAX), @private_key, 1) + ',
          DECRYPTION BY PASSWORD = ''S%^aap34577Hbw'',
          ENCRYPTION BY PASSWORD = ''S%^aap34577Hbw'')'
EXEC DatabaseA.sys.sp_executesql @sql;
GO

USE DatabaseA;
CREATE USER DemoUser;
GO

CREATE PROC dbo.ReadProcedure
AS
SELECT Col1 FROM DatabaseB.dbo.Table1;
GO
GRANT EXECUTE ON dbo.ReadProcedure TO DemoUser;
GO

CREATE PROC dbo.WriteProcedure
AS
INSERT INTO DatabaseB.dbo.Table1(Col1) VALUES(1);
GO
GRANT EXECUTE ON dbo.WriteProcedure TO DemoUser;
GO

--these both succeed because caller has read and write permissions
EXECUTE AS LOGIN = 'DemoUser';
GO
EXECUTE dbo.ReadProcedure;
GO
EXECUTE dbo.WriteProcedure;
GO
REVERT;
GO

--add deny cert to report procs
ADD SIGNATURE TO dbo.ReadProcedure BY CERTIFICATE DenyWriteCertificate WITH PASSWORD = 'S%^aap34577Hbw';
ADD SIGNATURE TO dbo.WriteProcedure BY CERTIFICATE DenyWriteCertificate WITH PASSWORD = 'S%^aap34577Hbw';
GO

--read proc succeeds, write proc fails
EXECUTE AS LOGIN = 'DemoUser';
GO
EXECUTE dbo.ReadProcedure;
GO
EXECUTE dbo.WriteProcedure;
GO
REVERT;
GO
Run Code Online (Sandbox Code Playgroud)