Security-Transparent Code

Uma das grandes inovações que o .NET trouxe é a capacidade de conceder ou negar acesso à uma determinada aplicação (Assembly), independentemente do usuário que está acessando-a, sendo ou não um “Administrador” da máquina onde a aplicação está sendo executada. Esse novo conceito, presente desde a primeira versão do .NET Framework, é chamada de Code Access Security – CAS.

Esse recurso é extremamente rico em detalhes, onde podemos customizar quais direitos a aplicação terá baseando-se em condições que são avaliadas durante a execução da mesma.  Para compor o CAS, temos vários objetos, tais como: Permissions, Permissions Sets, Security Levels, Membership Conditions, Demands, etc. Ao mesmo tempo que isso é extremamente poderoso, a sua configuração torna-se complexa demais, principalmente quando você não tem um conhecimento intermediário sobre o seu funcionamento.

Um dos principais cenários onde precisamos fazer o uso destas funcionalidades, é quando estamos criando libraries (DLLs) para serem consumidas em ambientes parcialmente confiáveis, ou seja, você cria uma DLL para encapsular o acesso ao banco de dados, sistema de arquivos, etc., e quer consumí-la em uma aplicação que não está sendo executada em “FullTrust” (o que muitas vezes acontece). Nesses casos, se gasta mais tempo testando e configurando as políticas com seus respectivos níveis de segurança, do que na tarefa que a DLL irá executar.

A partir do .NET Framework 2.0, a Microsoft trouxe um recurso chamado de Security-Transparent Code. Essa funcionalidade facilita a escrita e configuração de DLLs que serão consumidas por aplicações que rodam em ambientes parcialmente confiáveis. A ideia desta técnica é você refatorar o código (seja em assemblies, classes ou métodos) em “código transparente” e “código crítico”, com o propósito de separar o código que roda como parte da aplicação do código que roda como parte da infraestrutura, criando uma espécie de barreira entre essas duas seções.

O “código transparente” não fará nada (do ponto de vista da segurança) além do que lhe foi concedido, ou seja, não acessará nenhum tipo de código que exija a elevação dos privilégios atuais. Ao precisar executar uma tarefa que exige um nível de segurança mais elevado, entre em cena o “código crítico”, que rodará em “FullTrust”. Neste caso, o “código transparente” irá invocar algum membro do “código crítico”, que possuirá todos os privilégios necessários, independentemente de quais o cliente possua, evitando elevar os privilégios do “código transparente”.

Os códigos marcados como “transparente” terão algumas restrições, tais como: elevar os privilégios (Asserts) que lhe foram concedidos; efetuar Link Demands (tudo será transformado em “full demand”) e qualquer código unsafe que ele eventualmente execute em “código transparente”, também efetuará uma “full demand”.

Observação: A diferença entre Link Demand e Demand (ou Full Demand) é que a primeira ocorre somente em tempo de compilação (JIT) e apenas irá verificar se o chamador imediato tem a permissão requerida. Já a segunda opção, Demand, executará a stack walk, ou seja, irá percorrer todos os elementos da stack, verificando se todos eles possuem a permissão solicitada.

Depois desta reestruturação (física ou virtual), a CLR irá assegurar (em tempo de execução) que o “código transparente” não poderá efetuar qualquer elevação de privilégios, garantindo que tudo o que o ele tentar executar, exigirá que toda a call stack tenha a respectiva permissão. E, para customizar a comportamento desta funcionalidade, a Microsoft disponibiliza alguns atributos:

  • SecurityTransparentAttribute: Somente permitido em nível de Assembly, diz ao runtime que ele será “transparente”, ou seja, não poderá executar qualquer ação que exija a elevação de privilégios. É importante dizer que “código transparente” não pode invocar “código crítico”.
  • SecurityCriticalAttribute: Quando utilizado em nível de Assembly, indica que ele pode ter “código crítico”. Isso vai depender da opção que você define ao utilizar o enumerador SecurityCriticalScope. A opção Everything indica que todo o código será considerado “código crítico”, enquanto a opção Explicit, determina que somente o membro onde o atributo está sendo aplicado será considerado como “código crítico”.
  • SecurityTreatAsSafeAttribute: Indica que o membro onde este atributo está sendo aplicado, pode ser acessado por outros membros que estão marcados com os atributos SecurityTransparentAttribute ou AllowPartiallyTrustedCallersAttribute. Não há nenhuma validação que evite a compilação quando você aplica este atributo em métodos públicos, e se você fizer isso, estes membros podem ser acessados através do “código transparente” tendo ter possíveis vulnerabilidades.

No exemplo abaixo, estamos definindo o atributo SecurityCriticalAttribute em nível de Assembly, dizendo que ele poderá conter “código crítico”. Com isso, somos obrigados a determinar qual método será “crítico”, utilizando o atributo SecurityCriticalAttribute ou o SecurityTreatAsSafeAttribute. Neste caso, optamos pela segunda opção, pois desejarmos que o método ReadFile possa ser invocado a partir de membro internos bem como externos, e com a configuração abaixo, será considerado “transparente”, e sem este atributo não seria possível.

[assembly: SecurityCritical]

static void Main(string[] args)
{
    Console.WriteLine(ReadFile());
}

[SecurityCritical]
[SecurityTreatAsSafeAttribute] 
private static string ReadFile(string filename)
{
    new FileIOPermission(FileIOPermissionAccess.Read, filename).Assert();
    //Ler o arquivo
}