Speak guys!
In this article I would like to share with you a feature available since SQL Server 2005, which allows the execution of T-SQL codes on behalf of another user. I'm talking about the EXECUTE AS command.
With this high demand for security that companies are looking for, driven by demands for GDPR compliance, organizations are often taking this opportunity to review all security, auditing, and monitoring, and that's one of the reasons that motivated me to write this article.
If you want to delve into more security articles, be sure to click this link here to visit my other articles on this subject.
If you are experiencing a security issue or would like expert advice to review, test and apply security best practices for your business, feel free to contact me this link here.
Introduction
By default, all operations during a session are subject to permission checks for that user. When an EXECUTE AS statement is executed, the session execution context is switched to the login or specific username. After context switching, permissions are checked at the user's logon and security tokens for the account instead of the person calling the EXECUTE AS statement.
I will demonstrate how this works from the creation of a user named “test”, with permission from db_datareader in the AdventureWorks database, but with denied reading in the Person.Person table:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | USE [master] GO CREATE LOGIN [teste] WITH PASSWORD=N'aaa', DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF GO USE [AdventureWorks] GO CREATE USER [teste] FOR LOGIN [teste] GO ALTER ROLE [db_datareader] ADD MEMBER [teste] GO DENY SELECT ON Person.Person TO [teste] GO |
After running the above script, I will check which user is currently in the context of my session:
1 2 3 4 5 6 7 | SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] |
Now, I will use the EXECUTE AS statement to change the current user of my session:
1 2 3 4 5 6 7 8 9 10 11 | EXECUTE AS USER = 'teste' GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO |
As this user is in the database role “db_datareader”, he should have no problems when trying to consult the data in the Address table, for example:
But if we try to query data from the Person table, which I applied a DENY SELECT to this user, he will not be able to execute the command. Remember that my original user is sysadmin, ie he has no access restrictions.
With that, I was able to demonstrate a very interesting form of one of the purposes of EXECUTE AS, which is validate if a particular user can perform an activity after the DBA has granted a permission, For example.
Impersonation Types
View contentRUN AS in Ad-Hoc Queries / Instructions
- LOGIN: Allows you to impersonate a SQL Server login by getting all permissions at the instance level, inheriting permissions like CONTROL SERVER and server roles like sysadmin and securityadmin, for example.
- USER: Allows you to impersonate a SQL Server user, getting all permissions at the database level, inheriting permissions like SELECT on a table and database roles like db_datareader, for example.
Remembering that even if you are impersonating a sysadmin user through a bank that you are db_owner, for example, using EXECUTE AS USER, you will not have the sysadmin “powers” of that user, as EXECUTE AS USER only inherits the database-level permissions. Since you are already db_owner, you will not inherit any new permissions that you do not already have, you can only execute commands as another user (which is still a risk). If you can use the EXECUTE AS LOGIN of a sysadmin user, then you will have all the “powers” at the level of the instance that he has (that is, he can do EVERYTHING).
RUN AS on Database Objects
- CALLER: Run as CALLER is the default; If no other options are specified, the procedure will be performed in the security context of the caller.
- OWNER: Run as OWNER executes the procedure in the context of the object owner. If the object is created in a dbo-owned schema or by the database owner, the procedure runs with unlimited permissions.
- SELF: Run as SELF runs in the security context of the object creator. This is equivalent to running as specified user, where the specified user is the person who creates or changes the object.
- LOGIN / USER: As already described above, you can also use a specific user or login in the object header, allowing everyone who has EXECUTE permission on that object, such as a Stored Procedure, to execute this object with that user / login context specified automatically. .
Later on there is a specific EXECUTE AS topic in Database Objects, where I will give more details about this use.
Rolling back EXECUTE AS and returning to the original user
View contentThe REVERT command
The other way to do this is by using the REVERT command, which will return the security context to the previous user:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | USE [dirceuresende] GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] EXECUTE AS USER = 'teste_sysadmin' GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO REVERT GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO |
Note that the EXECUTE AS command can be nested, ie a security context tree can be created as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | USE [master] GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO EXECUTE AS LOGIN = 'teste_sysadmin' GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO EXECUTE AS LOGIN = 'teste' GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO EXECUTE AS LOGIN = 'teste2' GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO |
And now, let's undo the stack showing the nesting of this context:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO REVERT GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO REVERT GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO REVERT GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO |
EXECUTE AS… WITH COOKIES
This parameter specifies that the execution context can only be rolled back to the previous context if the REVERT WITH COOKIE invocation statement contains the correct @varbinary_variable value. This is a security measure, especially used in connection pooling, to ensure that only those who know the cookie hash can reverse the security context.
Example (Removed GO commands to prevent loss of variable information):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | USE [master] GO -- Exibe as informações do usuário original SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin]; -- Cria a variável do cookie e armazena o hash gerado DECLARE @cookie VARBINARY(8000); EXECUTE AS LOGIN = 'teste_sysadmin' WITH COOKIE INTO @cookie; -- Exibe o cookie SELECT @cookie; -- Mostra o usuário personificado (teste_sysadmin( SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin]; -- Volta o contexto de segurança para o usuário original REVERT WITH COOKIE = @cookie; -- Exibe as informações do usuário original novamente SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin]; |
Now I will try to apply the REVERT command without specifying the token:
Now I will try to change the token and try to apply the REVERT command:
EXECUTE AS… WITH NO REVERT
After presenting the REVERT and REVERT WITH COOKIES commands, there is another clause that impersonate cannot be rolled back to the original user in this session. This statement is WITH NO REVERT. In this case, the only way to revert is by opening a new session.
When the WITH NO REVERT COOKIE = @varbinary_variable clause is specified, SQL Server Database Engine passes the cookie value to @varbinary_variable. The execution context defined by this statement can be reverted to the previous context only if the calling statement REVERT WITH COOKIE = @varbinary_variable has the same value @varbinary_variable.
This option is useful in an environment in which a connection pool is used. Connection pooling is the maintenance of a database connection group for application reuse on an application server. Because the value passed to @varbinary_variable is known only to the caller of the EXECUTE AS statement (in this case, the application), the caller can ensure that the established execution context cannot be changed by anyone else.
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | USE [master] GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO EXECUTE AS LOGIN = 'teste_sysadmin' WITH NO REVERT GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO REVERT GO SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] GO |
What are the permissions to use EXECUTE AS?
View contentHowever, you who are DBA should keep in mind that this permission is EXTREMELY dangerous for an average user, since he can use EXECUTE AS to execute commands using a sysadmin login, for example, and thus do any thing in the instance.
This was one of the reasons why IMPERSONATE ANY LOGIN permission was created on SQL Server 2014. Users with CONTROL SERVER permission already had this privilege implicitly, so they could use EXECUTE AS to execute commands as another user, including sysadmin users. Starting with SQL Server 2014, the DBA can now apply a DENY IMPERSONATE ANY LOGIN command to users with CONTROL SERVER permission and prevent this from happening.
It is worth mentioning that there is also the command GRANT IMPERSONATE ON LOGIN / USER :: [user1] TO [user2], which allows freeing IMPERSONATE access to specific users. In addition, members of the database role db_owner can use EXECUTE AS on users created in these banks. Remembering that even if you are impersonating a sysadmin user through a bank that you are db_owner, for example, using EXECUTE AS USER, you will not have the sysadmin “powers” of that user, as EXECUTE AS User only inherits the database-level permissions. Since you are already db_owner, you will not inherit any new permissions that you do not already have, you can only execute commands as another user (which is still a risk).
Explicit Permissions to Use IMPERSONATE
As I mentioned above, there are some conditions for using EXECUTE AS command in SQL Server.
Condition #1: IMPERSONATE ANY LOGIN
Users who have the explicit IMPERSONATE ANY LOGIN permission can execute commands like any login to the SQL Server instance. Remember that permissions are instance-level, meaning that they apply to all databases in that instance, and commands that require privileges like sysadmin and CONTROL SERVER can be used through EXECUTE AS LOGIN.
To release this permission, you must use the following command:
1 | GRANT IMPERSONATE ANY LOGIN TO [login]; |
Condition #2: IMPERSONATE LOGIN
Users who have the IMPERSONATE LOGIN permission can execute commands as specific logins on the SQL Server instance. This command empowers a loginA to execute commands as if it were loginB and must be released for each desired login. Remember that permissions are instance-level, meaning that they apply to all databases in that instance, and commands that require privileges like sysadmin and CONTROL SERVER can be used through EXECUTE AS LOGIN.
To release this permission, you must use the following command:
1 | GRANT IMPERSONATE ON LOGIN::LoginB TO [LoginA]; |
Condition #3: IMPERSONATE USER
Users who have IMPERSONATE USER permission can execute commands as specific users in a database. This command empowers a user to execute commands as if it were the userB in the database that he has the permission of IMPERSONATE and must be released for each desired login. Commands that require instance level permissions (eg Shutdown, change server role members, create Linked Server, etc.) are NOT executed by IMPERSONATE USER, only commands that require database level permissions, such as INSERT, UPDATE, DELETE. , etc.
To release this permission, you must use the following command:
1 | GRANT IMPERSONATE ON USER::UsuarioB TO [UsuarioA]; |
Implicit Permissions to Use IMPERSONATE
Now I will demonstrate that there are some implicit conditions where you can use the EXECUTE AS command on SQL Server without having an explicit permission to do so.
Condition #1: SYSADMIN
Role of SQL Server “Administrator”. Users who are in this role can do anything in the database and therefore already have the permission of IMPERSONATE LOGIN, IMPERSONATE USER and IMPERSONATE ANY LOGIN and cannot be denied privileges with DENY.
To add someone to this role, use the following command:
1 | ALTER SERVER ROLE [sysadmin] ADD MEMBER [Login]; |
Condition #2: CONTROL SERVER
SQL Server “Administrator” permission. Users who have this permission can do almost anything in the database and therefore already have IMPERSONATE LOGIN, IMPERSONATE USER and IMPERSONATE ANY LOGIN permission, but, unlike role sysadmin, these users MAY have IMPERSONATE privileges denied with DENY.
To add someone to this role, use the following command:
1 | GRANT CONTROL SERVER TO [Login]; |
Condition #3: SECURITYADMIN
Users who are on the server role securityadmin control the security and permissions of the SQL Server instance. In this role, these users have the IMPERSONATE ANY LOGIN permission, so they can do anything in the database as they can use the EXECUTE AS LOGIN command and execute commands as if they were a sysadmin user and for this reason, Microsoft's own documentation treats this role as equivalent to role sysadmin for security concerns. Unlike role sysadmin, these users CAN have IMPERSONATE privileges denied with DENY.
To add someone to this role, use the following command:
1 | ALTER SERVER ROLE [securityadmin] ADD MEMBER [Login]; |
Condition #4: db_owner
Users who are in the database role db_owner can execute any DDL, DCL, or DML command in the database that they have this role. For this reason, these users already have the IMPERSONATE USER permission implied on all database users. Remember that these users MAY be denied IMPERSONATE privileges with DENY.
To add someone to this role, use the following command:
1 | ALTER DATABASE ROLE [db_owner] ADD MEMBER [Usuario]; |
Condition #5: db_securityadmin
Users in the database role db_securityadmin can manage security and permissions on the database. For this reason, these users have the IMPERSONATE USER permission implicit on all database users and can execute commands as if they were one of the users in role db_owner. Remember that these users MAY be denied IMPERSONATE privileges with DENY.
To add someone to this role, use the following command:
1 | ALTER DATABASE ROLE [db_securityadmin] ADD MEMBER [Usuario]; |
Who is allowed to run EXECUTE AS?
View contentSpeaking at the database level, where the user can run IMPERSONATE on bank users, he needs to be in the database role db_owner or has IMPERSONATE ON USER permission for the specific users he can impersonate.
Therefore, I will demonstrate below how to identify who are the users who have these permissions on the SQL Server instance.
Users with CONTROL SERVER permissions or roles sysadmin and securityadmin
To identify users with CONTROL SERVER permissions or roles sysadmin and securityadmin who can run EXECUTE AS for any login, run the script below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | -- Verificando usuários com permissões CONTROL SERVER ou roles sysadmin e securityadmin SELECT A.[name] AS [login], A.principal_id, A.[sid], A.[type_desc], A.is_disabled, B.[permission_name] FROM sys.server_principals A JOIN ( SELECT grantee_principal_id, [permission_name] COLLATE SQL_Latin1_General_CP1_CI_AI AS [permission_name] FROM sys.server_permissions WHERE class_desc = 'SERVER' AND [permission_name] = 'CONTROL SERVER' AND [state] IN ('G', 'W') UNION SELECT a1.member_principal_id, a2.[name] COLLATE SQL_Latin1_General_CP1_CI_AI AS [role] FROM sys.server_role_members AS a1 JOIN sys.server_principals AS a2 ON a1.role_principal_id = a2.principal_id WHERE a2.[name] IN ('sysadmin', 'securityadmin') ) B ON A.principal_id = B.grantee_principal_id WHERE A.is_fixed_role = 0 |
Users with IMPERSONATE ANY LOGIN
To identify who are the users who have the explicit IMPERSONATE ANY LOGIN permission, and thus being able to execute commands like any user, run the script below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | -- Verificando usuários com IMPERSONATE ANY LOGIN SELECT A.class, A.class_desc, A.[type], A.[permission_name], A.[state], A.state_desc, B.[name] AS grantee, -- quem recebeu a permissão C.[name] AS grantor -- quem concedeu a permissão FROM sys.server_permissions A JOIN sys.server_principals B ON A.grantee_principal_id = B.principal_id LEFT JOIN sys.server_principals C ON A.grantor_principal_id = C.principal_id WHERE A.[type] = 'IAL' |
Users with IMPERSONATE LOGIN
To identify who are the users who have the explicit IMPERSONATE LOGIN permission and can execute commands like some specific users, run the script below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | -- Verificando usuários com IMPERSONATE LOGIN SELECT A.class, A.class_desc, A.[type], A.[permission_name], A.[state], A.state_desc, B.[name] AS grantee, -- quem recebeu a permissão C.[name] AS impersonated_user -- quem pode ser personificado por quem recebeu a permissão FROM sys.server_permissions A JOIN sys.server_principals B ON A.grantee_principal_id = B.principal_id LEFT JOIN sys.server_principals C ON A.grantor_principal_id = C.principal_id WHERE A.[type] = 'IM' |
Users in the db_owner or db_securityadmin database roles
To identify who are the users in the database roles db_owner or db_securityadmin and can execute commands like any database user, run the script below:
1 2 3 4 5 6 7 8 9 10 11 12 | -- Verificando usuários na role db_owner ou db_securityadmin SELECT DB_NAME() AS [database], B.[name] AS [user], C.[name] AS [database_role] FROM sys.database_role_members A JOIN sys.database_principals B ON A.member_principal_id = B.principal_id JOIN sys.database_principals C ON A.role_principal_id = C.principal_id WHERE C.[name] IN ('db_owner', 'db_securityadmin') AND B.[name] <> 'dbo' |
Users in roles db_owner and db_securityadmin (check in all databases)
As in the previous example, to identify who are the users who are in the database roles db_owner or db_securityadmin, now parsing across all instance databases, run the script below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | -- Verificando usuários nas roles db_owner e db_securityadmin em todos os databases IF (OBJECT_ID('tempdb..#Dbowner_Database') IS NOT NULL) DROP TABLE #Dbowner_Database CREATE TABLE #Dbowner_Database ( [database] nvarchar(128), [user] nvarchar(128), [database_role] nvarchar(128) ) INSERT INTO #Dbowner_Database EXEC sys.sp_MSforeachdb ' SELECT ''?'' AS [database], B.[name] AS [user], C.[name] AS [database_role] FROM [?].sys.database_role_members A JOIN [?].sys.database_principals B ON A.member_principal_id = B.principal_id JOIN [?].sys.database_principals C ON A.role_principal_id = C.principal_id WHERE C.[name] IN (''db_owner'', ''db_securityadmin'') AND B.[name] <> ''dbo''' SELECT * FROM #Dbowner_Database |
Users with IMPERSONATE USER in a database
To identify who users have the explicit IMPERSONATE USER permission and can execute commands like some specific database users, run the script below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | -- Verificando usuários com IMPERSONATE USER em um database SELECT A.class, A.class_desc, A.[type], A.[permission_name], A.[state], A.state_desc, B.[name] AS grantee, -- quem recebeu a permissão C.[name] AS impersonated_user -- quem pode ser personificado por quem recebeu a permissão FROM sys.database_permissions A JOIN sys.database_principals B ON A.grantee_principal_id = B.principal_id LEFT JOIN sys.database_principals C ON A.grantor_principal_id = C.principal_id WHERE A.[type] = 'IM' |
Users with IMPERSONATE USER (check in all databases)
As in the previous example, to identify who are the users who are in the database roles sysadmin or db_securityadmin, now looking at all instance databases, run the script below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | -- Verificando usuários com IMPERSONATE USER em todos os databases IF (OBJECT_ID('tempdb..#Permissoes') IS NOT NULL) DROP TABLE #Permissoes CREATE TABLE #Permissoes ( [database] nvarchar(128), [class] tinyint, [class_desc] nvarchar(60), [type] char(4), [permission_name] nvarchar(128), [state] char(1), [state_desc] nvarchar(60), [grantee] nvarchar(128), [impersonated_user] nvarchar(128) ) INSERT INTO #Permissoes EXEC sys.sp_MSforeachdb ' SELECT ''?'' as [database], A.class, A.class_desc, A.[type], A.[permission_name], A.[state], A.state_desc, B.[name] AS grantee, C.[name] AS impersonated_user FROM [?].sys.database_permissions A JOIN [?].sys.database_principals B ON A.grantee_principal_id = B.principal_id LEFT JOIN [?].sys.database_principals C ON A.grantor_principal_id = C.principal_id WHERE A.[type] = ''IM''' SELECT * FROM #Permissoes |
RUN AS on database objects
View contentBefore the examples, I will remember the possible forms of EXECUTE AS on objects:
- CALLER: Run as CALLER is the default; If no other options are specified, the procedure will be performed in the security context of the caller.
- OWNER: Run as OWNER executes the procedure in the context of the object owner. If the object is created in a dbo-owned schema or by the database owner, the procedure runs with unlimited permissions.
- SELF: Run as SELF runs in the security context of the object creator. This is equivalent to running as specified user, where the specified user is the person who creates or changes the object.
- LOGIN / USER: As described above, you can also use a specific user (database level permissions) or login (instance level permissions) on the object header, allowing everyone who has EXECUTE permission on that object, such as a Stored Procedure, execute this object with the specified user / login context automatically.
The types of objects that may have the EXECUTE AS clause in the definition are:
- Stored Procedures - CALLER | SELF | OWNER | 'user_name'
- Functions (except inline table-valued functions) - CALLER | SELF | 'user_name'
- DDL Triggers with Database Scope - CALLER | SELF | 'user_name'
- DDL Triggers with Server Scope and logon triggers - CALLER | SELF | 'login_name'
- Queues - SELF | OWNER | 'user_name'
- Azure SQL Database - Stored Procedures - CALLER | SELF | OWNER | 'user_name'
- Azure SQL Database - Functions (except inline table-valued functions) - CALLER | SELF | OWNER | 'user_name'
- Azure SQL Database - DDL Triggers with Database Scope - CALLER | SELF | 'user_name'
To demonstrate a common use of EXECUTE AS in Stored Procedures, I will create an SP in msdb for running jobs by passing the job name as a parameter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | USE [msdb] GO CREATE OR ALTER PROCEDURE dbo.stpInicia_Job ( @Job_Name AS VARCHAR(128) ) WITH EXECUTE AS OWNER AS BEGIN EXEC msdb.dbo.sp_start_job @job_name = @Job_Name END GO GRANT EXECUTE ON dbo.stpInicia_Job TO [teste] GO |
Now I will try to manually execute the sp_start_job:
and then try to run for this SP that I created:
By adding an EXECUTE AS clause to an object, you do not need to do anything for the user to have access to that SP, in addition to the usual EXECUTE permission in this Stored Procedure, for example. I will repeat the examples directly connected with the user "test", without using EXECUTE AS at any time:
And now, I will execute the procedure that I created and that will allow me to run any instance job, even without having permissions to it on my user:
Note: This stpInicia_Job SP has to be created in msdb for the way that permissions work in SQL Server. If you create a Stored Procedure and within it there are queries or commands on objects in other databases, the executing user must have explicit permissions on those objects even using EXECUTE AS. If everything inside the Stored Procedure only references objects from the database itself, the executor does not need to have any permissions on the objects involved within the SP, only EXECUTE permission on the Stored Procedure itself.
It is worth remembering that when you create an object with EXECUTE AS USER = 'User', deactivating or denying the connect privilege for the login associated with that user will NOT change the behavior of the use of that object, once the login actions end not influencing permissions at the database level in this situation (except in cases where the user is sysadmin, for example, and does not have explicit permissions on that object's database).
That is, if an SP has the clause WITH EXECUTE AS USER = 'User1' and you disable the login associated with that user, this will not cause an SP that has executed to generate an error when someone tries to execute it. But if you rename or delete that user in the bank, then SP will stop working.
Safety Tests in Audit Routines
View contentTest # 1 - IMPERSONATE ANY LOGIN
To test the misused impersonate, I will take the same user as the example (test) and grant the IMPERSONATE ANY LOGIN privilege.
1 2 3 4 5 | USE [master] GO GRANT IMPERSONATE ANY LOGIN TO [teste] GO |
And now, I will connect in the instance with this user:
And let's try to select the table Person, which I applied a DENY SELECT to this user:
Well, he really doesn't have permission. What if we used another user to access the data? For example, the user “dirceu.resende”, which is sysadmin ..
Let's check which permissions he has access to after impersonate?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | SELECT ORIGINAL_LOGIN() AS [ORIGINAL_LOGIN], USER_NAME() AS [USER_NAME], SUSER_NAME() AS [SUSER_NAME], SUSER_SNAME() AS [SUSER_SNAME], SYSTEM_USER AS [SYSTEM_USER], IS_SRVROLEMEMBER('sysadmin') AS [isSysAdmin] SELECT 'login' AS token_type, * FROM sys.login_token AS LT UNION ALL SELECT 'user' AS token_type, * FROM sys.user_token AS UT GO EXECUTE AS LOGIN = 'dirceu.resende' GO SELECT 'login' AS token_type, * FROM sys.login_token AS LT UNION ALL SELECT 'user' AS token_type, * FROM sys.user_token AS UT |
Hmm .. What if we tried to create a table? This “test” user only has the db_datareader permission.
Yes .. This user really can do everything that the impersonated user can do .. As I am impersonating a user member of the server role sysadmin, a common user can now do ANYTHING, even shut down the server! And does he want to remain an “ordinary” user?
Ready! Now he himself has become a sysadmin. If you want, you can even delete the other sysadmin users and “take” control of the instance for them. See how extremely dangerous this permission can be!
And it doesn't stop there. Look at another example of how this permission, when misused, can wreak havoc. Imagine that you, DBA, have a routine to log changes made to your database, such as the one I make available in the post. How to create an Audit trigger to log object manipulation in SQL Server.
Now imagine a malicious user using this to do wrong things and blame another colleague? Let's test .. First, I will create the table and change a column. All logs will be recorded using the user who performed the actions.
And now, to delete the table, I will use EXECUTE AS to delete as if it were another user.
Ready. I screwed up and still blamed it on my colleague. Want to solve it? Start using the ORIGINAL_LOGIN () function in your audit and log routines. To demonstrate this, I will change the trigger I created to create this audit:
Now, I will create the table again, change it and remove the login “dirceu.resende” to delete the table and leave the responsibility on his accounts:
Yeah, this time it didn't work. The ORIGINAL_LOGIN () function revealed the actual command executor, even with EXECUTE AS.
Test # 2 - Test with IMPERSONATE USER
Another test that we can try is to release the impersonate privilege on a specific user. For example, I want the “test” user to be able to use the EXECUTE AS command only as the “test2” user, both with low access level. For this, I will create the user “test2”, with the same permissions as “test”, but without the DENY of select in the Person table.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | USE [master] GO CREATE LOGIN [teste2] WITH PASSWORD=N'aaa', DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF GO USE [AdventureWorks] GO CREATE USER [teste2] FOR LOGIN [teste2] GO ALTER ROLE [db_datareader] ADD MEMBER [teste2] GO |
Created user, let's try to use EXECUTE AS from the “test” user:
Obviously, it was wrong. The “test” user does not have IMPERSONATE permission. Let's grant permission and try again:
1 2 3 4 5 | USE [master] GO GRANT IMPERSONATE ON LOGIN::teste2 TO [teste] GO |
Oops! Now I am executing the commands as if it were the user “test2”. The “test” user is unable to query the Person table, as he has a DENY SELECT there. We will try to read the data through the user “teste2” with EXECUTE AS:
Oops! We were able to read the data in the table, even though the “test” user is not allowed to do so. Let's confirm if he doesn't succeed:
Yeah .. I can't, just with EXECUTE AS .. And can I use EXECUTE AS on another user I don't have access to?
Wonderful! I was unable to apply IMPERSONATE to another user, especially “dirceu.resende”, which is sysadmin .. Imagine the damage this could cause ..
EXECUTE AS and Resource Governor
View contentWhat I want to test here is whether EXECUTE AS “cheats” the Resource Governor as well, managing to execute a query without being limited by the RG.
For this, I will create the Resource Pool, Workload Group, the classification function and enable the Resource Governor in the instance, limiting the resources that can be used by the “test” user:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | USE [master] GO ---------------------------------------------------------------------------------------------- -- "Limpeza" do Resource Governor ---------------------------------------------------------------------------------------------- ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION=NULL) GO ALTER RESOURCE GOVERNOR RECONFIGURE GO ALTER RESOURCE GOVERNOR DISABLE GO IF (EXISTS(SELECT NULL FROM sys.resource_governor_workload_groups WHERE [name] = 'Grupo1')) DROP WORKLOAD GROUP [Grupo1] GO IF (EXISTS(SELECT NULL FROM sys.dm_resource_governor_resource_pools WHERE [name] = 'PoolA')) DROP RESOURCE POOL [PoolA] GO IF (OBJECT_ID('dbo.fncClassifica_ResourceGovernor') IS NOT NULL) DROP FUNCTION dbo.fncClassifica_ResourceGovernor GO ---------------------------------------------------------------------------------------------- -- Criação do Pool de Recursos ---------------------------------------------------------------------------------------------- CREATE RESOURCE POOL [PoolA] WITH ( MIN_CPU_PERCENT=0, MAX_CPU_PERCENT=20, CAP_CPU_PERCENT=20, MIN_MEMORY_PERCENT=0, MAX_MEMORY_PERCENT=30, AFFINITY SCHEDULER = AUTO, MIN_IOPS_PER_VOLUME=0, MAX_IOPS_PER_VOLUME=300 ) GO ---------------------------------------------------------------------------------------------- -- Criação do Workload Group ---------------------------------------------------------------------------------------------- CREATE WORKLOAD GROUP [Grupo1] WITH ( GROUP_MAX_REQUESTS=0, IMPORTANCE=LOW, REQUEST_MAX_CPU_TIME_SEC=2, REQUEST_MAX_MEMORY_GRANT_PERCENT=25, REQUEST_MEMORY_GRANT_TIMEOUT_SEC=0, MAX_DOP=1 ) USING [PoolA] GO ---------------------------------------------------------------------------------------------- -- Criação da função de classificação ---------------------------------------------------------------------------------------------- CREATE FUNCTION fncClassifica_ResourceGovernor() RETURNS SYSNAME WITH SCHEMABINDING AS BEGIN DECLARE @grp_name AS SYSNAME, @Usuario VARCHAR(200) = SUSER_NAME(), @Programa VARCHAR(200) = APP_NAME() IF (@Usuario |