Service broker - Cross-database SELECTs to insert into local DB? - sql-server

I'm using Remus' article http://rusanu.com/2006/03/07/call-a-procedure-in-another-database-from-an-activated-procedure/ as a guide.
What I'm trying to do:
I have a Activated Stored Procedure that (within the Activated SP) calls 2 different stored procedures. One of those stored procedures needs to access tables in another database to lookup some data, after which it writes the record locally with the extra info. Since I'm crossing databases, my options are
"trustworthy" (pass, thanks)
"replicate lookup tables to the same database service broker is in"
"get certificates working" (this approach)
So I did the following, but it's still failing with "lock time out exceeded" and "not able to access database... under the current security context.
In the database service broker is running in:
USE database_with_service_broker_and_queue
GO
CREATE CERTIFICATE mysp_Auditing ENCRYPTION
BY PASSWORD = '123457'
with subject = 'god_i_hope_this_works'
ADD SIGNATURE TO OBJECT::myschema.mystoredprocedure
BY CERTIFICATE mysp_Auditing
WITH PASSWORD = '123457'
ALTER CERTIFICATE mysp_Auditing REMOVE PRIVATE KEY
BACKUP CERTIFICATE mysp_Auditing to file = 'c:\mysp_auditing.CER'
Now in the database with the additional tables:
USE db_with_tables_I_need
GO
CREATE CERTIFICATE mysp_Auditing from file = 'c:\mysp_auditing.CER'
CREATE USER mysp_Auditing FROM CERTIFICATE mysp_Auditing
GRANT AUTHENTICATE to mysp_Auditing
GRANT SELECT ON TABLE1 to mysp_Auditing
GRANT SELECT on TABLE2 to mysp_Auditing
I then ENABLE the queue and watch alllll the error messages scroll by. I even tried changing the stored procedure to add EXECUTE AS OWNER, but still doesn't work.
Any help appreciated.

too long to post as comment
not able to access database... under the current security context.
This means the certificate business is not set up correct (I don't blame you, is ungodly hard to pull it off on first try). I would look at these:
make sure 'dbo' is a valid user. Try running EXECUTE AS USER='dbo'; on both DBs involved. If one fails (it means the DB was created by a Windows SID that is no longer valid) run ALTER AUTHORIZATION ON DATABASE:<dbname> TO sa.
make sure you do not change the procedure after you sign it. Any ALTER will silently drop the signature and invalidate your trust chain.
add EXECUTE AS OWNER
The procedure must have an EXECUTE AS clause, but it should not matter much what it is, as long as there is one. You are granting the SELECT permission to the certificate (hence to the signature) so it should work no matter who the procedure impersonates.
For debugging, I recommend you do it by simply running the stored proc manually, from SSMS query window:
USE myactivateddb;
GO
EXECUTE AS USER='dbo'; -- does not matter who, is important to be AS USER
GO
EXEC myotherdb.dbo.myotherproc ...;
GO
REVERT;
GO
This is easier to debug that trying to do it from activated procedure. If this works, then try the same but instead of executing the other DB stored proc, execute the activated procedure from the SSMS query window (w/o truning on activation on the queue).

Looks like metadata lock. If you do any DDL in your activation procedure, like creating an index or manipulating columns, you can expect lock timeouts if multiple instances of activation procedure are in service. Remember that activation procedure is typically a long running transaction, and if it takes a lock on any object, it keeps it locked until the end of transaction.

Related

Do I need to grant permission on the database if I already granted it to stored procedure?

I have a SQL Server database (let's call it DB1) with a few stored procedures, and these stored procedures are executed by my Web API (that will be consumed by my mobile app later).
These stored procedures get data from DB1 and from another database (let's call it DB2).
I created a SQL Server login to be used by the Web API with public and dbcreator server roles and only public database role in both DB1 and DB2.
Then I followed this guide and granted Execute permission on the stored procedures for the login.
But when I try to execute my Web API method that uses one of the procedures, I get an exception:
The SELECT permission was denied on the object 'APP_USERS', database 'DB2', schema 'dbo'.
So, do I have to grant the permissions on DB1 and DB2 for this to work, even if I already granted it on the stored procedure? Or am I just granting the wrong permission to the stored procedure?
Note: I used to grant permissions directly on the database for each login, because all applications consulting the database were internal of our enterprise (until now). But this mobile app will be public. I talked to a security expert who told me that this practice is insecure, and advised me to grant the permissions only on the stored procedures.
It looks like you've been introduced (whether you wanted to be or not!) to something called database permissions chaining. At a high level, you're allowed to have objects in your own database reference other objects and only have to grant permissions on the referencing object so long as both the referenced and referencing object are owned by the same database principal (i.e. user). For example, if I have a table that I own, I can write a stored proc doing whatever (say a SELECT) against the table and then grant execute on the proc to another user. When the other user goes to execute the proc, permission chaining kicks in and says "the proc and the table are owned by the same user - the execute permission is sufficient"
But! By default, the permissions chain is broken when the referenced object is in another database. Why? I can only speculate as to creators' intent, but imagine a multi-hosted database server and I'm an malicious actor. If I have my own database, I could write a proc that says select * from OtherDb.dbo.Users;, grant permissions on that proc and exfiltrate data from other users' databases.
There are a couple of ways around this:
You can enable cross db ownership chaining at the server level. I don't recommend this, but it is an easy button out of the problem you have.
You can grant permissions on the objects referenced in the procedure. This may be okay, depending on why you're gating data access through stored procedures (which, full disclosure, I like to do in general). This would be a simple grant select on dbo.APP_USERS to «some DB2 principal - a user or group»;. The downside here is that the principal to whom the permissions are granted can do any select on the table now, thereby bypassing the proc.
You can sign your stored procedures. This is a little more involved, but is the more secure option. It involves creating a certificate or asymmetric key in both databases, creating a user based on the same, granting permissions to that user, and finally calling add signature on the related procs. You'd think you're done, but you'll need to re-apply that signature any time someone changes the procedure definition. Why? Let's say that you sign the proc today but then I change it to do something unintended (either innocently or maliciously). If the signature persisted through an alter procedure, the original proc could be a Trojan horse.
Here is a rough sketch of the module signing dance.
use Db1;
create certificate ModuleSigningCert ...;
add signature to dbo.YourProc by certificate ModuleSigningCert;
use Db2;
-- import ModuleSigningCert - either by backup certificate/create certificate
-- you technically only need the public key portion
create user SigningUser from certificate ModuleSigningCert;
grant select on dbo.YourTable to SigningUser;
For what it's worth, I don't know that "database will be accessed by a public app" necessarily means "and now we need to do cross-database stuff". It may, but it may not. For instance, if the public app still accesses the database through an internal application server, you're not getting much security-wise with the multi-database setup.

SQL Server permission types

My team has recently moved into a more formalized system of SQL Server database auditing and deployment controls, and as a result several permissions have been restricted.
Many of us are not familiar with SQL Server security, and have encountered scenarios where we deploy something only to have a permissions restriction on, say, TRUNCATE TABLE denied in production.
It's just an annoyance at this stage, but I've tried to find some consolidated list (cheatsheet? cribsheet? reference lookup?) to easily check against for such functions so that it doesn't happen so easily, but I haven't found any.
I know that the MSN article for each function lists these, but I don't want to have to individually browse to the specific website for every common and rare function just to check, especially if I have to do it more than once because I forgot (for example).
The closest I found were sites like these:
https://www.mssqltips.com/sqlservertip/1718/database-level-permissions-for-sql-server-2005-and-2008/
https://www.simple-talk.com/sql/database-administration/sql-server-security-cribsheet/
...but they were incomplete (couldn't find TRUNCATE in both of them) and a little long: I'm hoping there's a table somewhere that simply put 'action -> action name -> permission name -> server/table level -> default role' or something together in one place.
Is there such a list somewhere?
Unfortunately I am not aware of any document that shows every action possible in SQL Server with the permissions required for it. Such a table would be impractical as there are a huge amount of possible actions, and some actions require multiple permissions, including scenarios where the permission requirements would change according to sub-portions of the action.
For the scenario you are trying to solve, it seems like you are using modules (SPs) to clearly define the actions allowed to the non-admin users, correct? In that case, you may be able to use digitally signed modules to grant the appropriate permissions when executing the SP instead of assigning the permission directly. For example:
CREATE USER [low_priv_user] WITHOUT LOGIN
go
CREATE TABLE [dbo].[myTable](data int);
go
CREATE PROC [dbo].[sp_truncate_my_table]
AS
TRUNCATE TABLE [dbo].[sp_truncate_my_table];
go
GRANT EXECUTE ON [dbo].[sp_truncate_my_table] TO [low_priv_user]
go
-- Will fail due to permission
EXECUTE ( 'EXEC [dbo].[sp_truncate_my_table];' )AS USER = 'low_priv_user';
go
-- Create a certificate to sign the SP,
CREATE CERTIFICATE [signing_cert]
ENCRYPTION BY PASSWORD = '<you_could_use_masetr_key_instead_of_p#55w0rD5>'
WITH SUBJECT = 'demo - module signature';
go
-- sign the SP
ADD SIGNATURE TO [dbo].[sp_truncate_my_table] BY CERTIFICATE [signing_cert]
WITH PASSWORD = '<you_could_use_masetr_key_instead_of_p#55w0rD5>';
go
-- destroy the private key
ALTER CERTIFICATE [signing_cert] REMOVE PRIVATE KEY;
go
-- Create a user for the certificate & grant it all the permissions you would need for running teh SP
CREATE USER [signing_cert] FROM CERTIFICATE [signing_cert];
go
GRANT ALTER ON [myTable] TO [signing_cert];
go
-- Permission check will be OK for the low privileged user
-- You control what this user is allowed to do via SPs
EXECUTE ( 'EXEC [dbo].[sp_truncate_my_table];' )AS USER = 'low_priv_user';
go
As you can see, I created a module that allows the caller to call TRUNCATE on a table, without granting ALTER permission directly to the user.
Ideally, when using this mechanism you would like to follow the least-privilege principle, and grant only the permissions you require and nothing else; but if you are having trouble finding the exact permissions you need, you may use a shortcut: GRANT CONTROL TO [signing_cert].
Obviously such shortcut has significant security implications, as you are literally granting full control of your database to the signed code (including dynamic SQL executed within these modules), but if you decide to do it, I would recommend the following precautions:
Destroy the private key to prevent anyone from abusing it
Do not use dynamic SQL within your signed modules (or at least make sure you are not subject to SQL injection)
If possible, avoid giving CONTROL permission on modules where you can grant the minimum privileges.
Audit all activity on your database.
I am also including a copy of the SQL Database Engine Permission Poster link, which may be useful.
I hope this information helps.

can a SQL Server stored proc execute with higher permission than its caller?

Our SQL Server database has a reporting feature that allows callers to read, but not write, any table, because the user (or, more precise, the connection opened by the web app that's operating on behalf of the user) has only datareader permissions on the database.
We'd like to be able to write a store procedure that is a special "cleanup report" that will scrub the DB of old cached data before running another report. We'd like the same read-only user above to be able to run this stored proc. The queries inside the stored proc will do DELETE operations, but we don't want to give the user the ability to delete anything other than by via calling this proc.
I know about Module Signing but was hoping to avoid the complexity of dealing with certificates.
Is there another solution? We're using SQL Standard Authentication if that matters.
CREATE PROCEDURE dbo.my_procedure
WITH EXECUTE AS OWNER
AS
BEGIN
-- do your stuff here
END
GO
GRANT EXEC ON dbo.my_procedure TO [your_datareader_member];
GO
The granted permission to execute the procedure will allow the delete to occur.
In fact this is a very relevant scenario, to limit ability to perform certain operations (such as delete). The user may not delete random rows from random tables but they can execute a specific targeted delete procedure.

Sql server execute permission; failure to apply permissions

I've just migrated from SQL2000 to SQL2008 and I have started getting an execute permission issue on a stored proc which uses sp_OACreate.
The rest of the system works fine with the db login which has been setup and added to the database.
I've tried:
USE master
GO
GRANT EXEC ON sp_OACreate TO [dbuser]
GO
But this fails with the following error:
Msg 15151, Level 16, State 1, Line 1
Cannot find the user 'dbuser', because
it does not exist or you do not have
permission.
I'm logged into the server as sa with full permissions. I can execute a similar sql statement and apply the permissions to a server role, however not a login/user.
How do I apply the changes to the specific user/login?
I can apply the permissions to the public role and it resolves my issue; however this seems to be a security issue to me which I don't really want to apply to the live server.
Leading on from John's answer I checked the user listings on the Master database and my user wasn't there. Whether it had been deleted or lost some how I don't know. Something may have gone crazy with the migration of the dbs to the new server instance.
Anyway; re-creating the user and associating it to the specific login enabled me to run the following statements on the master database to allow for the execution of the stored procs.
USE MASTER
GO
GRANT EXECUTE ON [sys].[sp_OADestroy] TO [dbuser]
GO
GRANT EXECUTE ON [sys].[sp_OACreate] TO [dbuser]
GO
GRANT EXECUTE ON [sys].[sp_OAMethod] TO [dbuser]
GO
GRANT EXECUTE ON [sys].[sp_OASetProperty] TO [dbuser]
GO
Thanks for all the help and pointers. Hope this helps other people in the future.
The error suggests that the User "dbuser" does not exist in the master database.
I assume the user exists within the master database?
You can check by using the following T-SQL
USE MASTER;
GO
SELECT *
FROM sys.sysusers
WHERE name = 'dbuser'
If the user turns out not to exist, simply use the CREATE USER statement and create a user called "dbuser". The user will automatically be mapped to a Login of the same name, provided one exists.
Your problem could be related to orphaned users.
Try
USE MASTER
GO
EXEC sp_change_users_login 'Report'
This will return one row per orphaned user name. Then,
EXEC sp_change_users_login 'Update_One', 'dbuser', 'dbuser'
Here is some code I'm using the verify that (current user) has EXECUTE permission on sp_OACreate etc:
use master;
select state_desc,name from
sys.database_permissions a
left join
sys.all_objects b
on a.major_id = b.object_id
where name like 'sp_OA%';
As pointed out by #John Sansom and #WestDiscGolf the user has to exist in the Master database and the execution rights must granted in the Master database, hence use Master is required. The query above will return records if the user has execute permissions and empty set if they do not. (Execution in the user database will also return empty set.)
I couldn't figure out a way check these permissions using fn_my_permissions, which is supposedly the right tool for jobs like this.
Check if your user has permissions for the database you use. You can do this by Security -> Logins -> Select User and open the properties window. Then select "User Mapping" from the right menu. Now check the databases that you want the given user to have access to. After that select from the bottom part of the window "Database role membership" and check "db_owner". Now the user will be the owner of the database and will be able to execute queries, store procedures and so on.
UPDATE:
Add user for the database by selecting your database -> security -> users -> right click "New User"
Or you can use this query
CREATE LOGIN AbolrousHazem
WITH PASSWORD = '340$Uuxwp7Mcxo7Khy';
USE AdventureWorks2008R2;
CREATE USER AbolrousHazem FOR LOGIN AbolrousHazem;
GO
Here are more details http://msdn.microsoft.com/en-us/library/ms173463.aspx

Current transaction ID in an audit trigger

I was looking at storing some form of transaction id from an audit trigger. The solution appeared to be to use sys.dm_tran_current_transaction as in this post SQL Server Triggers - grouping by transactions.
However, I cannot use this because the user account running sql statements will not have the "VIEW SERVER STATE" permission and results in the error:
Msg 297, Level 16, State 1, Line 3
The user does not have permission to perform this action.
Does anyone know of an alternative to this view that will provide a similar transaction id or a way to use "WITH EXECUTE AS" on the trigger to allow selecting from this view.
From my attempts at "WITH EXECUTE AS" it appears that server level permissions are not carried over, which is expected really since it is impersonating a database user.
You can resolve almost any security problem using code signing. Most granular and finely tuned access control, is just a bit on the hard side to understand.
Use EXECUTE AS OWNER on the trigger, create a certificate, sign the trigger, drop the private key (so that noone else can use it to ever sign anything again), export the certificate (public key only), import the certificate in master, create a login derived from the certificate, grant authenticate to this login (in order to extend the database execute as impersonation), then grant view server state to this login. This is bullet proof, perfectly controled priviledge control. If the trigger need to be changed, the signing process (including the cert derived login and grants) have to be done again. From a security point of view, this is desired (you are signing a specific variant of the trigger), from operational point of view is rather a pita, but is manageable.
create table t (i int);
create table audit (transaction_id int);
go
create trigger t_audit_trigger
on t
with execute as owner
after insert, update, delete
as
begin
set nocount on;
insert into audit (transaction_id)
select transaction_id from sys.dm_tran_current_transaction;
if (##ROWCOUNT != 1)
raiserror(N'Failed to audit transaction', 16, 1);
end
go
create certificate t_audit_view_server
encryption by password = 'Password#123'
with subject = N't_audit_view_server'
, start_date = '08/10/2009';
go
add signature to t_audit_trigger
by certificate t_audit_view_server
with password = 'Password#123';
go
alter certificate t_audit_view_server
remove private key;
backup certificate t_audit_view_server
to file = 'c:\temp\t_audit_view_server.cer';
go
use master;
go
create certificate t_audit_view_server
from file = 'c:\temp\t_audit_view_server.cer';
go
create login t_audit_view_server_login
from certificate t_audit_view_server;
go
grant authenticate server to t_audit_view_server_login;
grant view server state to t_audit_view_server_login;
go
From SQL Server 2008, Microsoft introduced sys.dm_exec_requests, which is to deprecate sys.sysprocesses. This view returns the transaction_id, and can be called without granting VIEW SERVER STATE. Like sys.sysprocesses, it returns details for the current process if VIEW SERVER STATE is not granted, and all processes if it is.
Although not directly answering your question, rather than using a custom built auditing framework, in SQL Server 2008 you could make use of the Change Data Capture Technology.
See the following reference from Books Online: http://msdn.microsoft.com/en-us/library/bb522489.aspx
EDIT (Solution, added): Here is a walkthrough of how to create a stored procedure to access the system view, making use of the execute as clause and using impersonation.
USE MASTER;
select * from sys.dm_tran_current_transaction
--Create a login with view server state permissions
CREATE LOGIN ViewServerStateLogin
WITH password = 'Hello123';
CREATE user ViewServerStateLogin;
--Create a login to test the permissions assignment
CREATE LOGIN TestViewServerState
WITH password = 'Hello123';
CREATE user TestViewServerState;
--Test with Login
EXECUTE AS LOGIN = 'TestViewServerState';
--This obviously does not work.
select * from sys.dm_tran_current_transaction
revert;
--Grant view server state permission to the ViewServerStateLogin
GRANT VIEW SERVER state TO ViewServerStateLogin;
--Create a procedure to wrap the call to the system view
CREATE PROCEDURE proc_TestViewServerState
AS
SET NOCOUNT ON;
EXECUTE AS LOGIN='ViewServerStateLogin'
select * from sys.dm_tran_current_transaction
revert;
RETURN(0);
--Assign execute permission to the test accounts
GRANT EXECUTE ON proc_TestViewServerState TO TestViewServerState
--Grant impersonation rights to the test login
GRANT IMPERSONATE ON LOGIN::ViewServerStateLogin TO TestViewServerState
--Test with Procedure
EXECUTE AS LOGIN = 'TestViewServerState';
EXEC proc_TestViewServerState
revert;
Starting with SQL Server 2016, you could use CURRENT_TRANSACTION_ID. According to the docs:
Any user can return the transaction ID of the current session.
Encrypt the stored procedure and don't share the pw for ViewServerStateLogin. Then you get a black box of sufficient density to satisfy the auditors.

Resources