How does impersonation and security permissions in tSQLt tests work? - sql-server

I have a tSQLt test which I expect to fail but it runs with success. When I run the code outside the procedure, it fails as expected, but when executing the test with Run, no error occurs.
I have read the question tSQLt Testing SQL Server security permissions but the accepted answer does not solve my problem.
My test looks like this:
CREATE PROCEDURE TestSecurity.[test AFDK_Reader cannot read from AWS schema]
AS
BEGIN
--EXEC tSQLt.ExpectException
EXECUTE AS USER = 'AFDK_Reader'
select *
from sys.user_token
SELECT * FROM fn_my_permissions('AWS', 'SCHEMA')
ORDER BY subentity_name, permission_name ;
SELECT *
FROM [AWS].[ADRESSEPUNKT_HISTORIK]
REVERT
END
The role has granted select permissions on the AFDK schema only and that is the only database role membership the SQL user has.
The AFDK_Reader has no permissions to read from the AWS schema.
Can anybody tell me how to get on with my debugging? Thanks in advance.

EXECUTE AS... REVERT commands don't behave in the way you are expecting inside a stored procedure. This is a general feature of stored procedure security in SQL Server; one common use of stored procedures is to abstract permissions. The MS docs page Customizing Permissions with Impersonation in SQL Server says:
SQL Server does not check the permissions of the caller if the stored
procedure and tables have the same owner.
and that's effectively what's happening here. Even though the EXECUTE AS changes the security context, that security context isn't checked inside the stored procedure.
The docs page also says:
However, ownership chaining doesn't work if objects have different
owners or in the case of dynamic SQL.
One way to get the behaviour you're expecting would be to run the SELECT statement from inside a dynamic SQL statement, which means that the active security context is tested against the table permissions:
CREATE PROCEDURE TestSecurity.[test AFDK_Reader cannot read from AWS schema]
AS
BEGIN
EXEC tSQLt.ExpectException
EXECUTE AS USER = 'AFDK_Reader'
EXEC ('SELECT * FROM [AWS].[ADRESSEPUNKT_HISTORIK]')
REVERT
END
A alternative (better?) solution might be to use the built-in permission functions to test permissions settings through metadata. Here's one way, still using EXECUTE AS... REVERT and sys.fn_my_permission:
CREATE PROCEDURE TestSecurity.[test AFDK_Reader cannot read from AWS schema]
AS
BEGIN
EXECUTE AS USER = 'AFDK_Reader'
DECLARE #permissionCount int = (SELECT COUNT(*) FROM sys.fn_my_permissions('[AWS].[ADRESSEPUNKT_HISTORIK]', 'OBJECT') WHERE permission_name = 'SELECT' AND subentity_name = '')
REVERT
EXEC tSQLt.AssertEquals 0, #permissionCount
END

Related

Sql Server: How to create a db-snapshot from within stored procedure for non-privileged user?

I'm trying to create a stored procedure that creates a dB-snapshot for a non privileged user.
The idea is to provide to a normal user a way to create a dB snapshot in order to run queries against it and delete the snapshot when it is done with it.
I thought it would be possible to use the 'with execute as owner" in the procedure declaration. However, I always get the following error:
CREATE DATABASE permission denied in database 'master'.
Here is my code:
-- The user that create the sp has sysadmin right
CREATE OR ALTER PROCEDURE [dbo].[makeSnapshot] WITH EXECUTE AS OWNER
AS
-- just an extract of the code (should test if exist...)
DECLARE #exec NVARCHAR(2000)
set #exec = 'CREATE DATABASE test_dbss1900 ON ( NAME = test, FILENAME =
''C:\\Program Files\\Microsoft SQL Server\\MSSQL14.SQLSERVER2017\\MSSQL\\Data\\test_1900.ss'' ) AS SNAPSHOT OF test';
EXEC (#exec)
GO
-- try to execute it (with any user)
EXEC dbo.[makeSnapshot]
Has anyone an idea how I can come up with a stored proc that will allow a normal user to create a db snapshot?
Thank for any help!
José
I actually found a solution by looking at - http://www.sommarskog.se/grantperm.html#serverlevel (chapter 5.3)
The way was to use certificates

Stored procedure to act differently based on user role

I'm trying to figure out if there is a way to achieve the converse of this:
can a SQL Server stored proc execute with higher permission than its caller?
I want to create a stored procedure which does one thing if the user is in a role, but a fallback option if they're not.
My first attempt tried to query the current user's roles, based on this:
How to query current user's roles
I tried to query what role a user was in, and decide what to do based on that. But if you set "mydomain\Domain Users" to a role (for example), users who belong to Domain Users aren't listed in the sys.database_role_members view. So users who were supposed to have permissions don't.
From here https://msdn.microsoft.com/en-us/library/ee677633.aspx
IS_ROLEMEMBER always returns 0 when a Windows group is used as the database
principal argument, and this Windows group is a member of another Windows
group which is, in turn, a member of the specified database role.
My next attempt works like this. Create a stored procedure with the actual permissions, and then a wrapper around it which calls the with lower permissions, and if that fails, perform the fallback action:
CREATE PROCEDURE [internal_myproc]
AS
BEGIN
-- do something here
END
GO
GRANT EXECUTE ON [internal_myproc] TO [Role1] AS [dbo]
GO
CREATE PROCEDURE [myproc]
AS
BEGIN
BEGIN TRY
EXEC [internal_myproc]
END TRY
BEGIN CATCH
-- perform fallback action
END CATCH
END
GO
GRANT EXECUTE ON [internal_myproc] TO [Role1] AS [dbo]
GO
GRANT EXECUTE ON [internal_myproc] TO [Role2] AS [dbo]
GO
GRANT EXECUTE ON [internal_myproc] TO [Role3] AS [dbo]
GO
The problem with this is that [Role1] and [Role2] both succeed in executing [internal_myproc] via [myproc]. If you take the code out of the stored procedure, it behaves the way it should, but because it's hidden inside a stored procedure, it gets implicit permissions to execute other stored procedures. I've experimented with "WITH EXECUTE AS" stuff, but it doesn't seem to change anything.
I also tried "IF HAS_PERMS_BY_NAME('internal_myproc', 'OBJECT', 'EXECUTE') = 1", suggested here MS SQL Server: Check to see if a user can execute a stored procedure , but that seems to not work in certain situations I haven't understood yet.
Is there a way to NOT grant those implicit permissions, to do a permission check inside a stored procedure? Or something equivalent?

Read-only clause for a query

I am wanting to allow users, via an ASP page, to run a query on my database, however I want this query to be restricted to read-only.
I will try to detect as much as I can in the ASP itself (detect words drop, update, delete etc.) and not run the query in these scenarios, but, is there a way I can just append a flag to the query when my ASP submits it to have it execute as read-only regardless of its content?
I realise that, if this is possible, update/delete/drop statements will error - this is fine.
I also want to avoid setting the whole DB or even table as read-only.
I also don't want to replicate my database in any form.
Thankyou for your help.
Dan
Your query should be dynamic. Main problem - sql injection.
I recommend this way:
create database role [DYNAMIC_SQL]. Give required select permissions on tables to [DYNAMIC_SQL] role.
create database user without login [dynamic_sql_proxy]. Map with [DYNAMIC_SQL] role.
create procedure [dynamic_sql__call] with hardcoded execution rights as [dynamic_sql_proxy]. Pass your custom query to this procedure.
Scripts:
(1)
USE [your_db]
GO
CREATE ROLE [DYNAMIC_SQL]
GO
GRANT SELECT ON [dbo].[your_table] TO [DYNAMIC_SQL] AS [dbo]
***
(2)
CREATE USER [dynamic_sql_proxy] WITHOUT LOGIN WITH DEFAULT_SCHEMA = [dbo]
ALTER ROLE [DYNAMIC_SQL] ADD MEMBER [dynamic_sql_proxy]
(3)
CREATE PROCEDURE [dbo].[dynamic_sql__call]
#err_code INT OUTPUT
,#err_msg NVARCHAR(4000) OUTPUT
,#sql NVARCHAR(MAX)
WITH EXECUTE AS 'dynamic_sql_proxy'
AS
BEGIN
SET #err_code = 0;
SET #err_msg = SPACE(0);
BEGIN TRY
EXEC(#sql);
END TRY
BEGIN CATCH
PRINT 'Please handle your error';
END CATCH;
RETURN #err_code;
END;
Thankyou Allan S. Hansen, I used the username/password method to good effect.

"service broker" The server principal "sa" is not able to access the database under the current security context

I'm working with SQL Server 2012 Express.
I'm using Service Broker to run a stored procedure asynchronously.
The activation procedure has to access another database to execute another stored procedure. This is the code:
CREATE PROCEDURE [dbo].[GetNewCodes]
#gintNewCodes bigint,
#presNewCodes tinyint,
#levelNewCodes bigint,
#quantityNewCodes smallint
AS
-- Get new codes from INCIC database.
DECLARE #return_value int,
#xmlGenerated xml,
#xmlString NVARCHAR(MAX)
SET NOCOUNT ON;
-- Set that this stored procedure is running
update dbo.RunningSPs with (serializable) set conf_value = 1
where sp_name = N'GetNewCodes'
if ##rowcount = 0
begin
insert dbo.RunningSPs(sp_name, conf_value) values (N'GetNewCodes', 1)
end
EXEC #return_value = [INCIC].[dbo].[ReadCodeBuffer]
#gint = #gintNewCodes,
#pres = #presNewCodes,
#level = #levelNewCodes,
#quantity = #quantityNewCodes,
#xmlGenerated = #xmlGenerated OUTPUT
SET #xmlString = cast(#xmlGenerated as nvarchar(max))
-- Process these new codes on TRZ.
EXEC dbo.ProcessCodes #XmlString = #xmlString
-- Update that we are not running this procedure any more.
update dbo.RunningSPs with (serializable) set conf_value = 0
where sp_name = N'GetNewCodes'
if ##rowcount = 0
begin
insert dbo.RunningSPs(sp_name, conf_value) values (N'GetNewCodes', 0)
end
The problem is here: [INCIC].[dbo].[ReadCodeBuffer], and the error message is:
Error: 50000
Unrecoverable error in procedure GetNewCodes: 916: The server principal "sa" is not able to access the database "INCIC" under the current security context.
I have followed this tutorial to implement Service, queue and activation stored procedure.
How can I fix this problem?
Read Call a procedure in another database from an activated procedure.
The problem is that activated procedures are run under an EXECUTE AS USER context and as such are subject to database impersonation restrictions (they are sandboxed within the database). Is all explained in Extending Database Impersonation by Using EXECUTE AS.
The solution is to sign your activated procedure and create an user derived from the signing certificate in the target database, and grant this derived user the required permissions. The first link shows a full example.
Remus Rusanu's answer is clearly definitive and correct. (Anyone dealing with Broker Services knows his expertise and invaluable blog.)
I just wanted to document my experience, since google will direct to this question when searching on "The server principal "sa" is not able to access the database..."
In my case, I was call another database from within an activated procedure, but invoking a sql statement directly, not a stored procedure.
Originally, the cross-database call worked fine without a signed certificate and the use of impersonation. Then, after a small syntax change, it started returning the above error message.
Here is what worked without the need for a signed certificate:
IF EXISTS (SELECT name FROM sys.databases WHERE name = N'MyOtherDb')
and here is what provoked the security exception:
IF DB_ID(N'MyOtherDb') IS NOT NULL

Determining the current security checks being made (SQL Server)

One thing that I've always hated more than just about anything in MS SQL Server is the way that security works. The security context constantly switches if you look at the server funny and it's often very hard (for me anyway) to predict or debug.
In dealing with an issue today, I though, "I wish I could just add a line to my code that would display the security context that SQL Server is using when this code runs." Does such a command exist? For example, SELECT security_context()
To be a little clearer... if I'm in a stored procedure and am therefor subject to the security context of the owner of the SP then I'd like to see that. If I'm in code that was called by sp_executesql and it's causing the security to be under the context of the SQL Server service account, then I would want to see that.
At least then I might be able to figure out why SQL Server thinks that I shouldn't have access to something.
Thanks!
EXAMPLE
-- Set up
CREATE USER Test_User WITHOUT LOGIN
CREATE TABLE Test_Security_Context (my_id INT)
INSERT INTO Test_Security_Context VALUES (1)
DENY SELECT ON Test_Security_Context TO Test_User
GO
CREATE PROCEDURE Test_Security_Context_SP
AS
SELECT SUSER_SNAME()
SELECT * FROM Test_Security_Context -- This will return ok
EXEC('SELECT SUSER_SNAME(); SELECT * FROM Test_Security_Context') -- SUSER_SNAME() will match above but select fails
GO
GRANT EXECUTE ON Test_Security_Context_SP TO Test_User
GO
-- Switch to the new user
SETUSER 'Test_User'
GO
-- Do the test
EXEC Test_Security_Context_SP
GO
-- Clean up
SETUSER
DROP PROCEDURE Test_Security_Context_SP
DROP TABLE Test_Security_Context
DROP USER Test_User
GO
Yes, there is such a pair of views that represents your current security context, considering all the details like EXECUTE AS or code signing:
sys.login_token for the server wide context
sys.user_token for the current database context
Every single access you get is ultimately derived from a row in the return of these results. Note that some access are implicit from hard coded role membership (like db_datareader database role or sysadmin server role).
Other that that:
ownership chaining is not related to security context: you are not under the 'context' of the SP owner. Ownership chaining simply states that access checks are skipped for objects owned by the same owner as current object (SP, View).
sp_executesql does not change the security context in any way
Not sure if this is what you mean by security context, but you can retrieve the user associated with your session like:
select SYSTEM_USER
This works for both a SQL Server login or a WIndows login. It even works inside stored procedures with execute as owner. For example,
create procedure dbo.Test
with execute as owner
as
select SYSTEM_USER
go
exec dbo.Test
select SYSTEM_USER
Prints:
sa
MyMachine\MyName
If you're looking for the Windows account that SQL Server is using to do things on your behalf, you could try to run whoami from the command like:
EXEC sp_configure 'show advanced options', 1
RECONFIGURE
EXEC sp_configure 'xp_cmdshell', 1
RECONFIGURE
EXEC master..xp_cmdshell 'whoami'
For me, that returns nt authority\network service.
I think you want to use CURRENT_USER to see the current security context. Here's an example:
SELECT CURRENT_USER AS 'Current User Name';
GO
EXECUTE AS LOGIN = 'junk'
GO
SELECT CURRENT_USER AS 'Current User Name';
GO
REVERT
SELECT CURRENT_USER AS 'Current User Name';
GO
with output (note: I'm admin on my SQL Server for this)
Current User Name
------------------
dbo
(1 row(s) affected)
Current User Name
------------------
Junk
(1 row(s) affected)
Current User Name
------------------
dbo
(1 row(s) affected)

Resources