Emprunt d’identité d’un client

Lorsqu’une application utilisateur demande des données à des objets sur le système via un fournisseur WMI, l’emprunt d’identité signifie que le fournisseur présente des informations d’identification qui représentent le niveau de sécurité du client plutôt que celui du fournisseur. L’emprunt d’identité empêche un client d’obtenir un accès non autorisé aux informations sur le système.

Les sections suivantes sont abordées dans cette rubrique :

WMI s’exécute généralement en tant que service d’administration à un niveau de sécurité élevé, à l’aide du contexte de sécurité LocalServer. L’utilisation d’un service d’administration permet à WMI d’accéder aux informations privilégiées. Lors de l’appel d’un fournisseur pour obtenir des informations, WMI transmet son identificateur de sécurité (SID) au fournisseur, ce qui lui permet d’accéder aux informations au même niveau de sécurité élevé.

Pendant le processus de lancement de l’application WMI, le système d’exploitation Windows donne à l’application WMI le contexte de sécurité de l’utilisateur qui a commencé le processus. Le contexte de sécurité de l’utilisateur étant généralement inférieur à celui de LocalServer, l’utilisateur peut ne pas avoir l’autorisation d’accéder à toutes les informations disponibles pour WMI. Lorsque l’application utilisateur demande des informations dynamiques, WMI transmet le SID de l’utilisateur au fournisseur correspondant. S’il est écrit de manière appropriée, le fournisseur tente d’accéder aux informations avec le SID de l’utilisateur plutôt qu’avec le SID du fournisseur.

Pour que le fournisseur emprunte correctement l’identité de l’application cliente, l’application cliente et le fournisseur doivent répondre aux critères suivants :

Inscription d’un fournisseur pour l’emprunt d’identité

WMI transmet uniquement le SID d’une application cliente aux fournisseurs inscrits en tant que fournisseurs d’emprunt d’identité. Pour permettre à un fournisseur d’effectuer l’emprunt d’identité, vous devez modifier le processus d’inscription du fournisseur.

La procédure suivante décrit comment inscrire un fournisseur pour l’emprunt d’identité. La procédure suppose que vous comprenez déjà le processus d’inscription. Pour plus d’informations sur le processus d’inscription, consultez Inscription d’un fournisseur.

Pour inscrire un fournisseur pour l’emprunt d’identité

  1. Définissez la propriété ImpersonationLevel de la classe __Win32Provider qui représente votre fournisseur sur 1.

    La propriété ImpersonationLevel indique si le fournisseur prend en charge l’emprunt d’identité ou non. Définir ImpersonationLevel sur 0 indique que le fournisseur n’emprunte pas l’identité du client et effectue toutes les opérations demandées dans le même contexte utilisateur que WMI. La définition d’ImpersonationLevel sur 1 indique que le fournisseur utilise des appels d’emprunt d’identité pour vérifier les opérations effectuées au nom du client.

  2. Définissez la propriété PerUserInitialization de la même classe __Win32Provider sur TRUE.

Notes

Si vous inscrivez un fournisseur avec la propriété __Win32ProviderInitializeAsAdminFirst définie sur TRUE, le fournisseur utilise le jeton de sécurité du thread au niveau de l’administration uniquement pendant la phase d’initialisation. Bien qu’un appel à CoImpersonateClient n’échoue pas, le fournisseur utilise le contexte de sécurité de WMI et non du client.

 

L’exemple de code suivant montre comment inscrire un fournisseur dans NetworkServiceHost.

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

Définition des niveaux d’emprunt d’identité au sein d’un fournisseur

Si vous inscrivez un fournisseur avec la propriété de classe __Win32ProviderImpersonationLevel définie sur 1, WMI appelle votre fournisseur pour emprunter l’identité de différents clients. Pour gérer ces appels, utilisez les fonctions COM CoImpersonateClient et CoRevertToSelf dans votre implémentation de l’interface IWbemServices .

La fonction CoImpersonateClient permet à un serveur d’emprunter l’identité du client qui a effectué l’appel. En plaçant un appel à CoImpersonateClient dans votre implémentation d’IWbemServices, vous autorisez votre fournisseur à définir le jeton de thread du fournisseur pour qu’il corresponde au jeton de thread du client, et ainsi emprunter l’identité du client. Si vous n’appelez pas CoImpersonateClient, votre fournisseur exécute du code à un niveau de sécurité administrateur, créant ainsi une vulnérabilité de sécurité potentielle. Si votre fournisseur doit temporairement agir en tant qu’administrateur ou effectuer la vérification d’accès manuellement, appelez CoRevertToSelf.

Contrairement à CoImpersonateClient, CoRevertToSelf est une fonction COM qui gère les niveaux d’emprunt d’identité de thread. Dans ce cas, CoRevertToSelf remplace le niveau d’emprunt d’identité par le paramètre d’emprunt d’identité d’origine. En général, le fournisseur est initialement un administrateur et alterne entre CoImpersonateClient et CoRevertToSelf selon qu’il effectue un appel qui représente l’appelant ou ses propres appels. Il incombe au fournisseur de passer correctement ces appels afin de ne pas exposer un trou de sécurité à l’utilisateur final. Par exemple, le fournisseur doit appeler uniquement les fonctions Windows natives au sein de la séquence de code emprunt d’identité.

Notes

L’objectif de CoImpersonateClient et CoRevertToSelf est de définir la sécurité d’un fournisseur. Si vous déterminez que votre emprunt d’identité a échoué, vous devez retourner un code d’achèvement approprié à WMI via IWbemObjectSink::SetStatus. Pour plus d’informations, consultez Gestion des messages d’accès refusés dans un fournisseur.

 

Maintenance des niveaux de sécurité dans un fournisseur

Les fournisseurs ne peuvent pas appeler CoImpersonateClient une seule fois dans une implémentation d’IWbemServices et supposent que les informations d’identification d’emprunt d’identité restent en place pendant la durée du fournisseur. Au lieu de cela, appelez CoImpersonateClient plusieurs fois au cours d’une implémentation pour empêcher WMI de modifier les informations d’identification.

La principale préoccupation de la définition de l’emprunt d’identité pour un fournisseur est la réentrance. Dans ce contexte, la réentrance se produit lorsqu’un fournisseur appelle WMI pour obtenir des informations et attend que WMI rappelle le fournisseur. En substance, le thread d’exécution quitte le code du fournisseur, uniquement pour réentrere le code à une date ultérieure. La réentrée fait partie de la conception de COM et n’est généralement pas un problème. Toutefois, lorsque le thread d’exécution entre dans WMI, le thread prend les niveaux d’emprunt d’identité de WMI. Lorsque le thread retourne au fournisseur, vous devez réinitialiser les niveaux d’emprunt d’identité avec un autre appel à CoImpersonateClient.

Pour vous protéger contre les failles de sécurité de votre fournisseur, vous devez effectuer des appels entrants dans WMI uniquement lors de l’emprunt d’identité du client. Autrement dit, les appels à WMI doivent être effectués après avoir appelé CoImpersonateClient et avant d’appeler CoRevertToSelf. Étant donné que CoRevertToSelf entraîne la définition de l’emprunt d’identité au niveau de l’utilisateur que WMI est en cours d’exécution, généralement LocalSystem, les appels réentrants à WMI après l’appel de CoRevertToSelf peuvent donner à l’utilisateur, et à tous les fournisseurs appelés, beaucoup plus de fonctionnalités qu’ils ne devraient avoir.

Notes

Si vous appelez une fonction système ou une autre méthode d’interface, la maintenance du contexte d’appel n’est pas garantie.

 

Gestion des messages d’accès refusé dans un fournisseur

La plupart des messages d’erreur Accès refusé s’affichent lorsqu’un client demande une classe ou des informations auxquelles il n’a pas accès. Si le fournisseur retourne un message d’erreur Accès refusé à WMI et que WMI le transmet au client, le client peut déduire que les informations existent. Dans certaines situations, il peut s’agir d’une violation de la sécurité. Par conséquent, votre fournisseur ne doit pas propager le message au client. Au lieu de cela, l’ensemble de classes que le fournisseur aurait fourni ne doit pas être exposé. De même, un fournisseur d’instance dynamique doit appeler la source de données sous-jacente pour déterminer comment traiter les messages Accès refusé. Il incombe au fournisseur de répliquer cette philosophie dans l’environnement WMI. Pour plus d’informations, consultez Création de rapports d’instances partielles et rapports d’énumérations partielles.

Lorsque vous déterminez comment votre fournisseur doit gérer les messages Accès refusé, vous devez écrire et déboguer votre code. Lors du débogage, il est souvent pratique de faire la distinction entre un déni dû à une faible emprunt d’identité et un déni dû à une erreur dans votre code. Vous pouvez utiliser un test simple dans votre code pour déterminer la différence. Pour plus d’informations, consultez Débogage de votre code d’accès refusé.

Création de rapports d’instances partielles

Une occurrence courante d’un message Accès refusé est lorsque WMI ne peut pas fournir toutes les informations pour remplir une instance. Par exemple, un client peut avoir l’autorité d’afficher un objet de disque dur, mais peut ne pas être autorisé à voir l’espace disponible sur le disque dur lui-même. Votre fournisseur doit déterminer comment gérer toute situation où le fournisseur ne peut pas remplir complètement une instance avec des propriétés en raison d’une violation d’accès.

WMI ne nécessite pas une seule réponse aux clients qui disposent d’un accès partiel à une instance. Au lieu de cela, WMI version 1.x autorise l’une des options suivantes au fournisseur :

  • Échec de l’opération entière avec WBEM_E_ACCESS_DENIED et ne retourne aucune instance.

    Retourne un objet d’erreur avec WBEM_E_ACCESS_DENIEDpour décrire la raison du refus.

  • Retourne toutes les propriétés disponibles et renseignez les propriétés indisponibles avec NULL.

Notes

Assurez-vous que le retour de WBEM_E_ACCESS_DENIED ne crée pas de faille de sécurité dans votre entreprise.

 

Création de rapports d’énumérations partielles

Une autre occurrence courante d’une violation d’accès est lorsque WMI ne peut pas retourner toute une énumération. Par exemple, un client peut avoir accès à l’affichage de tous les objets d’ordinateur du réseau local, mais peut ne pas avoir accès à l’affichage des objets d’ordinateur en dehors de son domaine. Votre fournisseur doit déterminer comment gérer toute situation où une énumération ne peut pas être terminée en raison d’une violation d’accès.

Comme avec un fournisseur d’instance, WMI ne nécessite pas une seule réponse à une énumération partielle. Au lieu de cela, WMI version 1.x autorise un fournisseur l’une des options suivantes :

  • Retournez WBEM_S_NO_ERROR pour toutes les instances auxquelles le fournisseur peut accéder.

    Si vous utilisez cette option, l’utilisateur ne sait pas que certaines instances n’étaient pas disponibles. Un certain nombre de fournisseurs, tels que ceux qui utilisent langage SQL (SQL) avec une sécurité au niveau des lignes, retournent des résultats partiels réussis à l’aide du niveau de sécurité de l’appelant pour définir le jeu de résultats.

  • Échec de l’opération entière avec WBEM_E_ACCESS_DENIED et ne retourne aucune instance.

    Le fournisseur peut éventuellement inclure un objet d’erreur qui décrit la situation au client. Notez que certains fournisseurs peuvent accéder aux sources de données en série et ne rencontrer de refus qu’à une partie de l’énumération.

  • Retourne toutes les instances accessibles, mais retourne également le code d’état non-terroriste WBEM_S_ACCESS_DENIED.

    Le fournisseur doit noter le refus pendant l’énumération et peut continuer à fournir des instances, se terminant avec le code d’état non-terroriste. Le fournisseur peut également choisir d’arrêter l’énumération au premier refus. La justification de cette option est que différents fournisseurs ont des paradigmes de récupération différents. Un fournisseur a peut-être déjà fourni des instances avant de découvrir une violation d’accès. Certains fournisseurs peuvent choisir de continuer à fournir d’autres instances et d’autres peuvent souhaiter se terminer.

En raison de la structure de COM, vous ne pouvez pas marshaler les informations pendant une erreur, à l’exception d’un objet d’erreur. Par conséquent, vous ne pouvez pas retourner à la fois des informations et un code d’erreur. Si vous choisissez de retourner des informations, vous devez utiliser un code d’état non terroriste à la place.

Débogage de votre code d’accès refusé

Certaines applications peuvent utiliser des niveaux d’emprunt d’identité inférieurs à RPC_C_IMP_LEVEL_IMPERSONATE. Dans ce cas, la plupart des appels d’emprunt d’identité effectués par le fournisseur pour l’application cliente échouent. Pour concevoir et implémenter correctement un fournisseur, vous devez garder cette idée à l’esprit.

Par défaut, le seul autre niveau d’emprunt d’identité qui peut accéder à un fournisseur est RPC_C_IMP_LEVEL_IDENTIFY. Dans les cas où une application cliente utilise RPC_C_IMP_LEVEL_IDENTIFY, CoImpersonateClient ne retourne pas de code d’erreur. Au lieu de cela, le fournisseur emprunte l’identité du client à des fins d’identification uniquement. Par conséquent, la plupart des méthodes Windows appelées par le fournisseur retournent un message d’accès refusé. Cela est inoffensif dans la pratique, car les utilisateurs ne seront pas autorisés à faire quelque chose d’inapproprié. Toutefois, pendant le développement du fournisseur, il peut être utile de savoir si le client était réellement usurpé d’identité ou non.

Le code nécessite la compilation correcte des références et des instructions #include suivantes.

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

L’exemple de code suivant montre comment déterminer si un fournisseur a réussi à emprunter l’identité d’une application 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);

Développement d’un fournisseur WMI

Définition des descripteurs de sécurité d’espace de noms

Sécurisation de votre fournisseur