Token de securité en Win32

Développement système natif en c/c++ avec win32 ...

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 46
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Token de securité en Win32

Post by Hydraxx »

Salut, :D

aujourd’hui on va voir un des mécanismes les plus importants de Windows : les tokens de sécurité.

C’est un sujet central, parce qu’en pratique énormément de comportements qui paraissent “bizarres” en Win32 deviennent immédiatement logiques dès qu’on comprend le token. Pourquoi OpenProcess marche sur une machine mais pas sur une autre. Pourquoi CreateProcessAsUserW échoue. Pourquoi un processus administrateur n’a pas toujours tous les droits. Pourquoi un thread peut accéder à une ressource alors que le processus qui l’héberge n’aurait pas pu. Dans énormément de cas, la réponse se trouve dans le contexte de sécurité, et donc dans le token.

Pourquoi les tokens sont fondamentaux

Comprendre les tokens permet de mieux voir :
  • comment Windows représente l’identité de sécurité d’un processus ou d’un thread
  • comment sont décidés les droits réels au moment d’un accès
  • pourquoi certains privilèges doivent etre activés explicitement
  • comment fonctionne l’impersonation
  • comment lancer un processus sous une autre identité
  • comment UAC, intégrité et privilèges interagissent
  • pourquoi “etre admin” ne veut pas dire “tout peut etre fait automatiquement”
Autrement dit, le token n’est pas un détail de l’API. C’est l’un des centres réels de la sécurité Windows.

Ce qu’est réellement un token

Un token de sécurité est un objet noyau qui représente une identité de sécurité et un ensemble d’attributs associés. Quand Windows doit décider si du code a le droit d’effectuer une action, il ne se base pas sur le nom du programme, son icône, ou le dossier dans lequel il se trouve. Il se base sur le token utilisé pour l’opération.

Un token contient notamment :
  • l’identité de l’utilisateur
  • les groupes auxquels cet utilisateur appartient
  • les privilèges présents
  • l’état activé ou non de certains privilèges
  • le niveau d’intégrité
  • le type de token
  • des informations d’élévation
  • le session id
  • des restrictions éventuelles
  • le propriétaire par défaut et le DACL par défaut
La vraie question à garder en tete est la suivante :

Code: Select all

Est-ce que ce contexte de sécurité a le droit d’effectuer cette action ?
La réponse dépend du token et de l’objet visé.

A quoi sert concrètement un token

Windows ne valide pas les accès à partir du “nom du programme”. Il valide les accès en comparant le contexte de sécurité du demandeur avec le descripteur de sécurité de la ressource ciblée.

Cela concerne par exemple :
  • les fichiers
  • les processus
  • les threads
  • les services
  • les clés de registre
  • les objets noyau
  • les pipes nommés
  • les sections mémoire
  • les stations de fenêtre et desktops
Autrement dit, chaque fois qu’un accès sensible doit etre autorisé ou refusé, le token est impliqué d’une manière ou d’une autre.

Processus, thread et ordre de priorité du contexte de sécurité

Un processus possède normalement un token primaire. C’est le token principal associé à son identité de sécurité.

Un thread, lui, peut éventuellement posséder un token d’impersonation. Dans ce cas, pour certaines opérations d’accès, c’est ce token de thread qui est utilisé à la place du token primaire du processus.

La logique générale est la suivante :
  • si le thread impersonne, le système utilise le token du thread
  • sinon, le système retombe sur le token primaire du processus
Cette idée est très importante. Elle explique pourquoi un service peut, via un thread, agir temporairement sous l’identité d’un client alors que le service lui-meme tourne sous une autre identité.

Token primaire et token d’impersonation

Il faut bien distinguer les deux grandes catégories.

Le token primaire :
  • est celui qui représente l’identité de base d’un processus
  • sert notamment à la création d’un nouveau processus
  • est le token “normal” attaché au processus
Le token d’impersonation :
  • est attaché à un thread
  • sert à accéder à des ressources au nom d’une autre identité
  • peut représenter différents niveaux d’impersonation
Les niveaux d’impersonation importants sont portés par le type SECURITY_IMPERSONATION_LEVEL :
  • SecurityAnonymous
  • SecurityIdentification
  • SecurityImpersonation
  • SecurityDelegation
Ce point est fondamental pour comprendre jusqu’où une identité impersonée peut réellement agir.

Ouvrir le token d’un processus

Pour manipuler un token, il faut d’abord obtenir un handle vers lui. L’API la plus classique est OpenProcessToken.

Exemple :

Code: Select all

HANDLE hToken = NULL;

if (!OpenProcessToken(
    GetCurrentProcess(),
    TOKEN_QUERY,
    &hToken))
{
    return 1;
}
Le handle obtenu est un vrai handle noyau, donc il faut le fermer avec CloseHandle quand on a terminé.

Exemple :

Code: Select all

CloseHandle(hToken);
Pour un thread, il existe aussi :
  • OpenThreadToken
  • SetThreadToken
  • GetCurrentThread
et pour le processus courant on utilise souvent :
  • GetCurrentProcess
  • OpenProcessToken
Droits d’accès sur un token

Comme pour les processus, les threads ou les fichiers, l’ouverture d’un token dépend des droits demandés.

Parmi les droits importants à connaitre :
  • TOKEN_QUERY
  • TOKEN_QUERY_SOURCE
  • TOKEN_ADJUST_PRIVILEGES
  • TOKEN_ADJUST_GROUPS
  • TOKEN_ADJUST_DEFAULT
  • TOKEN_ADJUST_SESSIONID
  • TOKEN_DUPLICATE
  • TOKEN_IMPERSONATE
  • TOKEN_ASSIGN_PRIMARY
  • TOKEN_ALL_ACCESS
Il faut éviter de demander TOKEN_ALL_ACCESS sans raison. Comme souvent en sécurité Windows, mieux vaut demander exactement ce dont on a besoin.

Lire les informations d’un token avec GetTokenInformation

L’API principale pour interroger un token est :
  • GetTokenInformation
Exemple de schéma classique :

Code: Select all

DWORD retSize = 0;
GetTokenInformation(hToken, TokenUser, NULL, 0, &retSize);

BYTE* buffer = new BYTE[retSize];
if (!buffer)
    return 1;

if (!GetTokenInformation(
    hToken,
    TokenUser,
    buffer,
    retSize,
    &retSize))
{
    delete[] buffer;
    return 1;
}

/* exploitation du buffer */

delete[] buffer;
Le modèle est important à retenir : beaucoup de classes d’information nécessitent un premier appel pour récupérer la taille nécessaire, puis un second pour obtenir réellement les données.

Les classes TOKEN_INFORMATION_CLASS à connaitre

Le deuxième paramètre de GetTokenInformation est de type TOKEN_INFORMATION_CLASS. Il détermine quel type d’information on veut obtenir.

Parmi les classes les plus utiles à connaitre :
  • TokenUser
  • TokenGroups
  • TokenPrivileges
  • TokenOwner
  • TokenPrimaryGroup
  • TokenDefaultDacl
  • TokenSource
  • TokenType
  • TokenImpersonationLevel
  • TokenStatistics
  • TokenSessionId
  • TokenIntegrityLevel
  • TokenElevationType
  • TokenElevation
  • TokenLinkedToken
  • TokenRestrictedSids
  • TokenMandatoryPolicy
  • TokenOrigin
Rien qu’avec cette liste, on couvre déjà une grosse partie des besoins réels.

Les structures importantes du monde des tokens

Pour lire ou manipuler correctement un token, il faut reconnaitre plusieurs structures Win32 essentielles.

Les plus importantes à connaitre au début sont :
  • TOKEN_USER
  • TOKEN_GROUPS
  • TOKEN_PRIVILEGES
  • TOKEN_OWNER
  • TOKEN_PRIMARY_GROUP
  • TOKEN_DEFAULT_DACL
  • TOKEN_STATISTICS
  • TOKEN_ELEVATION
  • TOKEN_MANDATORY_LABEL
  • SID_AND_ATTRIBUTES
  • LUID
  • LUID_AND_ATTRIBUTES
  • ACL
  • SID
Ces structures apparaissent partout dès qu’on commence à faire de la sécurité Windows sérieusement.

L’utilisateur et les groupes

Quand on demande TokenUser, on reçoit une structure TOKEN_USER, qui contient essentiellement un SID_AND_ATTRIBUTES. Le SID représente l’identité de sécurité de l’utilisateur.

Quand on demande TokenGroups, on reçoit une structure TOKEN_GROUPS contenant une liste de SID_AND_ATTRIBUTES.

Le type SID_AND_ATTRIBUTES est important, car il combine :
  • un SID
  • des attributs indiquant le rôle ou l’état de cette identité
Pour manipuler les SID, plusieurs API sont utiles :
  • GetLengthSid
  • CopySid
  • EqualSid
  • IsValidSid
  • AllocateAndInitializeSid
  • FreeSid
  • ConvertSidToStringSidW
  • ConvertStringSidToSidW
  • LookupAccountSidW
ConvertSidToStringSidW est particulièrement utile en debug, car il permet d’obtenir une forme lisible du SID.

Les privilèges

Un privilège n’est pas la meme chose qu’un droit d’accès sur un objet. C’est une capacité spéciale reconnue par le système pour certaines opérations sensibles.

Exemples de privilèges très connus :
  • SeDebugPrivilege
  • SeShutdownPrivilege
  • SeBackupPrivilege
  • SeRestorePrivilege
  • SeTakeOwnershipPrivilege
  • SeIncreaseQuotaPrivilege
  • SeAssignPrimaryTokenPrivilege
  • SeImpersonatePrivilege
  • SeLoadDriverPrivilege
  • SeSecurityPrivilege
Un privilège peut etre :
  • présent dans le token
  • absent du token
  • présent mais désactivé
  • présent et activé
  • présent mais supprimé ou restreint selon le contexte
C’est un point central : avoir un privilège dans le token ne signifie pas forcément qu’il est actif au moment voulu.

Activer un privilège avec AdjustTokenPrivileges

Le schéma classique pour activer un privilège consiste à :
  • ouvrir le token avec TOKEN_ADJUST_PRIVILEGES et souvent TOKEN_QUERY
  • résoudre le LUID du privilège avec LookupPrivilegeValueW
  • remplir une structure TOKEN_PRIVILEGES
  • appeler AdjustTokenPrivileges
  • vérifier GetLastError
Exemple :

Code: Select all

TOKEN_PRIVILEGES tp = { 0 };

if (!LookupPrivilegeValueW(
    NULL,
    L"SeDebugPrivilege",
    &tp.Privileges[0].Luid))
{
    return 1;
}

tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

if (!AdjustTokenPrivileges(
    hToken,
    FALSE,
    &tp,
    sizeof(tp),
    NULL,
    NULL))
{
    return 1;
}

if (GetLastError() != ERROR_SUCCESS)
{
    return 1;
}
Le dernier test est essentiel. AdjustTokenPrivileges peut réussir au sens API tout en n’activant pas réellement le privilège demandé, par exemple si le privilège n’est pas présent dans le token.

Les attributs importants d’un privilège incluent notamment :
  • SE_PRIVILEGE_ENABLED
  • SE_PRIVILEGE_ENABLED_BY_DEFAULT
  • SE_PRIVILEGE_REMOVED
  • SE_PRIVILEGE_USED_FOR_ACCESS
Niveau d’intégrité et UAC

Le niveau d’intégrité est un autre élément fondamental du token. Il ne remplace pas les ACL ni les privilèges, mais ajoute une couche supplémentaire de contrôle dans le modèle MIC, Mandatory Integrity Control.

Les niveaux courants sont :
  • Low
  • Medium
  • High
  • System
Pour récupérer le niveau d’intégrité, on demande généralement :
  • TokenIntegrityLevel
et on obtient une structure :
  • TOKEN_MANDATORY_LABEL
Le niveau lui-meme est encodé dans un SID particulier.

Ce point est très important avec UAC. Un utilisateur membre du groupe Administrateurs n’exécute pas forcément ses processus avec un token élevé en permanence. Très souvent, il démarre avec un contexte medium, puis un contexte high après élévation explicite.

Autrement dit :
  • etre membre du groupe Administrateurs ne veut pas dire que le processus courant tourne déjà avec tous les effets d’un token élevé
TokenElevation, TokenElevationType et linked token

Pour raisonner proprement sur l’élévation, plusieurs classes d’information sont très utiles :
  • TokenElevation
  • TokenElevationType
  • TokenLinkedToken
Structures utiles associées :
  • TOKEN_ELEVATION
TokenElevation permet de savoir si le token est élevé. TokenElevationType permet de distinguer plusieurs cas. TokenLinkedToken est important dans les scénarios UAC, car un token peut posséder un token lié représentant l’autre version du contexte, par exemple la version filtrée ou élevée.

C’est une partie importante pour comprendre le comportement admin moderne sous Windows.

Token primaire contre token d’impersonation en pratique

Il faut toujours garder une règle simple :
  • pour créer un processus sous une identité, on a besoin d’un token primaire
  • pour accéder à une ressource au nom d’un autre, un token d’impersonation peut suffire selon le contexte
Cela explique pourquoi certaines API exigent explicitement un token primaire alors que d’autres se contentent d’un contexte d’impersonation thread.

Dupliquer un token

L’API clé ici est :
  • DuplicateTokenEx
Exemple :

Code: Select all

HANDLE hNewToken = NULL;

if (!DuplicateTokenEx(
    hToken,
    TOKEN_ALL_ACCESS,
    NULL,
    SecurityImpersonation,
    TokenPrimary,
    &hNewToken))
{
    return 1;
}
Cette fonction est extrêmement importante. Elle sert notamment à :
  • obtenir un token primaire à partir d’un autre token
  • préparer un contexte pour CreateProcessAsUserW
  • construire des scénarios d’impersonation ou de sandbox
  • manipuler des tokens dans les services Windows
Il existe aussi :
  • DuplicateToken
mais DuplicateTokenEx est la version plus complète et plus utile dans les scénarios sérieux.

Impersonation : agir temporairement comme un autre

L’impersonation est un mécanisme majeur de Windows. Elle permet à un thread d’agir temporairement sous une autre identité de sécurité.

API importantes à connaitre :
  • ImpersonateLoggedOnUser
  • ImpersonateSelf
  • SetThreadToken
  • RevertToSelf
  • OpenThreadToken
Dans beaucoup de services ou de serveurs IPC, cette mécanique est centrale. Le serveur reçoit une requete, impersonne le client, accède à une ressource avec les droits du client, puis revient à son identité normale avec RevertToSelf.

Lancer un processus avec un token

Pour lancer un processus sous une autre identité de sécurité, il faut généralement utiliser des API comme :
  • CreateProcessAsUserW
  • CreateProcessWithTokenW
  • CreateProcessWithLogonW
Exemple avec CreateProcessAsUserW :

Code: Select all

STARTUPINFOW si = { 0 };
PROCESS_INFORMATION pi = { 0 };

si.cb = sizeof(si);

if (!CreateProcessAsUserW(
    hNewToken,
    NULL,
    L"cmd.exe",
    NULL,
    NULL,
    FALSE,
    0,
    NULL,
    NULL,
    &si,
    &pi))
{
    return 1;
}
Cette API demande souvent certains privilèges, notamment :
  • SeAssignPrimaryTokenPrivilege
  • SeIncreaseQuotaPrivilege
Selon le contexte exact, CreateProcessWithTokenW peut parfois etre plus pratique, mais elle a aussi ses propres contraintes.

CreateProcessWithLogonW, elle, permet un autre modèle fondé sur des identifiants explicites, souvent utile en applicatif.

Restreindre un token

Windows permet aussi de fabriquer des tokens restreints, ce qui est important pour certains scénarios de confinement ou de sécurité défensive.

API utiles à connaitre :
  • CreateRestrictedToken
  • CheckTokenMembership
  • IsTokenRestricted
Un token restreint peut retirer certains groupes, désactiver certains privilèges, ou ajouter des SID restrictifs. C’est une partie importante du modèle de sandboxing léger historique sous Windows.

Décrire un accès : AccessCheck

Quand on veut comprendre ou simuler une décision d’accès, l’API très importante est :
  • AccessCheck
Elle permet de demander si un token possède ou non les droits nécessaires vis-à-vis d’un descripteur de sécurité donné.

Les structures utiles autour de ça incluent souvent :
  • GENERIC_MAPPING
  • PRIVILEGE_SET
  • SECURITY_DESCRIPTOR
Cela permet de mieux comprendre comment Windows décide en pratique.

Descripteurs de sécurité, ACL et relation avec le token

Le token n’agit pas seul dans le vide. Il est toujours comparé à une politique de sécurité portée par l’objet cible.

Il faut donc au moins connaitre de nom :
  • SECURITY_DESCRIPTOR
  • ACL
  • ACE
  • DACL
  • SACL
Le principe général est simple :
  • le token décrit qui demande l’accès
  • le security descriptor décrit qui est autorisé ou auditée
  • Windows compare les deux
Pour manipuler ou interroger ces structures, on rencontre souvent :
  • GetSecurityInfo
  • SetSecurityInfo
  • InitializeSecurityDescriptor
  • SetSecurityDescriptorDacl
  • GetKernelObjectSecurity
  • SetKernelObjectSecurity
Sans cette vision globale, on comprend mal comment fonctionne réellement une décision de sécurité.

Session, logon et provenance du token

Un token n’est pas juste “user + groupes + privilèges”. Il porte aussi des informations de session et d’origine.

Classes utiles à connaitre :
  • TokenSessionId
  • TokenOrigin
  • TokenSource
Structures importantes :
  • TOKEN_SOURCE
  • TOKEN_STATISTICS
Cela est très utile dans les outils de diagnostic, les services, le terminal services / RDS, et certains scénarios d’administration.

Admin, SeDebugPrivilege et faux raccourcis

Une erreur fréquente consiste à croire que “etre admin” suffit. En réalité, la sécurité Windows est plus subtile.

Quelques règles simples à retenir :
  • etre dans le groupe Administrateurs ne garantit pas un token élevé en permanence
  • un privilège présent dans le token n’est pas forcément activé
  • SeDebugPrivilege n’ouvre pas magiquement toutes les portes si le reste du contexte ne suit pas
  • le niveau d’intégrité et les ACL continuent à compter
  • certaines opérations demandent une combinaison précise de droits, privilèges et type de token
C’est exactement pour ça que des appels paraissent parfois incohérents à ceux qui n’ont pas encore une vision claire du token.

Quelques API très utiles autour des tokens

En plus des grandes API déjà vues, il vaut la peine de connaitre aussi :
  • GetTokenInformation
  • SetTokenInformation
  • LookupPrivilegeNameW
  • LookupPrivilegeDisplayNameW
  • LookupAccountNameW
  • LookupAccountSidW
  • CheckTokenMembership
  • GetCurrentThreadEffectiveToken
  • GetCurrentThreadToken
  • GetCurrentThreadToken
  • GetCurrentProcessToken
Certaines de ces API sont plus récentes ou plus pratiques selon la version de Windows ciblée, mais les connaitre de nom aide beaucoup.

Les structures à connaitre absolument

Pour avoir déjà une base solide, il faut reconnaitre rapidement les structures suivantes :
  • TOKEN_USER
  • TOKEN_GROUPS
  • TOKEN_PRIVILEGES
  • TOKEN_OWNER
  • TOKEN_PRIMARY_GROUP
  • TOKEN_DEFAULT_DACL
  • TOKEN_STATISTICS
  • TOKEN_ELEVATION
  • TOKEN_MANDATORY_LABEL
  • SID_AND_ATTRIBUTES
  • LUID
  • LUID_AND_ATTRIBUTES
  • TOKEN_SOURCE
  • PRIVILEGE_SET
  • GENERIC_MAPPING
  • SECURITY_DESCRIPTOR
  • ACL
  • SID
Les API à connaitre absolument

Pour ouvrir et manipuler un token :
  • OpenProcessToken
  • OpenThreadToken
  • DuplicateToken
  • DuplicateTokenEx
  • SetThreadToken
  • CloseHandle
Pour lire les informations :
  • GetTokenInformation
  • SetTokenInformation
  • CheckTokenMembership
  • IsTokenRestricted
Pour les privilèges :
  • LookupPrivilegeValueW
  • LookupPrivilegeNameW
  • LookupPrivilegeDisplayNameW
  • AdjustTokenPrivileges
Pour l’impersonation :
  • ImpersonateLoggedOnUser
  • ImpersonateSelf
  • RevertToSelf
Pour créer un processus avec un token :
  • CreateProcessAsUserW
  • CreateProcessWithTokenW
  • CreateProcessWithLogonW
Pour les SID et comptes :
  • LookupAccountSidW
  • LookupAccountNameW
  • ConvertSidToStringSidW
  • ConvertStringSidToSidW
  • EqualSid
  • IsValidSid
Pour les contrôles d’accès et descripteurs de sécurité :
  • AccessCheck
  • GetSecurityInfo
  • SetSecurityInfo
  • GetKernelObjectSecurity
  • SetKernelObjectSecurity
  • InitializeSecurityDescriptor
  • SetSecurityDescriptorDacl
Pour les tokens restreints :
  • CreateRestrictedToken
Erreurs classiques

Comme souvent en sécurité Windows, les erreurs les plus fréquentes viennent d’un raisonnement trop simplifié.

Parmi les fautes classiques :
  • confondre appartenance au groupe Administrateurs et élévation effective
  • penser qu’un privilège est automatiquement activé parce qu’il est présent
  • oublier CloseHandle sur le token
  • demander trop de droits d’accès au token
  • ne pas vérifier GetLastError après AdjustTokenPrivileges
  • confondre token primaire et token d’impersonation
  • croire que l’impersonation transforme magiquement tout le processus alors qu’elle touche souvent le thread
  • ignorer le niveau d’intégrité
  • oublier que le token est comparé à un security descriptor et pas utilisé seul
Ce qu’il faut retenir

Le token de sécurité définit l’identité réelle utilisée par Windows pour autoriser ou refuser une action. Il contient l’utilisateur, les groupes, les privilèges, le niveau d’intégrité, et d’autres informations qui déterminent le contexte de sécurité réel du code.

Les idées importantes à garder sont les suivantes :
  • Windows vérifie les accès à partir du token, pas du nom du programme
  • un processus possède un token primaire
  • un thread peut posséder un token d’impersonation
  • les privilèges doivent souvent etre activés explicitement
  • le niveau d’intégrité joue un rôle très important avec UAC
  • la différence entre token primaire et token d’impersonation est fondamentale
  • la sécurité dépend toujours de la rencontre entre le token et le security descriptor de l’objet ciblé
Conclusion

Comprendre les tokens, c’est comprendre une grande partie de la sécurité Windows réelle. Sans eux, les notions de privilèges, d’impersonation, d’élévation, d’accès aux processus, de création de processus sous une autre identité ou d’autorisation sur les objets système restent floues.

Si tu maitrises déjà correctement :
  • OpenProcessToken
  • GetTokenInformation
  • TOKEN_USER, TOKEN_GROUPS et TOKEN_PRIVILEGES
  • LookupPrivilegeValueW et AdjustTokenPrivileges
  • TokenIntegrityLevel
  • DuplicateTokenEx
  • CreateProcessAsUserW
  • ImpersonateLoggedOnUser et RevertToSelf
  • AccessCheck et la logique security descriptor + token
alors tu possèdes déjà une très bonne base pour comprendre la sécurité Windows au niveau système.

Et surtout, tu commences à voir que le token n’est pas une simple structure parmi d’autres, mais le coeur réel de la question : “qu’est-ce que ce code a le droit de faire ?”

A bientot pour le prochain cours :D

Who is online

Users browsing this forum: No registered users and 1 guest