SQL owner chaining - sql-server

Basically I have a front end application called AdminProgram. In SQL Server I have a role called AdminProgramUsers.
Now this role has permissions to various SPs and the like but, crucially, there is no select permission on any table. Everything they see or modify is done through SPs.
Now I've just written a new SP for them. I have had no choice but to use dynamic SQL, that is constructing the actual SQL query in a String variable called #FinalQuery and then running EXECUTE #FinalQuery.
Now initially I was getting a lot of user doesn't have access to the tables, needed to either grant SELECTs to the columns in question, but that solution won't work due to internal politics.
The other thing I seem to understand is that I need to use EXECUTE AS somehow. I have Googled, but I'm struggling to get this right.
And to pre-emptively answer a couple of questions, there is no option but to use dynamic SQL in this SP much to my displeasure, we're talking about SQL Server 2005 and there will be no way of signing any SPs with certificates (mainly as the DBA will flip a nut at the prospect of anything complicated).
So... I think the answer lies somewhere in EXECUTE AS with ownership chaining... but I need to know how to do it... assuming that the role available is AdminProgramUsers...
The current solution I have is:
CREATE SP MySp AS
DECLARE #FinalTable (columns)
DECLARE #FinalQuery
SET #FinalQuery = "EXECUTE AS CALLER
SELECT blah blah"
INSERT #FinalTable
EXECUTE (#QueryString) AS user ='AdminProgramUsers'
Do some more processing on #FinalTable
Select * from #FinalTable
The error I get is:
Unexpected Error in My Sp
Cannot execute as the database principal because the principal "AdminProgramUsers" does not exist, this type of principal cannot be impersonated, or you do not have permission.

here is an excerpt from ms documentation about execute on sql server 2005:
The user or login name specified in AS { LOGIN | USER } = ' name '
must exist as a principal in sys.database_principals or
sys.server_principals, respectively, or the statement will fail.
Additionally, IMPERSONATE permissions must be granted on the
principal. Unless the caller is the database owner or is a member of
the sysadmin fixed server role, the principal must exist even when the
user is accessing the database or instance of SQL Server through a
Windows group membership.
in your code you feed the execute a role but a user or login is expected instead.
the above text is not very clear if you don't read this:
LOGIN
Specifies the context to be impersonated is a login. The scope
of impersonation is the server.
USER
Specifies the context to be
impersonated is a user in the current database. The scope of
impersonation is restricted to the current database. A context switch
to a database user does not inherit the server-level permissions of
that user.
you can specify a user or a login as impersonation context; a role is neither a login nor a user.

Related

How to grant permissions that a user's role has the grant option for?

I'm setting up a secure login system for a new version of my company's software directly on SQL Server 2017. I'm trying to have user account's CONNECT permission to any databases revoked until the users login to an initial authentication database with a set username/password, and use a login procedure to enable their account on a specific database, to which the client would then disconnect from the initial database and reconnect using their personal account, connecting to their target database.
A certificate was created in the initial database, and used to sign a procedure that calls the one shown below. The procedure shown below is not signed by any certificates. The certificate that was created was copied to the target database, and used to create a user, who was then added to a group, Certified, that has permissions to execute the procedure shown below, and has CONNECT WITH GRANT OPTION on the database. When the procedure below gets executed, the grant/revoke statement fails, saying that the grantor doesn't have the necessary permissions (or that the user/role was not found, depending on if I try to add an AS clause)
The granting/revoking of the connect permission is done through dynamic SQL in a stored procedure, in the target database (every database that's part of our software will have an identical procedure to do this job). It works if I run the code as a member of the sysadmin fixed role, but not when I am running it with my set authentication account. The procedure in the target database (ModifyUser) is called from another procedure in the initial database that is signed with a certificate, to which there is a user from the same certificate in the target database that is a member of the role Certified that has been granted CONNECT WITH GRANT OPTION, however whenever I run the procedure, the statement fails.
I've tried 3 versions, changing the content of the AS clause:
AS the certificate user, who is part of the database role, uCompCompID
AS the database role, that the certificate user is a part of, Certified
SQL Server documentation doesn't seem clear about which of these should be used when trying to grant a permission that a role has the grant option for
With no AS clause, so as to grant the permission as the user running the dynamic SQL
This seems to run the query as the authentication user, not the certificate user that is a member of the role that has the grant option for CONNECT
The ModifyUser procedure is short, so I'll include the whole thing below. This procedure is stored in the same database as the user I want to grant/revoke the CONNECT permission to, but is called for a different database signed by a certificate that matches a user created in this same database.
This is version 3, where there is no AS clause.
PROCEDURE [Authorization].[ModifyUser]
#user nvarchar(128),
#status bit
AS
BEGIN
SET NOCOUNT ON
IF #user IS NULL
THROW 50002, 'Invalid argument: #user', 0
IF LTRIM(RTRIM(#user)) = ''
THROW 50002, 'Invalid argument: #user', 1
IF #status IS NULL
THROW 50002, 'Invalid argument: #status', 0
DECLARE #statement nvarchar(200)
IF #status = 1
SET #statement = 'GRANT CONNECT TO ' + #user
ELSE
SET #statement = 'REVOKE CONNECT TO ' + #user
EXEC (#statement)
END
The expected result would be that the CONNECT permission on the target user is changed, but the received result is always an error. The exact error depends on the version used:
Msg 15151, Level 16, State 1, Line 2
Cannot find the user 'uCompCompID', because it does not exist or you do not have permission.
Msg 15151, Level 16, State 1, Line 2
Cannot find the user 'Certified', because it does not exist or you do not have permission.
Msg 4613, Level 16, State 1, Line 2
Grantor does not have GRANT permission.
This is not an issue if I grant the permissions directly on the certificate user. However, I would like to keep the permissions in a role so that when I inevitably need to recreate the certificate after modifying one of the procedures involved in this system, I only need to worry about adding the new certificate user to the appropriate group, instead of needing to grant permissions to the recreated user every time a change is made that requires resigning any of the certificates.
I've resolved this issue for my case. To go to the solution, skip ahead to the horizontal line break.
To clarify my setup if my original post didn't do a good job, I have a database, Initial, that created a certificate, cCompCompID, which was used to sign a procedure in that same database. The certificate was then copied to another database, Target, where it was used to create a user, uCompCompID, that was then added to the role Certified. The role was granted permission to execute ModifyUser, as well as CONNECT WITH GRANT OPTION so that ModifyUser could be used to change the connect permission on other users, but only when called from the signed procedure in Initial (this is a property of Module Signing, which I am using to keep the system as secure as possible from unintended access to these procedures).
I wanted to keep the permissions to the role Certified and not on the user, because I know I'll need to modify the procedures in the future, and that would require the procedure in Initial be resigned, and I would need to create a new certificate to sign the procedure, and then copy the new certificate to the Target database, drop and recreate the user, and then I'd need to worry about granting the right permissions again. Keeping the permissions on the role simplifies the process. The certificate needs to be recreated, because everywhere I've seen, the recommended course of action is t drop the private key from certificates once use of it is done so as to prevent unintended use of them later.
The correct way to grant a permission that a role has the GRANT OPTION for is to use AS <role name> at the end of the grant statement. The reason this was not working correctly in case 2 in my original post is explained by the documentation on granting permissions on a database with an AS clause. When granting, revoking, or denying a permission as a database role, the user executing the statement must have:
ALTER permission on the role, membership in the db_securityadmin fixed database role, membership in the db_owner fixed database role, or membership in the sysadmin fixed server role.
The resolution to my issue is to grant the certificate user, uCompCompID ALTER permission on the Certified role, so that it will be able to grant or revoke the permission by using its role membership. Granting the user this permission does not pose a security threat, because ModifyUser is only granted the permissions granted to uCompCompID when it is called from the procedure in Initial that was signed by the same certificate that uCompCompID was created from. It will not have these permissions if it were invoked directly, or by any procedure not signed by that certificate, thanks to Module Signing (And since the private key is dropped from the certificate once the procedure in Initial is signed, there is no risk of it being signed to anything else to create a security threat without a much bigger hole elsewhere)
As #sepupic points out, it is also necessary for me to sign ModifyUser with the same certificate for the permissions to work. ModifyUser originally has its permissions because it was called from a different procedure that was signed with the certificate. When ModifyUser executes dynamic SQL without being signed, these extra permissions are removed until the dynamic SQL is completed. If ModifyUser is signed with the same certificate, then the dynamic SQL executed by ModifyUser will retain the permissions that were expected and required.
Thank you #BenThul and #sepupic for your help.
First of all you should update your question because it's not clear in what databases did you create a cerificate and which procedure is signed an which is not:
The procedure in the target database (ModifyUser) is called from
another procedure in the initial database that is signed with a
certificate, to which there is a user from the same certificate in the
target database that is a member of the role Certified that has been
granted CONNECT WITH GRANT OPTION
From this it seems that only the procedure in the initial database is signed with a
certificate, but the inner procedure (in the target database) is not, in this case only the outer procedure has permissions granted to a user created from certificate, that's why you get error N3
Grantor does not have GRANT permission
In other cases you get your error because you use execute as clause (this clause admits only user, not login!). When you use it, your proc is sandboxed within the database where it the procedure created, i.e. you cannot do nothing in another database even if you are sysadmin, server just cannot use the corresponding login (to search the corresponding user in other database) unless the database is trustworthy and in't owner has authenticate in another database.

SQL Server Server Logins and Database Users

At work we use AD groups to control access to SQL Server databases.
I can see these groups in Security > Logins and (database) > Security > Users.
The problem is that some DBs have 50+ such AD groups as valid logins/users and I know I am a member of more than one.
Is there a way I can determine which of these AD groups I am logged in under?
You're effectively logged in as all of them. You'll have the union of all of the individual permissions granted to each group that you're a member of.
There are 2 straight forward built in commands that can help with what you are looking for:
First the extended procedure XP_LoginInfo (available at least since SS2000) will show you all the connection paths a particular login is allowed to use to connect to the instance with
DECLARE #LoginName sysname
SELECT #LoginName = SYSTEM_USER
EXEC xp_LoginInfo #AcctName = #LoginName, #Option = 'all'
All the group names shown under the last column "permission path" are the groups that the supplied LoginName is part of. Also note the "privilege" column; it will show if a login has admin or user level rights on the instance.
Now, the function fn_my_permissions (available since SS2005) will show you all the permissions the currently connected login has on the current database or the server.
SELECT * FROM fn_my_permissions(NULL, 'DATABASE');
SELECT * FROM fn_my_permissions(NULL, 'SERVER');
As Damien_The_Unbeliever responded on 11/28/12, basically the final list of permissions is the union of all the permissions assigned--both Grant and Deny--to each group's (and individually where an ID has been added explicitly) permission path you see by executing xp_LoginInfo.
So the answer basically is that you don't exactly connect with any one group when there are multiple allowed paths. Instead, in essence, you connect with them all. While nowhere does SQL Server clearly show or even state the 'unioned' nature of permissions, it can be inferred with the above 2 commands.
A ton of useful Security Catalog Views can be found on MSDN here.

how to deal with "remapping can only be done for users that were mapped to Windows or SQL logins"

The scenario:
Trying to restore from a bacpac taken from SQL Azure.
Either to a new SQL Azure database instance, or an on premises server. For the earlier with the Management portal or the DAC Framework Client Side Tools.
It seems to work fine, and naturally the SQL users are not mapped to SQL logins after the restore.
What I tried:
When I try to map it with:
alter user MyUser with login = MyLogin, it fails with:
Msg 33016, Level 16, State 1, Line 6 The user cannot be remapped to a login. Remapping can only be done for users that were mapped to Windows or SQL logins.
Running select * from sys.database_principals does list the users, but with a much longer SID than a SQL authenticated user I created to compare it to.
On premises if I run a sp_change_users_login 'Report' the users are Not listed, so are not being detected as orphaned.
On premises if I try using sp_change_users_login, it fails with:
Msg 15291, Level 16, State 1, Procedure sp_change_users_login, Line 114 Terminating this procedure. The User name 'MyUser' is absent or invalid.
On premises if I try it through the User Mapping section of the Login Properties UI, I get:
Create failed for User 'My User'. ... User, group, or role 'MyUser'
already exists in the current database.
I tried doing it all over again, in case something was corrupted when restoring for some reason, same results.
The question:
How can I remap these SQL Users?
I'd like to avoid having to re-create those from scratch and any relation those have to the schema objects in the database?
Some extra info:
One type of SQL users that look a lot like what I'm seeing for the SQL Azure users, are ones created with
create user AnotherUser without login
Those fail in the exact same way in all the 3 mapping approaches I used above. That is not the case in any of the approaches for regular SQL users. Additionally the sid is also long, and begins with the same "0x010500000000000903000000"
A sqlmatters article explains that
A user without login is a special type of user that has deliberately
been set up without an associated login.
one can check if it is such a case by examining the SID:
-- SQL to run to identify users without login :
SELECT CASE WHEN DATALENGTH(sid) = 28
AND type = 'S' -- only want SQL users
AND principal_id > 4 -- ignore built in users
THEN 1 ELSE 0 END AS is_user_without_login,*
FROM sys.database_principals
where users without login have longer SID than regular (orphaned) users.
These special users cannot be mapped to a login because they are made that way. Someone must have intentionally or by mistake created a user WITHOUT LOGIN.
You can also find users that can be mapped to a login with the following:
SELECT *
FROM sys.database_principals
WHERE 1=1
AND [type] = 'S'
AND [name] NOT IN ('dbo','guest','INFORMATION_SCHEMA','sys')
AND authentication_type_desc IN ('WINDOWS','INSTANCE')
If you ever get that, it is Most likely a corrupt backup. Just like the linked on premises scenario, the behaviour comes out of nowhere and was traced to a corrupt backup.
In my case, it was the same. Right before posting the question, my last try was: a different backup of the database, it worked without issues.

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

Error with SQL Server "EXECUTE AS"

I've got the following setup:
There is a SQL Server DB with several tables that have triggers set on them (that collect history data). These triggers are CLR stored procedures with EXECUTE AS 'HistoryUser'. The HistoryUser user is a simple user in the database without a login. It has enough permissions to read from all tables and write to the history table.
When I backup the DB and then restore it to another machine (Virtual Machine in this case, but it does not matter), the triggers don't work anymore. In fact, no impersonation for the user works anymore. Even a simple statement such as this
exec ('select 3') as user='HistoryUser'
produces an error:
Cannot execute as the database principal because the principal "HistoryUser" does not exist, this type of principal cannot be impersonated, or you do not have permission.
I read in MSDN that this can occur if the DB owner is a domain user, but it isn't. And even if I change it to anything else (their recommended solution) this problem remains.
If I create another user without login, I can use it for impersonation just fine. That is, this works just fine:
create user TestUser without login
go
exec ('select 3') as user='TestUser'
I do not want to recreate all those triggers, so is there any way how I can make the existing HistoryUser work?
Detect Orphaned Users, then resolve by linking to a login.
DETECT:
USE <database_name>;
GO;
sp_change_users_login #Action='Report';
GO;
RESOLVE:
The following command relinks the server login account specified by <login_name> with the database user specified by <database_user>:
USE <database_name>;
GO
sp_change_users_login #Action='update_one', #UserNamePattern='<database_user>',
#LoginName='<login_name>';
GO
https://msdn.microsoft.com/en-us/library/ms175475.aspx
What user account does the trigger execute as.
You'll need to grant that user IMPERSONATE priviledges for the User Account HistoryUser.
GRANT IMPERSONATE ON USER:: YourUser TO HistoryUser
More details here
http://msdn.microsoft.com/en-us/library/ms181362.aspx
Problems like this that arise after moving a database from one machine to another usually involve mismatched SID's, although I'm not sure if or how it applies to your case. Try dropping and re-creating the database user, making sure to reinstate its permissions to those tables.
It is an "orphaned user". It wont work. Documentation states this clear.
:-(
Fix "orphaned user" state and it will work again

Resources