Representar um cliente

Quando um aplicativo de usuário solicita dados de objetos no sistema por meio de um provedor WMI, a representação significa que o provedor apresenta credenciais que representam o nível de segurança do cliente em vez do provedor. A representação impede que um cliente obtenha acesso não autorizado a informações sobre o sistema.

As seguintes seções serão abordadas neste tópico:

O WMI normalmente é executado como um serviço administrativo em um alto nível de segurança, usando o contexto de segurança LocalServer. O uso de um serviço administrativo fornece ao WMI os meios para acessar informações privilegiadas. Ao chamar um provedor para obter informações, o WMI passa seu SID (identificador de segurança) para o provedor, permitindo que o provedor acesse informações no mesmo nível de alta segurança.

Durante o processo de inicialização do aplicativo WMI, o sistema operacional Windows fornece ao aplicativo WMI o contexto de segurança do usuário que iniciou o processo. Normalmente, o contexto de segurança do usuário é um nível de segurança inferior ao LocalServer, portanto, o usuário pode não ter permissão para acessar todas as informações disponíveis para o WMI. Quando o aplicativo de usuário solicita informações dinâmicas, o WMI passa o SID do usuário para o provedor correspondente. Se for gravado adequadamente, o provedor tentará acessar informações com o SID do usuário, em vez do SID do provedor.

Para que o provedor represente com êxito o aplicativo cliente, o aplicativo cliente e o provedor devem atender aos seguintes critérios:

Registrar um provedor para representação

O WMI passa apenas o SID de um aplicativo cliente para provedores que se registraram como provedores de representação. Habilitar um provedor para executar a representação requer que você modifique o processo de registro do provedor.

O procedimento a seguir descreve como registrar um provedor para representação. O procedimento pressupõe que você já entendeu o processo de registro. Para obter mais informações sobre o processo de registro, consulte Registrar um provedor.

Para registrar um provedor para representação

  1. Defina a propriedade ImpersonationLevel da classe __Win32Provider que representa seu provedor como 1.

    A propriedade ImpersonationLevel documenta se o provedor dá suporte à representação ou não. A definição de ImpersonationLevel como 0 indica que o provedor não representa o cliente e executa todas as operações solicitadas no mesmo contexto de usuário que o WMI. A definição de ImpersonationLevel como 1 indica que o provedor usa chamadas de representação para verificar operações executadas em nome do cliente.

  2. Defina a propriedade PerUserInitialization da mesma classe __Win32Provider como TRUE.

Observação

Se você registrar um provedor com __Win32Provider propriedade InitializeAsAdminFirst definida como TRUE, o provedor usará o token de segurança de thread no nível de administração somente durante a fase de inicialização. Embora uma chamada para CoImpersonateClient não falhe, o provedor usa o contexto de segurança do WMI e não do cliente.

 

O exemplo de código a seguir mostra como registrar um provedor para representação.

instance of __Win32Provider
{
    CLSID = "{FD4F53E0-65DC-11d1-AB64-00C04FD9159E}";
    ImpersonationLevel = 1;
    Name = "MS_NT_EVENTLOG_PROVIDER";
    PerUserInitialization = TRUE;
};

Definir níveis de representação dentro de um provedor

Se você registrar um provedor com a classe __Win32Provider propriedade ImpersonationLevel definida como 1, o WMI chamará seu provedor para representar vários clientes. Para manipular essas chamadas, use as funções COM CoImpersonateClient e CoRevertToSelf na sua implementação da interface IWbemServices.

A função CoImpersonateClient permite que um servidor represente o cliente que fez a chamada. Ao fazer uma chamada para CoImpersonateClient em sua implementação de IWbemServices, você permite que seu provedor defina o token de thread do provedor para corresponder ao token de thread do cliente e, portanto, representar o cliente. Se você não chamar CoImpersonateClient, seu provedor executará o código em um nível de segurança de administrador, criando assim uma possível vulnerabilidade de segurança. Se o provedor precisar agir temporariamente como administrador ou executar a verificação de acesso de forma manual, chame CoRevertToSelf.

Em contraste com CoImpersonateClient, CoRevertToSelf é uma função COM que manipula níveis de representação de thread. Nesse caso, CoRevertToSelf altera o nível de representação de volta para a configuração de representação original. Em geral, o provedor é inicialmente um administrador e alterna entre CoImpersonateClient e CoRevertToSelf, dependendo se ele está fazendo uma chamada que representa o chamador ou suas próprias chamadas. O provedor é responsável por fazer essas chamadas corretamente para não expor uma falha de segurança ao usuário final. Por exemplo, o provedor só deve chamar funções nativas do Windows dentro da sequência de código representada.

Observação

A finalidade de CoImpersonateClient e CoRevertToSelf é definir a segurança de um provedor. Se você determinar a falha na sua representação, deverá retornar um código de conclusão apropriado ao WMI por meio de IWbemObjectSink::SetStatus. Para obter mais informações, consulte Manipular mensagens de acesso negado em um provedor.

 

Manter níveis de segurança em um provedor

Os provedores não podem chamar CoImpersonateClient uma vez em uma implementação de IWbemServices e presumir que as credenciais de representação permanecem em vigor para a duração do provedor. Em vez disso, chame CoImpersonateClient várias vezes durante uma implementação para evitar que o WMI altere as credenciais.

A principal preocupação para definir a representação de um provedor é a reentrância. Nesse contexto, a reentrância é quando um provedor faz uma chamada ao WMI para obter informações e aguarda até que o WMI chame de volta para o provedor. Em essência, o thread de execução deixa o código do provedor, apenas para reentrada no código em uma data posterior. A reentrância faz parte do design do COM e geralmente não é uma preocupação. No entanto, quando o thread de execução entra no WMI, o thread assume os níveis de representação do WMI. Quando o thread retornar ao provedor, redefina os níveis de representação com outra chamada para CoImpersonateClient.

Para se proteger contra falhas de segurança em seu provedor, faça chamadas de reentrância no WMI somente ao representar o cliente. Ou seja, as chamadas para WMI devem ser feitas depois que você chamar CoImpersonateClient e antes de chamar CoRevertToSelf. Como CoRevertToSelf faz com que a representação seja definida para o nível do usuário em que o WMI está sendo executado, geralmente LocalSystem, as chamadas de reentrância para WMI depois de chamar CoRevertToSelf podem dar ao usuário e a todos os provedores chamados muito mais funcionalidades do que deveriam ter.

Observação

Caso você chame uma função do sistema ou outro método de interface, não há garantia de que o contexto de chamada seja mantido.

 

Manipular mensagens de acesso negado em um provedor

A maioria das mensagens de erro de Acesso Negado aparece quando um cliente solicita uma classe ou informações às quais não tem acesso. Se o provedor retornar uma mensagem de erro de Acesso Negado para WMI e o WMI passar isso para o cliente, o cliente poderá inferir que as informações existem. Em algumas situações, isso pode ser uma violação de segurança. Portanto, seu provedor não deve propagar a mensagem para o cliente. Em vez disso, o conjunto de classes que o provedor teria fornecido não deve ser exposto. Da mesma forma, um provedor de instância dinâmica deve chamar a fonte de dados subjacente para determinar como lidar com mensagens de Acesso Negado. O provedor é responsável por replicar essa filosofia no ambiente WMI. Para obter mais informações, consulte Relatar instâncias parciais e Relatar enumerações parciais.

Quando você determina como seu provedor deve manipular mensagens de Acesso Negado, grave e depure o código. Durante a depuração, geralmente é conveniente distinguir entre uma negação devido à baixa representação e uma negação devido a um erro em seu código. Você pode usar um teste simples em seu código para determinar a diferença. Para obter mais informações, consulte Depurar seu código de acesso negado.

Relatar instâncias parciais

Uma ocorrência comum de uma mensagem de Acesso Negado é quando o WMI não pode fornecer todas as informações para preencher uma instância. Por exemplo, um cliente pode ter autoridade para exibir um objeto de unidade de disco rígido, mas pode não ter autoridade para ver quanto espaço está disponível na própria unidade de disco rígido. Seu provedor deve determinar como manipular qualquer situação quando o provedor não puder preencher completamente uma instância com propriedades devido a uma violação de acesso.

O WMI não requer uma única resposta aos clientes que têm acesso parcial a uma instância. Em vez disso, a versão 1.x do WMI permite ao provedor uma das seguintes opções:

  • Reprovar toda a operação com WBEM_E_ACCESS_DENIED e não retornar nenhuma instância.

    Retorne um objeto de erro junto com WBEM_E_ACCESS_DENIED para descrever o motivo da negação.

  • Retornar todas as propriedades disponíveis e preencher as propriedades indisponíveis com NULL.

Observação

Certifique-se de que retornar WBEM_E_ACCESS_DENIED não cria uma falha de segurança em sua empresa.

 

Relatar enumerações parciais

Outra ocorrência comum de uma violação de acesso é quando o WMI não pode retornar toda uma enumeração. Por exemplo, um cliente pode ter acesso para exibir todos os objetos de computador de rede local, mas pode não ter acesso para exibir objetos de computador fora de seu domínio. Seu provedor deve determinar como manipular qualquer situação quando uma enumeração não pode ser concluída devido a uma violação de acesso.

Assim como acontece com um provedor de instância, o WMI não requer uma única resposta a uma enumeração parcial. Em vez disso, o WMI versão 1.x permite ao provedor uma das seguintes opções:

  • Retornar WBEM_S_NO_ERROR para todas as instâncias que o provedor pode acessar.

    Se você usar essa opção, o usuário não estará ciente de que algumas instâncias não estavam disponíveis. Vários provedores, como aqueles que usam linguagem SQL com segurança em nível de linha, retornam resultados parciais bem-sucedidos usando o nível de segurança do chamador para definir o conjunto de resultados.

  • Reprovar toda a operação com WBEM_E_ACCESS_DENIED e não retornar nenhuma instância.

    Opcionalmente, o provedor pode incluir um objeto de erro que descreve a situação para o cliente. Observe que alguns provedores podem acessar fontes de dados em série e podem não encontrar negações até a metade da enumeração.

  • Retornar todas as instâncias que podem ser acessadas, mas que também retornam o código de status nonerror WBEM_S_ACCESS_DENIED.

    O provedor deve observar a negação durante a enumeração e pode continuar fornecendo instâncias, terminando com o código de status nonerror. O provedor também pode optar por encerrar a enumeração na primeira negação. A justificativa para essa opção é que provedores diferentes têm paradigmas de recuperação diferentes. Um provedor pode já ter entregue instâncias antes de descobrir uma violação de acesso. Alguns provedores podem optar por continuar fornecendo outras instâncias e outros podem querer encerrar.

Devido à estrutura do COM, você não pode realizar marshal de nenhuma informação durante um erro, exceto para um objeto de erro. Portanto, você não pode retornar informações e um código de erro. Se você optar por retornar informações, use um código de status nonerror.

Depurar seu código de acesso negado

Alguns aplicativos podem usar níveis de representação inferiores a RPC_C_IMP_LEVEL_IMPERSONATE. Nesse caso, haverá falha na maioria das chamadas de representação feitas pelo provedor para o aplicativo cliente. Para projetar e implementar um provedor com êxito, você deve ter essa ideia em mente.

Por padrão, o único outro nível de representação que pode acessar um provedor é RPC_C_IMP_LEVEL_IDENTIFY. Nos casos em que um aplicativo cliente usa RPC_C_IMP_LEVEL_IDENTIFY, CoImpersonateClient não retorna um código de erro. Em vez disso, o provedor representa o cliente apenas para fins de identificação. Portanto, a maioria dos métodos do Windows chamados pelo provedor retornará uma mensagem de acesso negado. Isso é inofensivo na prática, pois os usuários não terão permissão para fazer nada inadequado. No entanto, pode ser útil durante o desenvolvimento do provedor para saber se o cliente foi realmente representado ou não.

O código requer as referências a seguir e as instruções #include para compilar corretamente.

#define _WIN32_DCOM
#include <iostream>
using namespace std;
#include <wbemidl.h>

O exemplo de código a seguir mostra como determinar se um provedor representou com êxito um aplicativo cliente.

DWORD dwImp = 0;
HANDLE hThreadTok;
DWORD dwBytesReturned;
BOOL bRes;

// You must call this before trying to open a thread token!
CoImpersonateClient();

bRes = OpenThreadToken(
    GetCurrentThread(),
    TOKEN_QUERY,
    TRUE,
    &hThreadTok
);

if (bRes == FALSE)
{
    printf("Unable to read thread token (%d)\n", GetLastError());
    return 0;
}

bRes = GetTokenInformation(
    hThreadTok,
    TokenImpersonationLevel, 
    &dwImp,
    sizeof(DWORD),
    &dwBytesReturned
);

if (!bRes)
{
    printf("Unable to read impersonation level\n");
    CloseHandle(hThreadTok);
    return 0;
}

switch (dwImp)
{
case SecurityAnonymous:
    printf("SecurityAnonymous\n");
    break;

case SecurityIdentification:
    printf("SecurityIdentification\n");
    break;

case SecurityImpersonation:
    printf("SecurityImpersonation\n");
    break;

case SecurityDelegation:
    printf("SecurityDelegation\n");
    break;

default:
    printf("Error. Unable to determine impersonation level\n");
    break;
}

CloseHandle(hThreadTok);

Desenvolver um provedor do WMI

Definir descritores de segurança de namespace

Como proteger seu provedor