Moniker d’élévation COM

Le moniker d’élévation COM permet aux applications qui s’exécutent sous contrôle de compte d’utilisateur (UAC) d’activer des classes COM avec des privilèges élevés. Pour plus d’informations, consultez Focus on Least Privilege.

Quand utiliser le moniker d’élévation

Le moniker d’élévation est utilisé pour activer une classe COM afin d’accomplir une fonction spécifique et limitée qui nécessite des privilèges élevés, comme la modification de la date et de l’heure système.

L’élévation nécessite la participation d’une classe COM et de son client. La classe COM doit être configurée pour prendre en charge l’élévation en annotant son entrée de Registre, comme décrit dans la section Exigences. Le client COM doit demander une élévation à l’aide du moniker d’élévation.

Le moniker d’élévation n’est pas destiné à fournir la compatibilité de l’application. Par exemple, si vous souhaitez exécuter une application COM héritée telle que WinWord en tant que serveur avec élévation de privilèges, vous devez configurer l’exécutable du client COM pour exiger une élévation, plutôt que d’activer la classe de l’application héritée avec le moniker d’élévation. Lorsque le client COM avec élévation de privilèges appelle CoCreateInstance à l’aide du CLSID de l’application héritée, l’état élevé du client est acheminé vers le processus serveur.

Toutes les fonctionnalités COM ne sont pas compatibles avec l’élévation. Les fonctionnalités qui ne fonctionneront pas sont les suivantes :

  • L’élévation ne circule pas d’un client vers un serveur COM distant. Si un client active un serveur COM distant avec le moniker d’élévation, le serveur ne sera pas élevé, même s’il prend en charge l’élévation.
  • Si une classe COM avec élévation de privilèges utilise l’emprunt d’identité pendant un appel COM, elle peut perdre ses privilèges élevés pendant l’emprunt d’identité.
  • Si un serveur COM avec élévation de privilèges inscrit une classe dans la table d’objets en cours d’exécution (ROT), la classe n’est pas disponible pour les clients non élevés.
  • Un processus élevé à l’aide du mécanisme UAC ne charge pas les classes par utilisateur pendant les activations COM. Pour les applications COM, cela signifie que les classes COM de l’application doivent être installées dans la ruche du Registre HKEY_LOCAL_MACHINE si l’application doit être utilisée à la fois par des comptes non privilégiés et privilégiés. Les classes COM de l’application doivent uniquement être installées dans le HKEY_USERS hive si l’application n’est jamais utilisée par des comptes privilégiés.
  • Le glisser-déplacer n’est pas autorisé entre les applications non élevées et les applications avec élévation de privilèges.

Spécifications

Pour utiliser le moniker d’élévation pour activer une classe COM, la classe doit être configurée pour s’exécuter en tant qu’utilisateur de lancement ou l’identité de l’application « Activer en tant qu’activateur ». Si la classe est configurée pour s’exécuter sous une autre identité, l’activation retourne l’erreur CO_E_RUNAS_VALUE_MUST_BE_AAA.

La classe doit également être annotée avec un nom d’affichage « convivial » compatible avec l’interface utilisateur multilingue (MUI). Cela nécessite l’entrée de Registre suivante :

HKEY_LOCAL_MACHINE\Software\Classes\CLSID
   {CLSID}
      LocalizedString = displayName

Si cette entrée est manquante, l’activation retourne l’erreur CO_E_MISSING_DISPLAYNAME. Si le fichier MUI est manquant, le code d’erreur de la fonction RegLoadMUIStringW est retourné.

Si vous le souhaitez, pour spécifier une icône d’application à afficher par l’interface utilisateur UAC, ajoutez la clé de Registre suivante :

HKEY_LOCAL_MACHINE\Software\Classes\CLSID
   {CLSID}
      Elevation
         IconReference = applicationIcon

IconReference utilise le même format que LocalizedString :

@pathtobinary,-resourcenumber

En outre, le composant COM doit être signé pour que l’icône s’affiche.

La classe COM doit également être annotée en tant que LUA-Enabled. Cela nécessite l’entrée de Registre suivante :

HKEY_LOCAL_MACHINE\Software\Classes\CLSID
   {CLSID}
      Elevation
         Enabled = 1

Si cette entrée est manquante, l’activation retourne l’erreur CO_E_ELEVATION_DISABLED.

Notez que ces entrées doivent exister dans la ruche HKEY_LOCAL_MACHINE, et non dans la ruche HKEY_CURRENT_USER ou HKEY_USERS. Cela empêche les utilisateurs d’élever les classes COM qu’ils n’avaient pas également les privilèges d’inscription.

Moniker d’élévation et interface utilisateur d’élévation

Si le client est déjà élevé, l’utilisation du moniker d’élévation n’entraîne pas l’affichage de l’interface utilisateur d’élévation.

Comment utiliser le moniker d’élévation

Le moniker d’élévation est un moniker COM standard, similaire aux monikers de session, de partition ou de file d’attente. Il dirige une demande d’activation vers un serveur spécifié avec le niveau d’élévation spécifié. Le CLSID à activer s’affiche dans la chaîne moniker.

Le moniker d’élévation prend en charge les jetons de niveau d’exécution suivants :

  1. Administrateur
  2. Le plus élevé

La syntaxe est la suivante :

Elevation:Administrator!new:{guid}
Elevation:Highest!new:{guid}

La syntaxe précédente utilise le moniker « new » pour renvoyer un instance de la classe COM spécifiée par guid. Notez que le moniker « new » utilise en interne l’interface IClassFactory pour obtenir un objet de classe, puis appelle IClassFactory::CreateInstance dessus.

Le moniker d’élévation peut également être utilisé pour obtenir un objet de classe, qui implémente IClassFactory. L’appelant appelle ensuite CreateInstance pour obtenir un objet instance. La syntaxe est la suivante :

Elevation:Administrator!clsid:{guid}

Exemple de code

L’exemple de code suivant montre comment utiliser le moniker d’élévation. Il suppose que vous avez déjà initialisé COM sur le thread actuel.

HRESULT CoCreateInstanceAsAdmin(HWND hwnd, REFCLSID rclsid, REFIID riid, __out void ** ppv)
{
    BIND_OPTS3 bo;
    WCHAR  wszCLSID[50];
    WCHAR  wszMonikerName[300];

    StringFromGUID2(rclsid, wszCLSID, sizeof(wszCLSID)/sizeof(wszCLSID[0])); 
    HRESULT hr = StringCchPrintf(wszMonikerName, sizeof(wszMonikerName)/sizeof(wszMonikerName[0]), L"Elevation:Administrator!new:%s", wszCLSID);
    if (FAILED(hr))
        return hr;
    memset(&bo, 0, sizeof(bo));
    bo.cbStruct = sizeof(bo);
    bo.hwnd = hwnd;
    bo.dwClassContext  = CLSCTX_LOCAL_SERVER;
    return CoGetObject(wszMonikerName, &bo, riid, ppv);
}

BIND_OPTS3 est nouveau dans Windows Vista. Il est dérivé de BIND_OPTS2.

Le seul ajout est un champ HWND , hwnd. Ce handle représente une fenêtre qui devient propriétaire de l’interface utilisateur d’élévation, le cas échéant.

Si hwnd a la valeur NULL, COM appelle GetActiveWindow pour rechercher un handle de fenêtre associé au thread actif. Ce cas peut se produire si le client est un script, qui ne peut pas remplir une structure BIND_OPTS3 . Dans ce cas, COM essaie d’utiliser la fenêtre associée au thread de script.

Élévation par-dessus l’épaule (OTS)

L’élévation ots (Over-the-shoulder) fait référence au scénario dans lequel un client exécute un serveur COM avec les informations d’identification d’un administrateur plutôt que les siennes. (Le terme « par-dessus l’épaule » signifie que l’administrateur veille sur l’épaule du client pendant que le client exécute le serveur.)

Ce scénario peut provoquer un problème pour les appels COM sur le serveur, car le serveur n’appelle peut-être pas CoInitializeSecurity de manière explicite (c’est-à-dire, par programmation) ou implicitement (autrement dit, déclarativement, à l’aide du Registre). Pour ces serveurs, COM calcule un descripteur de sécurité qui permet uniquement à SELF, SYSTEM et Builtin\Administrators d’effectuer des appels COM sur le serveur. Cet arrangement ne fonctionnera pas dans les scénarios OTS. Au lieu de cela, le serveur doit appeler CoInitializeSecurity, explicitement ou implicitement, et spécifier une liste de contrôle d’accès qui inclut le SID de groupe INTERACTIF et SYSTEM.

L’exemple de code suivant montre comment créer un descripteur de sécurité (SD) avec le SID de groupe INTERACTIVE.

BOOL GetAccessPermissionsForLUAServer(SECURITY_DESCRIPTOR **ppSD)
{
// Local call permissions to IU, SY
    LPWSTR lpszSDDL = L"O:BAG:BAD:(A;;0x3;;;IU)(A;;0x3;;;SY)";
    SECURITY_DESCRIPTOR *pSD;
    *ppSD = NULL;

    if (ConvertStringSecurityDescriptorToSecurityDescriptorW(lpszSDDL, SDDL_REVISION_1, (PSECURITY_DESCRIPTOR *)&pSD, NULL))
    {
        *ppSD = pSD;
        return TRUE;
    }

    return FALSE;
}

L’exemple de code suivant montre comment appeler Implicitement CoInitializeSecurity avec le SD de l’exemple de code précédent :

// hKey is the HKCR\AppID\{GUID} key
BOOL SetAccessPermissions(HKEY hkey, PSECURITY_DESCRIPTOR pSD)
{
    BOOL bResult = FALSE;
    DWORD dwLen = GetSecurityDescriptorLength(pSD);
    LONG lResult;
    lResult = RegSetValueExA(hkey, 
        "AccessPermission",
        0,
        REG_BINARY,
        (BYTE*)pSD,
        dwLen);
    if (lResult != ERROR_SUCCESS) goto done;
    bResult = TRUE;
done:
    return bResult;
}

L’exemple de code suivant montre comment appeler Explicitement CoInitializeSecurity avec le SD ci-dessus :

// Absolute SD values
PSECURITY_DESCRIPTOR pAbsSD = NULL;
DWORD AbsSdSize = 0;
PACL  pAbsAcl = NULL;
DWORD AbsAclSize = 0;
PACL  pAbsSacl = NULL;
DWORD AbsSaclSize = 0;
PSID  pAbsOwner = NULL;
DWORD AbsOwnerSize = 0;
PSID  pAbsGroup = NULL;
DWORD AbsGroupSize = 0;

MakeAbsoluteSD (
    pSD,
    pAbsSD,
    &AbsSdSize,
    pAbsAcl,
    &AbsAclSize,
    pAbsSacl,
    &AbsSaclSize,
    pAbsOwner,
    &AbsOwnerSize,
    pAbsGroup,
    &AbsGroupSize
);

if (ERROR_INSUFFICIENT_BUFFER == GetLastError())
{
    pAbsSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LMEM_FIXED, AbsSdSize);
    pAbsAcl = (PACL)LocalAlloc(LMEM_FIXED, AbsAclSize);
    pAbsSacl = (PACL)LocalAlloc(LMEM_FIXED, AbsSaclSize);
    pAbsOwner = (PSID)LocalAlloc(LMEM_FIXED, AbsOwnerSize);
    pAbsGroup = (PSID)LocalAlloc(LMEM_FIXED, AbsGroupSize);

    if ( ! (pAbsSD && pAbsAcl && pAbsSacl && pAbsOwner && pAbsGroup))
    {
        hr = E_OUTOFMEMORY;
        goto Cleanup;
    }
    if ( ! MakeAbsoluteSD(
        pSD,
        pAbsSD,
        &AbsSdSize,
        pAbsAcl,
        &AbsAclSize,
        pAbsSacl,
        &AbsSaclSize,
        pAbsOwner,
        &AbsOwnerSize,
        pAbsGroup,
        &AbsGroupSize
        ))
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }
}
else
{
    hr = HRESULT_FROM_WIN32(GetLastError());
    goto Cleanup;
}

// Call CoInitializeSecurity .

Autorisations COM et étiquettes d’accès obligatoire

Windows Vista introduit la notion d’étiquettes d’accès obligatoires dans les descripteurs de sécurité. L’étiquette détermine si les clients peuvent obtenir un accès d’exécution à un objet COM. L’étiquette est spécifiée dans la partie liste de contrôle d’accès système (SACL) du descripteur de sécurité. Dans Windows Vista, COM prend en charge l’étiquette SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP. Les listes SACL dans les autorisations COM sont ignorées sur les systèmes d’exploitation antérieurs à Windows Vista.

À partir de Windows Vista, dcomcnfg.exe ne prend pas en charge la modification du niveau d’intégrité (IL) dans les autorisations COM. Il doit être défini par programmation.

L’exemple de code suivant montre comment créer un descripteur de sécurité COM avec une étiquette qui autorise les demandes de lancement/activation de tous les clients LOW IL. Notez que les étiquettes sont valides pour les autorisations Lancement/activation et Appel. Ainsi, il est possible d’écrire un serveur COM qui interdit le lancement, l’activation ou les appels de clients avec un certain IL. Pour plus d’informations sur les niveaux d’intégrité, consultez la section « Présentation du mécanisme d’intégrité de Windows Vista » dans Comprendre et travailler en mode protégé Explorer Internet.

BOOL GetLaunchActPermissionsWithIL (SECURITY_DESCRIPTOR **ppSD)
{
// Allow World Local Launch/Activation permissions. Label the SD for LOW IL Execute UP
    LPWSTR lpszSDDL = L"O:BAG:BAD:(A;;0xb;;;WD)S:(ML;;NX;;;LW)";
    if (ConvertStringSecurityDescriptorToSecurityDescriptorW(lpszSDDL, SDDL_REVISION_1, (PSECURITY_DESCRIPTOR *)&pSD, NULL))
    {
        *ppSD = pSD;
        return TRUE;
    }
}

BOOL SetLaunchActPermissions(HKEY hkey, PSECURITY_DESCRIPTOR pSD)
{
    
    BOOL bResult = FALSE;
    DWORD dwLen = GetSecurityDescriptorLength(pSD);
    LONG lResult;
    lResult = RegSetValueExA(hkey, 
        "LaunchPermission",
        0,
        REG_BINARY,
        (BYTE*)pSD,
        dwLen);
    if (lResult != ERROR_SUCCESS) goto done;
    bResult = TRUE;
done:
    return bResult;
};

CoCreateInstance et niveaux d’intégrité

Le comportement de CoCreateInstance a changé dans Windows Vista pour empêcher les clients Low IL de se lier aux serveurs COM par défaut. Le serveur doit autoriser explicitement ces liaisons en spécifiant la liste SACL. Les modifications apportées à CoCreateInstance sont les suivantes :

  1. Lors du lancement d’un processus de serveur COM, l’IL dans le jeton de processus serveur est défini sur l’il du jeton de client ou de serveur, selon la valeur la plus faible.
  2. Par défaut, COM empêche les clients Low IL de se lier à des instances en cours d’exécution de tous les serveurs COM. Pour autoriser la liaison, le descripteur de sécurité Launch/Activation d’un serveur COM doit contenir une liste SACL qui spécifie l’étiquette Low IL (voir la section précédente pour l’exemple de code pour créer un tel descripteur de sécurité).

Serveurs avec élévation de privilèges et inscriptions ROT

Si un serveur COM souhaite s’inscrire dans la table d’objets en cours d’exécution (ROT) et autoriser un client à accéder à l’inscription, il doit utiliser l’indicateur ROTFLAGS_ALLOWANYCLIENT. Un serveur COM « Activer en tant qu’activateur » ne peut pas spécifier ROTFLAGS_ALLOWANYCLIENT, car le gestionnaire de contrôle de service DCOM (DCOMSCM) applique une case activée d’usurpation à cet indicateur. Par conséquent, dans Windows Vista, COM ajoute la prise en charge d’une nouvelle entrée de Registre qui permet au serveur de stipuler que ses inscriptions ROT sont mises à la disposition de n’importe quel client :

HKEY_LOCAL_MACHINE\Software\Classes\AppID
   {APPID}
      ROTFlags

La seule valeur valide pour cette entrée REG_DWORD est :

ROTREGFLAGS_ALLOWANYCLIENT 0x1

L’entrée doit exister dans la ruche HKEY_LOCAL_MACHINE .

Cette entrée fournit un serveur « Activer en tant qu’activateur » avec les mêmes fonctionnalités que ROTFLAGS_ALLOWANYCLIENT fournit pour un serveur RunAs.

Sécurité dans COM

Présentation et utilisation d’Internet Explorer en mode protégé