My client just upgraded from SQL Server 2000 to SQL Server 2016. One stored procedure used 15-20 times per day runs through a table of file names and reports for each on whether it exists.
Previously it used xp_FileExist, but it now seems to require admin rights to be used. I can't give all 176 users admin rights and keep my job.
I found articles suggesting use of GRANT IMPERSONATE and EXECUTE AS USER but can't get it to work.
-- Old code works only for administrators
-- non-administrators ALWAYS get 0 for result even when file exists
EXEC master.dbo.xp_fileexist #docUNC, #FileExists OUTPUT
-- code run using admin account
SELECT SUSER_NAME() LOGIN_Name, USER_NAME() username;
Results
LOGIN_Name username
--------------------------------------
DomainName\potomcandice dbo
-- attempted new code for grant impersonate (want to let maronj impersonate potomcandice)
GRANT IMPERSONATE ON USER:: maronj TO potomcandice
-- New code
EXECUTE AS USER='potomcandice'
EXEC master.dbo.xp_fileexist #docUNC, #FileExists OUTPUT
Still get 0 for result even when the file does exist.
Hoping for suggestions on either
a) how to use "Grant Impersonate" and "Execute as User/Login" or
b) alternatives to xp_fileexist that do not involve xp_commandshell.
I use xp_fileexist inside a function that I found here and ran into the same problem where it didn't return correct results to non-admins. The solution in my environment was to simply add WITH EXECUTE AS OWNER to the function definition as shown below.
CREATE OR ALTER FUNCTION dbo.fnFileExists(#Path VARCHAR(8000)) RETURNS BIT WITH EXECUTE AS OWNER AS
/* Returns 1 if a file exists at the suppied Path. Returns 0 if it doesn't.
Uses WITH EXECUTE AS OWNER to enable use by non-admins.
Source: https://www.tech-recipes.com/rx/30527/sql-server-how-to-check-if-a-file-exists-in-a-directory/
*/
BEGIN
DECLARE #Result INT
EXEC master.dbo.xp_fileexist #Path, #Result OUTPUT
RETURN #Result
END
Find your SP in Object Explorer, go to SP Properties. In an opened window go to Permissions
Then click "Search" under the SP name.
hit "Browse" to find object name and mark "Public". Click OK.
Then Mark Grant "Execute" and try if it works.
This should do the trick
Related
I'm trying to run a stored procedure which starts up a job. When I run it as myself (as a sysadmin), it runs without a hitch, but when I run it as a custom SQL Server user ("SQLAgentUser" -- mapped to a SQL Server login), it gives the error:
EXECUTE permission was denied on the object 'sp_start_job', database 'msdb', schema 'dbo'.
SQLAgentUser is a member of all the following roles in msdb:
SQLAgentOperatorRole
SQLAgentReaderRole
SQLAgentUserRole
In addition, SQLAgentUser has the following explicit permissions granted in MSDB:
GRANT EXECUTE on sp_start_job (and when I look at effective permissions in SSMS, it says it has Execute permissions)
GRANT EXECUTE on sp_stop_job (same as above)
In MyDb (not its actual name), SQLAgentUser has EXECUTE permissions to the sqladm schema, as well as DELETE, INSERT, SELECT, and UPDATE permissions to sqladm.AgentJobsLastRun.
According to everything I found online, this SHOULD be all that's needed, but I'm still getting the error when executing as SQLAgentUser.
Here's the erroring code:
USE [MyDb]
GO
DECLARE #RC int
EXECUTE AS USER = 'SQLAgentUser'
UPDATE [sqladm].[AgentJobsLastRun]
SET RunDate = NULL
WHERE JobName = 'MonthlyJobs'
EXECUTE #RC = [sqladm].[udp_DailyJob]
REVERT
GO
If I comment out, "EXECUTE AS USER = 'SQLAgentUser'," it runs without a hitch.
...and the code inside [sqladm].[udp_DailyJob]:
SET NOCOUNT ON;
-- Insert statements for procedure here
-- First check that the monthly job has run this month (should have run at 2 AM on the first). If not, manually run the job.
DECLARE #AgentJobNameSys nvarchar(128) = N'MonthlyJobs'
DECLARE #LastRunDate datetime = COALESCE((SELECT [RunDate] FROM [sqladm].[AgentJobsLastRun] WHERE JobName = #AgentJobNameSys),DATEADD(month,-1,getdate()))
IF #LastRunDate < DATEFROMPARTS(YEAR(getdate()),MONTH(getdate()),1)
BEGIN
EXEC msdb.dbo.sp_start_job #AgentJobNameSys;
END
Running SQL Server 2019 Developer Edition on my local computer.
Many thanks! :)
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
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
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?
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)