SQL Server security permission differences between select and sp_executesql within stored procedure

eiee3dmh  于 2023-11-16  发布在  其他
关注(0)|答案(2)|浏览(93)

A member of my team was doing some performance test and tweaks on SQL stored procedures... and noted that;
within the same stored procedure container with the same exec permissions on that sp;

case 1 : calling a sql query string (select) statement with variables, joins, etc...

case 2 : building the sql query string and calling sp_executesql with the sql and all the parameters passed into it

In testing, case 1 was dramatically slower than case 2 . Makes sense, all good, because the caching is what we were after in that regards.

What surfaced though is that granting permissions to a user to run the stored procedure, in case 1 , meant they didn't need explicit permission to all the tables included in the joined select statement, while in case 2 with calling the query through sp_executesql we would have to explicitely grant permission to all the tables in the sql query for it to run successfully.

Questions//

First is why... is there a specific rationale as to why the permissions are implied and cascaded with one, and not the other, and

Second is... is there a work around***?

Both cases were executed within the same stored procedure container with no changes to permissions. case 1 works fine, case 2 triggers errors with no permissions to all the tables.

case 1 sample code

ts.tick_serv_id AS tick_serv_id
       ,ts.ticket_id AS ticket_id    
       ,ts.service_id AS service_id
       ,ts.quantity AS quantity
       ,ts.employee_id AS employee_id    
       ,ts.commission AS commission
       ,ts.assumed_service_date AS assumed_service_date
       ,ts.service_date AS service_date
       ,ts.status AS status
       ,ts.comments AS comments
       ,s.service_type_id AS service_type_id    
       ,st.description      AS description
       ,st.edit_quantity AS edit_quantity
       ,s.print_text AS print_text
       ,sr.unit_type AS unit_type
       ,sr.rate AS rate
       FROM ticket_services ts
   INNER JOIN tickets t ON t.ticket_id=ts.ticket_id
   INNER JOIN services s ON ts.service_id=s.service_id                                   
   INNER JOIN service_rates sr ON ts.service_id=sr.service_id 
   INNER JOIN service_types st ON s.service_type_id=st.service_type_id     
   WHERE (tick_serv_id = @tick_serv_id OR @tick_serv_id IS NULL)
     AND (ts.ticket_id = @ticket_id OR @ticket_id IS NULL)
       AND (ts.service_id = @service_id OR @service_id IS NULL)
       AND (ts.quantity = @quantity OR @quantity IS NULL)
       AND (ts.employee_id = @employee_id OR @employee_id IS NULL)
       AND (ts.commission = @commission OR @commission IS NULL)
       AND (ts.assumed_service_date = @assumed_service_date OR @assumed_service_date IS NULL)
       AND (ts.service_date = @service_date OR @service_date IS NULL)
       AND (t.open_date BETWEEN sr.open_date AND sr.close_date)
       AND (ts.status = @status OR @status = '')
       AND (ts.comments = @comments OR @comments = '')

case 2 sample code

@tick_serv_id       bigint,
      @ticket_id            bigint,
      @service_id          int,
      @quantity            int,
      @employee_id         int,
      @commission          float,
      @assumed_service_date datetime,
      @service_date        datetime,
      @status              char(1),
      @comments            varchar(500)'

DECLARE @sql nvarchar(max) = N'
              SELECT
              ts.tick_serv_id 
              ,ts.ticket_id  
              ,ts.service_id  
              ,ts.quantity  
              ,ts.employee_id  
              ,ts.commission 
              ,ts.assumed_service_date 
              ,ts.service_date  
              ,ts.status  
              ,ts.comments 
              ,s.service_type_id  
              ,st.description      
              ,st.edit_quantity  
              ,s.print_text AS print_text
              ,sr.unit_type AS unit_type
              ,sr.rate AS rate
      FROM ticket_services ts
         INNER JOIN tickets t ON t.ticket_id=ts.ticket_id
      INNER JOIN services s ON ts.service_id=s.service_id                                       
      INNER JOIN service_rates sr ON ts.service_id=sr.service_id     
      INNER JOIN service_types st ON s.service_type_id=st.service_type_id  
         WHERE 1 = 1 AND (t.open_date BETWEEN sr.open_date AND sr.close_date)'
      + CASE WHEN @tick_serv_id IS NOT NULL THEN
        N' AND tick_serv_id = @tick_serv_id' ELSE N'' END
      + CASE WHEN @ticket_id IS NOT NULL THEN
        N' AND ts.ticket_id = @ticket_id' ELSE N'' END
      + CASE WHEN @service_id IS NOT NULL THEN
        N' AND ts.service_id LIKE @service_id' ELSE N'' END
      + CASE WHEN @quantity IS NOT NULL THEN
        N' AND ts.quantity = @quantity' ELSE N'' END
      + CASE WHEN @employee_id IS NOT NULL THEN
        N' AND ts.emplyee_id = @employee_id' ELSE N'' END
      + CASE WHEN @commission IS NOT NULL THEN
        N' AND ts.commission = @commission' ELSE N'' END
      + CASE WHEN @assumed_service_date IS NOT NULL THEN
        N' AND ts.assumed_service_date = @assumed_service_date' ELSE N'' END
      + CASE WHEN @service_date IS NOT NULL THEN
        N' AND ts.service_date = @service_date' ELSE N'' END
      + CASE WHEN @status <> '' THEN
        N' AND ts.status = @status' ELSE N'' END
      + CASE WHEN @comments  <> '' THEN
        N' AND comments = @comments' ELSE N'' END
         ;

              EXEC sys.sp_executesql @sql, @params,
              @tick_serv_id                                                
              ,@ticket_id                               
              ,@service_id                               
              ,@quantity                                                       
              ,@employee_id                                                 
              ,@commission                                             
              ,@assumed_service_date                    
              ,@service_date                            
              ,@status                                  
              ,@comments;
elcex8rz

elcex8rz1#

First is why... is there a specific rationale as to why the permissions are implied and cascaded with one, and not the other

The reason why permissions are not required with the static SQL statement inside the stored procedure is due to ownership chaining. When all the objects involved are owned by the same user, permissions are not checked on indirectly referenced objects. Users need only execute permissions on the stored proc.

Second is... is there a work around***?

Dynamic SQL effectively breaks the ownership chain. Workarounds that do not require one grant permissions to end users on the objects include, EXECUTE AS and signing the proc with a certificate associated with a user with the needed permissions.

Below is an example of the certificate method gleaned from this tutorial in the documentation . This creates an ephemeral certificate for the permissions needed by the proc. See Erland's article for a thorough discussion of the certificate technique.

CREATE CERTIFICATE YourStoredProcedureDynamicSqlCertificate
   ENCRYPTION BY PASSWORD = 'pGFD4bb925DGvbd2439587y'  
      WITH SUBJECT = 'Provide dynamic SQL permissions';   
GO
ADD SIGNATURE TO dbo.YourStoredProcedure
   BY CERTIFICATE YourStoredProcedureDynamicSqlCertificate 
    WITH PASSWORD = 'pGFD4bb925DGvbd2439587y';  
GO  
CREATE USER YourStoredProcedureDynamicSqlCertificateUser 
   FROM CERTIFICATE YourStoredProcedureDynamicSqlCertificate;  
GO  
GRANT SELECT ON dbo.services TO YourStoredProcedureDynamicSqlCertificateUser;  
GRANT SELECT ON dbo.tickets TO YourStoredProcedureDynamicSqlCertificateUser;  
GO  
ALTER CERTIFICATE YourStoredProcedureDynamicSqlCertificate
    REMOVE PRIVATE KEY;
GO
aor9mmx1

aor9mmx12#

A further workaround in some cases is using EXEC(@sql)

You loose the ability to pass variables to the dynamic sql, you have to inline everything, but you win remaining in the same ownership chain as your static SQL.

Inlining your variables can be a serious security risk, as it enables SQL injection attacks. Be mindful when deciding for it!

相关问题