FICHIER JOINT : PRIVCODE.ZIP Les privileges NT. ----------------------- Debutant en programmation sous Windows NT/2K/XP, j'ai recemment rencontre un probleme : Lors de l'appel a une fonction de l'API Windows (pour exporter une branches du registre vers le disque dur), la fonction me renvoyait l'erreur ERROR_PRIVILEGE_NOT_HELD. En fait, il fallait que j'active un privilege pour etre autorise a appeler cette fonction. Il y a peu de documents en francais a ce sujet, donc j'ai du faire mes premiers pas en securite NT a partir de textes anglais. Ca m'a pris pas mal de temps, et je suis sur que je serai alle bien plus vite si j'avais dispose d'un point de depart en francais. J'ai donc decide de synthetiser ce que j'avais appris dans un tutoriel, qui pourra etre utile a d'autres debutants et qui me servira d'aide memoire si besoin. C'est mon premier tutoriel, alors si vous avez des critiques, des suggestions ou si il y a des erreurs (de quelques nature que ce soit), soyez sympas de me mailer a "p3h5az" chez dune2.info (supprimez le 3 et le 5 : Ils sont la pour tromper les robots). Vous etes pres ? C'est partit ! Intro. ------ Contrairement aux systemes Windows 9x, les systemes de la famille NT possedent une vraie gestion de la securite. Nous allons nous interesser tout particulierement au mecanisme des fonctions privilegiees, qui est encore assez peu documente en francais. Soyez bien conscient que tout ce qui sera dit ici ne s'applique qu'a Windows NT, 2000 et XP, et en aucun cas a Windows 95, 98 ou millenium. Ce tutoriel est illustre par un code en Pascal/Delphi. (J'espere que les inconditionnels du C me pardonneront). Pour y comprendre quelque chose, vous devez : - Maitriser les notions de base de la programmation (pointeurs, handles, etc.) - Avoir deja appele une fonction des APIs Windows dans un de vos codes (au moins une fois !) - Savoir la difference entre un process et une thread ==> Cet article s'adresse donc aux programmeurs debutant sous Windows NT/2K/XP, c'est a dire ceux qui savent deja un peu coder, mais qui ne connaissent pas (ou mal) la securite sous NT. Voici les sources qui m'ont le plus servi : Security model of Windows systems (Francais) : http://www.hsc.fr/ressources/articles/mod_sec_win/index.html Windows NT Privileges (Anglais) : http://www.thedelphimagazine.com/samples/1577/1577.htm An Overview of Windows NT Security (Anglais) : http://world.std.com/~jimf/papers/nt-security/nt-security.html Et bien sur la MSDN ! Sommaire. --------- 1/ .. Les privileges. 1.1/ .... Principe. 1.2/ .... Etablissement de la liste des privileges accessibles pendant une session. 1.3/ .... Attribution ou Activation ? 1.4/ .... Liste des privileges et des droits utilisateurs. 2/ .. Access Token (Jeton d'acces). 2.1/ .... Principe. 2.2/ .... Token principal et personnification de Token (impersonation). 2.3/ .... Contenu d'un Access Token. 2.4/ .... Breves explications sur les DACLs et les ACEs. 3/ .. Mise en pratique. 3.1/ .... Acceder a un Access Token. 3.2/ .... Precisions sur les structures representant des Privileges. 3.3/ .... Precisions sur les structures representant des SIDs. 3.4/ .... Presentation du code source. 1/ Les privileges. ------------------ 1.1/ Principe. -------------- A chaque tache importante du point de vue de la securite, correspond un privilege qu'il est necessaire d'activer avant d'etre autorise par le systeme a effectuer ladite tache. Par exemple le privilege SE_BACKUP_NAME permet d'effectuer des sauvegardes de fichiers systemes ou de branches du registre, le privilege SE_DEBUG_NAME est necessaire pour deboguer un process appartenant a un autre acompte, le privilege SE_REMOTE_SHUTDOWN_NAME est requis pour eteindre l'ordinateur depuis le reseau, etc. Une liste de privileges plus ou moins complete est associee a chaque entite principale de securite, c'est-a-dire a chaque compte utilisateur, groupe, ou alias. Chaque privilege peut etre sous deux etats : active ou desactive. Lorsqu'un programme veut appeler une fonction privilegiee, trois cas sont possibles : - Si l'utilisateur NE POSSEDE PAS le privilege necessaire, alors l'appel de la fonction privilegiee est refuse. - Si l'utilisateur POSSEDE le privilege, ET qu'il N'EST PAS ACTIVE, alors l'appel de la fonction privilegiee est refuse. - Si l'utilisateur POSSEDE le privilege, ET si il est ACTIVE, alors l'appel de la fonction privilegiee est autorise. La plupart des privileges sont desactives par defaut, meme pour le compte administrateur. Autrement dit, lorsqu'un utilisateur possede le privilege requis pour appeler une fonction, le programme doit d'abord activer ce privilege avant d'etre autorise a appeler la fonction. Si l'appel reussit, alors le programme n'a plus besoin du privilege : il doit donc le desactiver. ==> Un programme sur ne devrait activer de privilege que le temps d'effectuer une action privilegiee. [Nda : Par la suite, j'utiliserai l'expression 'principal de securite' pour designer indifferemment l'une ou l'autre des entites 'compte utilisateur', 'alias' ou 'groupe'. Le sens que je prete ici a cette expression est specifique aux systemes windows. Il ne faut pas le generaliser a toute la securite informatique]. Remarque : Les privileges n'ont de sens que sur le systeme local : - Si Bob veut eteindre l'ordinateur de Fred en utilisant le privilege qui permet d'eteindre un ordinateur depuis le reseau, c'est l'ordinateur de Fred qui doit avoir le privilege active. (Le systeme sur lequel est effectuee l'action). - Si Bob active ce privilege sur son propre systeme, cela ne lui permettra pas d'eteindre l'ordinateur de Fred (par contre, cela permettra aux autres d'eteindre l'ordinateur de Bob). 1.2/ Etablissement de la liste des privileges accessibles pendant une session. ------------------------------------------------------------------------------ Tout ce qui touche a la securite d'un systeme NT est gere par le sous-systeme LSA (Local Security Authority). Quand un utilisateur ouvre une session, l'autorite de securite locale verifie le mot de passe en le comparant avec les informations stockees dans une base de donnees de securite : - SAM (Security Account Manager) pour Windows NT et pour les comptes locaux sur 2K/XP. - Active Directory pour les controleurs de domaine 2 K ou XP. Si le mot de passe est valide, LSA utilise les informations stockees dans la SAM pour creer un "Access Token". Il s'agit d'un objet Windows, qui contient entre autres la liste des privileges accessibles a l'utilisateur. (Nous reparlerons plus en detail des "Access Token" plus loin dans ce texte). Pour etablir cette liste, LSA combine les privileges assignes a chaque principal de securite auquel appartient l'utilisateur. Certains privileges sont donc directement assignes a l'utilisateur, alors que d'autres sont herites au travers des groupes et alias dont il fait partie. C'est cette liste de privileges qui va determiner ce qu'il sera ou non possible de faire pendant la session. 1.3/ Attribution ou Activation ? -------------------------------- Resume : - Une liste de privileges est assignee a chaque principal de securite, le tout est stocke dans la SAM ou dans Active Directory. - Une liste de privileges est etablie au debut de chaque session, puis stockee dans un Access Token. Cette liste est creee d'apres les informations contenues dans la SAM ou dans Active Directory. Vous devez donc etre conscient que l'on peut faire deux sortes de choses avec les privileges : 1) Ajouter, supprimer ou lister les privileges associes a un 'principal' de securite (compte utilisateur, alias, groupe, etc.). Il s'agit d'assigner des privileges a tel utilisateur ou tel groupe. S'attribuer un privilege de cette maniere n'a d'interet que si l'on veut configurer le systeme, cela ne permet pas d'appeler des fonctions privilegiees. Cette tache est donc plutot reservee aux administrateurs systeme, ou aux applications d'administration. - LSA permet de lire ou de modifier les strategies de securite locale (LSA policy) par programme a l'aide d'un jeu de fonctions regroupees dans LSA API. Par exemple : "LsaAddAccountRights", "LsaRemoveAccountRights" et "LsaEnumerateAccountRights". - Les administrateurs Windows peuvent effectuer la meme tache en tapant "secpol.msc" dans une fenetre console, ou en utilisant le "panneau de configuration" puis "outils d'administration" puis "Strategie de securite locale". Dans les deux cas, il faut se rendre dans "Strategies locales/Attribution des droits utilisateurs". ==> Windows affiche alors la liste des droits utilisateurs et des privileges, ainsi que les principaux de securite auxquels ils sont assignes. (Voir details plus bas sur la difference entre droits et privileges). NB. : Les modifications aux regles de securite locale ne prennent effet qu'a l'ouverture d'une nouvelle session. Si un administrateur s'attribue un privilege, il doit terminer sa session puis en ouvrir une autre avant de pouvoir l'utiliser. Nous ne donnerons pas plus de details sur LSA API. 2) Activer, desactiver ou lister les privileges associes a un process. Il s'agit d'aller modifier l'etat des privileges directement dans la liste de privileges de "l'Access Token" associe a un process, pour permettre a notre programme d'appeler une fonction privilegiee. ==> C'est sur ce sujet que nous allons concentrer nos efforts. 1.4/ Liste des privileges et des droits utilisateurs. ----------------------------------------------------- Avant de parler des Access Token, voici la liste des privileges, extraite de la MSDN. Jetez-y un coup d'oil avant de continuer, cela vous permettra de vous rendre compte de ce que les privileges permettent de faire. Cela peut aussi vous aider a configurer votre systeme en meilleure connaissance de cause. Il reste des privileges dont je ne comprends pas bien le role. Je serais tres heureux que quelqu'un m'envoie un mail pour me l'expliquer. Les constantes suivantes sont declarees dans Winnt.h +-----------------------------------------------------------------------+ | NOM_DE_LA_CONSTANTE = 'Nom chaine du privilege' | +-----------------------------------------------------------------------+ | Chaine proposee pour ce privilege dans "attribution des droits | | utilisateurs". | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Explication du role du privilege. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_ASSIGNPRIMARYTOKEN_NAME = 'SeAssignPrimaryTokenPrivilege' | +-----------------------------------------------------------------------+ | "Remplacer un jeton niveau de processus" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Permet de remplacer l'Access Token d'un processus deja demarre. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_AUDIT_NAME = 'SeAuditPrivilege' | +-----------------------------------------------------------------------+ | "Generer des audits de securite" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Autorise la creation d'entrees dans le journal de securite, qui | | permet le suivi des acces systeme non autorises. Ce privilege ne | | permet pas de specifier les options d'audit. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_BACKUP_NAME = 'SeBackupPrivilege' | +-----------------------------------------------------------------------+ | "Sauvegarder des fichiers et des repertoires" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Autorise son detenteur a outrepasser les autorisations d'acces sur | | les fichiers, les dossiers ou sur les branches du registre. Le but | | est de permettre aux non-administrateurs d'effectuer des sauvegardes | | du systeme ou de donnees. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_CHANGE_NOTIFY_NAME = 'SeChangeNotifyPrivilege' | +-----------------------------------------------------------------------+ | "Outrepasser le controle de defilement" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Ce privilege permet d'acceder a tout dossier autorise, meme si | | l'acces au repertoire parent est interdit. Autrement dit, ce | | privilege permet de parcourir les repertoires, y compris ceux sur | | lesquels l'utilisateur n'a pas d'autorisation d'acces. 'Parcourir' un | | repertoire ne permet pas de le lister, cela permet seulement | | d'acceder a ses sous-dossiers. Ce privilege est active par defaut | | pour tous les utilisateurs. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_CREATE_PAGEFILE_NAME = 'SeCreatePagefilePrivilege' | +-----------------------------------------------------------------------+ | "Creer un fichier d'echange" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour creer un fichier d'echange permanent. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_CREATE_PERMANENT_NAME = 'SeCreatePermanentPrivilege' | +-----------------------------------------------------------------------+ | "Creer des objets partages permanents" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour creer des objets permanents. (Si quelqu'un veut bien | | m'expliquer ce que sont ces objets permanents...). | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_CREATE_TOKEN_NAME = 'SeCreateTokenPrivilege' | +-----------------------------------------------------------------------+ | "Creer un objet jeton" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour creer un Access Token. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_DEBUG_NAME = 'SeDebugPrivilege' | +-----------------------------------------------------------------------+ | "Deboguer des programmes" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour deboguer un process appartenant a un autre acompte. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_ENABLE_DELEGATION_NAME = 'SeEnableDelegationPrivilege' | +-----------------------------------------------------------------------+ | "Autoriser que l'on fasse confiance aux comptes Ordinateurs et | | Utilisateurs pour la delegation" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Permet d'identifier les comptes ordinateurs et Utilisateurs comme | | entites de confiance (Trusted) pour la delegation. (Bon ok, je ne | | sais pas trop ce que ca veut dire). | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_IMPERSONATE_PRIVILEGE = 'SeImpersonatePrivilege' | +-----------------------------------------------------------------------+ | Necessaire sous NT pour personnifier une thread. | | ==> N'existe plus sous 2K/XP. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_INC_BASE_PRIORITY_NAME = 'SeIncreaseBasePriorityPrivilege' | +-----------------------------------------------------------------------+ | "Augmenter la priorite de la planification" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour augmenter la priorite d'un processus. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_INCREASE_QUOTA_NAME = 'SeIncreaseQuotaPrivilege' | +-----------------------------------------------------------------------+ | "Changer les quotas memoire d'un processus" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour augmenter les quotas memoire d'un processus. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_LOAD_DRIVER_NAME = 'SeLoadDriverPrivilege' | +-----------------------------------------------------------------------+ | "Charger et decharger les pilotes de peripheriques" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour charger ou decharger un driver. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_LOCK_MEMORY_NAME = 'SeLockMemoryPrivilege' | +-----------------------------------------------------------------------+ | "Verrouiller des pages en memoire" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour verrouiller des pages memoire de memoire physique | | avec "AllocateUserPhysicalPages". | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_MACHINE_ACCOUNT_NAME = 'SeMachineAccountPrivilege' | +-----------------------------------------------------------------------+ | "Ajouter un ordinateur au domaine" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour ajouter un nouvel ordinateur au domaine. Ne concerne | | que les controleurs de domaine. (Ce privilege n'est pas necessaire | | pour ajouter un compte localement). | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_MANAGE_VOLUME_NAME = 'SeManageVolumePrivilege' | +-----------------------------------------------------------------------+ | "Effectuer des taches de maintenance de volume" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Permet a un utilisateur d'utiliser des fonctions d'acces aux disques | | normalement reservees a un administrateur. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ SE_PROF_SINGLE_PROCESS_NAME = 'SeProfileSingleProcessPrivilege' | +-----------------------------------------------------------------------+ | "Optimiser un processus unique" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Requis pour recuperer les informations de performance sur un | | processus non systeme. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_REMOTE_SHUTDOWN_NAME = 'SeRemoteShutdownPrivilege' | +-----------------------------------------------------------------------+ | "Forcer l'arret a partir d'un systeme distant" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour eteindre l'ordinateur a partir du reseau. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_RESTORE_NAME = 'SeRestorePrivilege' | +-----------------------------------------------------------------------+ | "Restaurer des fichiers ou des repertoires" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour outrepasser les autorisations d'acces sur les | | fichiers, dossiers, et branches du registre, dans un but de | | restauration du systeme ou des donnees. (Grace a ce privilege, cette | | tache peut etre confiee a des non-admins). Par suite, ce privilege | | permet a son detenteur de definir n'importe quel SID valide (groupe | | ou utilisateur) comme proprietaire d'un objet. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_SECURITY_NAME = 'SeSecurityPrivilege' | +-----------------------------------------------------------------------+ | "Gerer le journal d'audit et de securite" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Permet de specifier les options d'audit de l'acces aux objets (tels | | que des fichiers, des dossiers, des objets active directory ou des | | clefs du registre). Ce privilege identifie son proprietaire comme | | "operateur de securite", c'est-a-dire qu'il permet de lire et | | d'effacer le journal de securite (=lire et ecrire dans la SACL d'un | | objet). Ce privilege ne permet pas d'ajouter des entrees au journal | | de securite, c'est le role de SE_AUDIT_NAME. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_SHUTDOWN_NAME = 'SeShutdownPrivilege' | +-----------------------------------------------------------------------+ | "Arreter le systeme" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Requis pour eteindre l'ordinateur localement. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_SYNC_AGENT_NAME = 'SeSyncAgentPrivilege' | +-----------------------------------------------------------------------+ | "Synchroniser les donnees du service d'annuaire" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Autorise un controleur de domaine a utiliser les services de | | synchronisation de l'annuaire LDAP. Ce privilege permet a son | | detenteur de lire tous les objets et proprietes de l'annuaire sans se | | preoccuper de leurs protections. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_SYSTEM_ENVIRONMENT_NAME = 'SeSystemEnvironment' | +-----------------------------------------------------------------------+ | "Modifier les valeurs d'env. de microprogrammation" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Requis pour modifier la RAM non volatile du systeme (inoperant si le | | systeme ne possede pas ce type de memoire). | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_SYSTEM_PROFILE_NAME = 'SeSystemProfilePrivilege' | +-----------------------------------------------------------------------+ | "Optimiser les performances systeme" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour recuperer les informations de performance des | | processus systeme. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_SYSTEMTIME_NAME = 'SeSystemtimePrivilege' | +-----------------------------------------------------------------------+ | "Modifier l'heure systeme" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Necessaire pour modifier l'heure du systeme. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_TAKE_OWNERSHIP_NAME = 'SeTakeOwnershipPrivilege' | +-----------------------------------------------------------------------+ | "Prendre possession des fichiers ou autres objets" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Permet a son detenteur de prendre possession d'un objet, sans se | | preoccuper des droits d'acces. Ce privilege permet d'affecter | | n'importe quel proprietaire valide a un objet. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_TCB_NAME = 'SeTcbPrivilege' | +-----------------------------------------------------------------------+ | "Agir en tant que partie du systeme d'exploitation" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Identifie son detenteur comme une partie du systeme d'exploitation.| | (Donc comme un processus 'de confiance'). Ce privilege permet | | notamment de placer des autorisations arbitraires dans un Access | | Token. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ |SE_UNDOCK_NAME = 'SeUndockPrivilege' | +-----------------------------------------------------------------------+ | "Retirer l'ordinateur de sa station d'accueil" | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Permet de retirer un ordinateur portable de sa station d'accueil. | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ |SE_UNSOLICITED_INPUT_NAME = 'SeUnsolicitedInputPrivilege' | +-----------------------------------------------------------------------+ | Read unsolicited input from a terminal device. Lire une entree non | | sollicitee sur un peripherique terminal ? (ecrivez-moi si vous pensez | | savoir traduire celui-la). | | N/A | +-----------------------------------------------------------------------+ Dans les "Strategies locales/Attribution des droits utilisateurs", on trouve aussi les droits suivants. Il ne agit pas de privileges au sens propre, mais de droits utilisateurs. Tout comme les privileges, les droits utilisateurs sont assignes aux principaux de securite, mais ils ne peuvent pas etre actives ou desactives (ils n'ont aucun etat). Les constantes suivantes sont declarees dans Ntsecapi.h +-----------------------------------------------------------------------+ | NOM_DE_LA_CONSTANTE = 'Nom chaine du droit utilisateur' | | Chaine proposee dans "attribution des droits utilisateurs" | +-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+ | SE_INTERACTIVE_LOGON_NAME = 'SeInteractiveLogonRight' | | "Ouvrir une session localement" | +-----------------------------------------------------------------------+ | SE_NETWORK_LOGON_NAME = 'SeNetworkLogonRight' | | "Acceder a cet ordinateur depuis le reseau" | +-----------------------------------------------------------------------+ | SE_BATCH_LOGON_NAME = 'SeBatchLogonRight' | | "Ouvrir une session en tant que tache" | +-----------------------------------------------------------------------+ | SE_SERVICE_LOGON_NAME = 'SeServiceLogonRight' | | "Ouvrir une session en tant que service" | +-----------------------------------------------------------------------+ | SE_DENY_INTERACTIVE_LOGON_NAME = 'SeDenyInteractiveLogonRight' | | "Refuser les ouvertures de sessions locales" | +-----------------------------------------------------------------------+ | SE_DENY_NETWORK_LOGON_NAME = 'SeDenyNetworkLogonRight' | | "Refuser l'acces a cet ordinateur a partir du reseau" | +-----------------------------------------------------------------------+ | SE_DENY_BATCH_LOGON_NAME = 'SeDenyBatchLogonRight' | | "Refuser l'ouverture de session en tant que tache" | +-----------------------------------------------------------------------+ | SE_DENY_SERVICE_LOGON_NAME = 'SeDenyServiceLogonRight' | | "Refuser l'ouverture de session en tant que service" | +-----------------------------------------------------------------------+ Comme vous pouvez le remarquer, Windows distingue les privileges des droits utilisateurs. On lit souvent que l'expression 'droits utilisateurs' est synonyme de 'privileges', et qu'il s'agirait simplement de la formule choisie par microsoft pour designer les privileges dans la GUI de Windows. Ce n'est qu'en partie vrai : les privileges sont effectivement designes par l'expression "droits utilisateurs" dans Windows, mais du point de vue du programmeur il y a quand meme une difference entre les 2 : - Les privileges peuvent avoir deux etats : actives ou desactives. Lorsque l'appel d'une fonction necessite un privilege, le programme doit prealablement l'activer, sinon l'appel echouera. - Les droits utilisateurs ne possedent aucun etat, puisqu'il existe des droits du type autoriser, et des droits du type refuser. Ils permettent, par leur simple presence ou absence, de determiner quels types de connexions au systeme sont autorisees, sans qu'il soit necessaire d'activer ou de desactiver quoi que ce soit. ==> Les fonctions de LSA API qui servent a assigner les droits et les privileges aux principaux, peuvent manipuler indifferemment les droits utilisateurs et les privileges. Par contre, vous devez etre bien conscient que tenter d'activer un droit n'a aucun sens. 2/ Access Token (Jeton d'acces). -------------------------------- 2.1/ Principe. -------------- La liste et l'etat des privileges associes a un processus sont stockes dans un objet Windows appele "Access Token" (Jeton d'Acces en francais). Il sera donc necessaire d'acceder a un Access Token si nous voulons activer un privilege. Mais voyons d'abord plus en detail de quoi il s'agit. Comme nous l'avons deja dit precedemment, tout ce qui touche a la securite locale des systemes de la famille de NT est gere par un sous-systeme protege appele LSA (Local Security Authority). Lorsqu'un utilisateur ouvre une session, que ce soit via l'ecran d'accueil ou le reseau, LSA verifie son mot de passe en le comparant avec les informations stockees dans la SAM (Security Account Manager) ou dans Active Directory. Ensuite, si le mot de passe est valide, LSA cree un Access Token representant cet utilisateur, toujours a partir des informations de la SAM ou d'Active Directory. L'Access Token fournit le contexte de securite du process. Entre autres choses, il contient l'identite de l'utilisateur, les groupes auxquels appartient cet utilisateur, et une liste de privileges (la somme des privileges associes a l'utilisateur et des privileges associes a chacun des groupes dont il est membre). Durant toute la session, tous les processus qui seront lances par cet utilisateur recevront une copie de cet Access Token. Quand un processus (ou une thread) tente d'acceder a un objet securise (par exemple un dossier, une clef de registre, un process, etc.), le systeme examine l'Access Token de ce process pour verifier si le droit d'acces demande sur l'objet est autorise ou non pour l'utilisateur qui execute le process. De meme, lorsqu'un processus tente d'appeler une fonction privilegiee, le systeme examine l'Access Token de ce process, a la recherche du privilege requis. Si le privilege est trouve et s'il est active, alors le systeme autorisera l'appel a la fonction. 2.2/ Token principal et personnification de Token (impersonation). ------------------------------------------------------------------ Etant donne que chaque process possede une copie distincte de l'Access Token qui lui est associe, il est tout a fait possible de modifier l'Access Token d'un processus sans affecter les autres processus (meme ceux appartenant au meme utilisateur). Par contre, il en va tout autrement pour les threads. Par defaut, elles ne recoivent pas de copie de l'Access Token du processus, mais heritent implicitement de ce Token. Cela signifie que lorsqu'une thread modifie son Token, elle modifie egalement le Token des autres threads, puisqu'il s'agit du meme Access Token (celui du processus). Si toutes les threads d'un processus devaient avoir le meme contexte de securite, cela poserait un grave probleme de securite. Imaginez un serveur multithreads, sur lequel chaque client utilise une thread differente. Si l'un des clients est administrateur du serveur, et qu'il active un privilege auquel il a legitimement droit, alors tous les autres clients, qui ne sont pas admins, vont eux aussi etre en mesure d'exploiter ce privilege, puisque toutes les threads ont le meme Token. Heureusement, ce probleme peut etre facilement resolu en creant une copie distincte de l'Access Token du process pour chaque thread qui a besoin d'activer un privilege. Ce procede est appele 'impersonation' en anglais (du verbe 'impersonate'), et peut etre traduit par personnification en francais (on peut aussi trouver 'impersonnalisation', mais j'aime moins). On peut personnifier le Token d'une thread en appelant la fonction "ImpersonateSelf" depuis cette thread. Une fois la personnification reussie la thread possede son propre Access Token et peut le modifier, par exemple en activant un privilege, sans affecter le contexte de securite des autres threads. Apres avoir utilise le privilege, la thread doit detruire son Token personnifie en appelant la fonction "RevertToSelf". Des que la personnification du Token est detruite, la thread herite a nouveaux de l'Access Token du processus. L'expression "Token principal" (Primary Token), designe le Token attache par defaut au process et a ses threads. Par opposition, l'expression "Token personnifie" (impersonation Token) designe la copie de ce Token attachee a une thread grace a la fonction "ImpersonateSelf". NB : Notre code de demonstration sera single-thread, et n'utilisera donc pas la personnification. 2.3/ Contenu d'un Access Token. ------------------------------- Maintenant que nous connaissons mieux les Access Token, voyons ce qu'ils contiennent plus en detail. Remarque preliminaire : les SIDs sont des identifiants uniques pour les principaux de securite (compte utilisateur, alias, groupe, etc.). En interne, le systeme se refere toujours aux principaux de securite par leurs SIDs, et non par leurs noms de type chaine de caracteres. Un Access Token contient : - Le SID de l'utilisateur a qui est attache ce Token. - Les SIDs des groupes auxquels appartient l'utilisateur. - Un SID de session (logon SID), qui identifie la session ouverte par l'utilisateur (Logon session). - Une liste de privileges, etablie d'apres les droits associes a l'utilisateur, et les droits associes aux groupes auxquels il appartient. A chaque privilege est associe un etat : Active, Active par defaut, ou Desactive. - Le SID du compte proprietaire par defaut de tous les nouveaux objets crees. - Le SID du groupe principal par defaut de tous les nouveaux objets crees. Ce SID doit absolument figurer egalement dans la liste des groupes auxquels appartient l'utilisateur. - La DACL (Discretionary Access Control List) qui sera attribuee par defaut a tous les nouveaux objets securises crees par l'utilisateur du Token. La DACL contient les autorisations/restrictions d'acces associees a un objet securise. (Voir explications sur les DACLs et les ACEs) - La source du Token. Il s'agit d'une chaine de caracteres qui permet de reconnaitre le type de session ouverte par l'utilisateur : Session Manager, LAN Manager, ou RPC Server. - Une variable specifiant si le Token est un Token principal (primary Token) ou une personnification (Impersonation Token). - Une liste (optionnelle) de SIDs de restrictions. Il s'agit de SIDs qui ne doivent etre pris en compte que pour les interdictions d'acces et jamais pour les autorisations. - Etc. --> Consultez la MSDN pour plus de details... 2.4/ Breves explications sur les DACLs et les ACEs. --------------------------------------------------- Ce paragraphe n'est pas indispensable a la comprehension du mecanisme des privileges. Il n'existe que pour satisfaire votre eventuelle curiosite au sujet du controle d'acces. Si ce sujet ne vous interesse pas, vous pouvez ignorer ce paragraphe et passer a la suite. Chaque objet securise de Windows (comme un dossier, une clef de registre, un process, etc.) possede une DACL (discretionary access control list), qui definit les autorisations/restrictions d'acces de cet objet. Une DACL est constituee d'un ensemble d'ACEs (Access Control Entry). Une ACE est une structure de donnees qui associe : - un SID - un droit d'acces (par exemple le droit de lecture, d'effacement, ecriture dans la DACL ou de modification du proprietaire de l'objet...) - une variable specifiant si ce droit d'acces doit etre autorise ou interdit pour ce SID ==> Une ACE permet donc de stocker une autorisation ou une restriction, pour un droit d'acces et un SID specifique. ==> La DACL contient la liste des ACEs d'un objet, c'est-a-dire l'ensemble des autorisations/restrictions associees a cet objet. Lorsqu'un process demande a acceder a un objet, le systeme compare les SIDs contenus dans l'Access Token du process (le SID de l'utilisateur et ceux des groupes auxquels il appartient) avec ceux contenus dans la DACL de l'objet. Le but de cette comparaison est de trouver, dans la DACL de l'objet, une entree ACE associant le droit d'acces desire (lire, effacer, etc.) avec l'un des SIDs contenus dans le Token du process. Une fois une telle entree ACE trouvee, il ne reste plus qu'a lire si cette ACE autorise ou interdit ce droit d'acces pour ce SID. - Si l'objet n'a pas de DACL, le systeme permet a tout le monde d'y acceder. - Si la DACL n'a pas d'ACE, le systeme refuse l'acces a l'objet, considerant qu'aucune autorisation d'acces explicite n'a ete trouvee, et que tout ce qui n'est pas explicitement autorise est implicitement interdit. - Si le systeme ne trouve aucune ACE qui associe un SID present dans l'Access Token du process avec le droit d'acces desire, il refuse egalement l'acces a l'objet, considerant la aussi qu'aucune autorisation d'acces explicite n'a ete trouvee. ==> La recherche s'arrete sur la premiere ACE autorisant ou refusant le droit d'acces souhaite a l'un des SID de l'Access Token. L'ordre des ACEs dans la DACL est donc determinant : Prenons l'exemple de l'utilisateur "Bob", qui appartient au groupe "Eleves". Si la DACL d'un objet contient une autorisation de lecture pour le groupe "Eleves" ET une interdiction de lecture pour l'utilisateur "Bob", c'est l'ordre des ACEs dans la DACL qui determinera si Bob pourra ou non acceder a l'objet. Les interdictions etant prioritaires sur les autorisations, c'est l'interdiction de lecture pour "Bob" qui sera lue en premier. Windows place toujours les interdictions avant les autorisations, de telle sorte que les restrictions sont toujours prioritaires sur les autorisations. 3/ Mise en pratique. -------------------- 3.1/ Acceder a un Access Token. ------------------------------- Nous avons vu que pour activer un privilege pour un process, il fallait modifier son Access Token. Pour acceder au Token d'un process (ou d'une thread), Windows met a notre disposition les fonctions : "OpenProcessToken" et "OpenThreadToken". Ces deux fonctions sont equivalentes, la seule difference est que l'une opere pour les process et l'autre pour les threads. "OpenProcessToken" attend deux arguments : - Un handle designant le process dont on veut ouvrir le Token. La fonction "GetCurrentProcess" permet d'obtenir un handle vers notre processus. (On utilise "GetCurrentThread" avec "OpenThreadToken"). - Le droit d'acces demande sur ce Token. Il sera compare avec les autorisations de la DACL du Token, pour determiner si ce type d'acces est autorise ou interdit. Par exemple, le droit d'acces TOKEN_QUERY permet de recuperer des informations dans un Token, le droit d'acces TOKEN_ADJUST_PRIVILEGES permet de modifier les privileges d'un Token, etc. (Il existe de nombreux autres droits d'acces, certains sont generiques, c'est a dire valables pour tous les objets Windows, d'autres sont specifiques aux Access Token. Une liste de tous les droits d'acces existants figure dans la MSDN). NB : Pour combiner plusieurs droits d'acces, on effectue un OU LOGIQUE (OR) ou une somme arithmetique (+) entre les differents droits a combiner. Si le droit d'acces demande est autorise pour le Token du process specifie, alors "OpenProcessToken" retourne un handle vers le Token demande. L'appelant devra fournir ce handle en argument a toutes les fonctions qui ont besoin d'acceder a l'Access Token du process. Une fois que le programme a termine avec ce Token, il doit liberer le handle qui le designe avec la fonction "CloseHandle". Recapitulons la demarche a suivre pour acceder a un Token. - Appel de "OpenPRocessToken", avec en argument : . Un handle vers le process dont on veut recuperer le Token (souvent obtenu avec "GetCurrentProcess"). . Le droit d'acces souhaite sur ce Token. ==> Si l'acces est autorise, GetProcessToken nous renvoie un handle vers ce Token. - On utilise ce handle pour acceder au Token (avec GetTokenInformations, AdjustTokenPrivileges, AdjustTokenGroups, etc.). - Quand on a fini, on libere le handle du Token avec CloseHandle. 3.2/ Precisions sur les structures representant des Privileges. --------------------------------------------------------------- Les privileges sont designes de differentes manieres : - Par leur nom chaine : 'SeRemoteShutdownPrivilege', 'SeTakeOwnership Privilege', 'SeDebugPrivilege', etc. - Par la constante definie pour chaque nom chaine dans WinNT.h : SE_REMOTE_SHUTDOWN_NAME = 'SeRemoteShutdownPrivilege', SE_TAKE_OWNERSHIP _NAME = 'SeTakeOwnershipPrivilege', SE_DEBUG_NAME = 'SeDebugPrivilege', etc. - Par leur LUID (Local Unique Identifier). En effet, pour le systeme, il est plus simple de designer les privileges par un nombre (en l'occurrence de 64 bits) que par une chaine de caracteres. Lorsque le programmeur voudra appeler une fonction necessitant un privilege en argument, il devra d'abord convertir ce privilege en LUID. Les LUIDs sont garantis uniques sur un systeme donne, et durant toute la session (mais pas plus !). Cela signifie que : - Un LUID genere par le systeme de Bob ne designe pas le meme privilege sur l'ordinateur de Fred. - Un LUID genere maintenant par le systeme de Bob ne designera plus le meme privilege lors d'une autre session sur l'ordinateur de Bob. ==> Il ne faut pas essayer de 'retenir' les LUIDs, il faut convertir le nom chaine du privilege en LUID juste avant d'en avoir besoin (ou au debut du programme). Pour faire la correspondance entre un privilege et son LUID, Windows met deux fonctions a notre disposition : "LookupPrivilegeValue" et "LookupPrivilegeName". Remarque : Windows fournit aussi la fonction "LookupPrivilegeDisplayName", qui renvoie une chaine de caracteres designant le privilege de maniere comprehensible pour l'utilisateur, dans la langue par defaut du systeme. Par exemple, en francais, SE_REMOTE_SHUTDOWN_NAME donne 'Forcer l'arret a partir d'un systeme distant', SE_TAKE_OWNERSHIP_NAME donne 'Prendre possession des fichiers ou d'autres objets', SE_DEBUG_NAME donne 'Deboguer des programmes', etc. 3.3/ Precision sur les structures representant des SIDs. -------------------------------------------------------- Comme nous l'avons precedemment evoque, les SIDs (Security IDentifier) sont des identifiants uniques designant les principaux de securite (au sens large, meme la session en cours est designee par un SID). Il existe deux manieres de designer un SID : - Une chaine de caracteres de la forme 'S-1-5-21-827538205-1740945663 838521125-1003'. On l'utilise le plus souvent pour afficher un SID a l'ecran. - Les SIDs binaires : ils servent a passer des SIDs aux fonctions Windows. On peut passer de la forme binaire a la forme chaine grace aux fonctions "ConvertSidToStringSid" et "ConvertStringSidToSid". Attention : Microsoft avertit clairement les programmeurs qu'ils ne doivent jamais manipuler les SIDs 'a la main', mais utiliser les fonctions Windows adaptees. Par 'a la main' je veux dire en accedant directement a l'emplacement memoire ou est situe le SID. Concretement, cela veut dire que Windows vous fournit un pointeur vers un SID, et que vous n'avez pas le droit d'acceder a l'emplacement memoire qu'il designe. Soyez-y attentif, car un tel acces ne donnera pas le resultat souhaite, mais ne declenchera pas non plus d'exception (plantage assure pour votre prog). Pour comparer deux SIDs, copier un SID dans une autre variable, recuperer la taille d'un SID, ou toute autre operation sur les SIDs, vous devrez imperativement utiliser les fonctions Windows prevues a cet effet. (Par exemple "AllocateAndInitializeSid", "CopySid", "EqualPrefixSid", "EqualSid", "FreeSid", "GetLengthSid", "GetSidLengthRequired", "InitializeSid", "IsValidSid", etc. cherchez "Security Identifiers" dans la MSDN pour plus de details). 3.4/ Presentation du code source. --------------------------------- Le code source illustrant cet article fournit une interface 'Pascal friendly' a quelques fonctions Windows (pas toutes ce serait bien trop long) en rapport avec les privileges et les Tokens. Il est un peu long, mais contient plus de commentaires que de code. Il est plutot simple a comprendre, puisqu'il s'agit seulement de transtyper quelques variables, d'appeler la fonction Windows qui fait le boulot pour nous, et de renvoyer un code de sortie a l'appelant. (Les codeurs en C/C++ ne devraient donc avoir aucun probleme pour suivre). Etant donne que la declaration de la plupart des APIs Windows utilisees n'est pas fournie avec Delphi, ce code utilise l'excellent travail du 'Project JEDI' qui s'est fixe pour but de pallier a cette lacune. Les unites JwaWinBase, JwaWinNT, JwaWinError, etc. presentes dans les clauses Uses sont donc des versions Delphi de WinBase.h, WinNT.h, WinError.h, etc. Vous pouvez telecharger les dernieres versions de ces fichiers sur " http://delphi jedi.org " N'hesitez pas a aller y jeter un coup d'oeil, c'est gratuit et open source. Le code se divise en quatre unites, et un programme principal de demonstration. + L'unite LookupLuid implemente les fonctions "Privilege2Luid", "Luid2Privilege" et "Privilege2UserStr", qui fournissent une interface Pascal aux fonctions Windows "LookupPrivilegeValue", "LookupPrivilegeName" et "LookupPrivilegeDisplayName". + L'unite LookupAccount implemente les fonctions "GetAccountSid", "GetAccountName", "Str2Sid" et "Sid2Str" qui sont des interfaces Pascal pour "LookupAccountName", "LookupAccountSid", "ConvertStringSidToSid" et "ConvertSidToStringSid". + L'unite LookupPrivileges implemente les fonctions "PrivilegeEnabled" et "SetPrivilegeAttribute" qui sont des interfaces Pascal pour les fonctions Windows "PrivilegeCheck" et "AdjustTokenPrivileges". + L'unite TokenInfos implemente les fonctions "EnumTokenPrivileges", "EnumTokenGroups", "GetTokenUser" et "GetTokenOwner" qui sont des interfaces Pascal pour la fonction Windows "GetTokenInformation". (On peut recuperer encore plein d'autres informations dans un Access Token, mon exemple n'est pas exhaustif). Pour chaque fonction Windows etudiee, vous trouverez : - Un descriptif de la fonction extrait de la MSDN (mais francise), souvent complete de remarques personnelles. - Le code source de l'interface Pascal propose pour cette fonction, abondamment commente. - Parfois de longs paragraphes de commentaires sur certains points qui ne sont pas evidents pour les debutants. Le programme demo.dpr utilise ces fonctions pour afficher des informations sur son Token a l'ecran, puis pour tenter d'effectuer une copie de la SAM sur le disque dur. (il faut le privilege SE_BACKUP_NAME pour etre autorise a exporter une branche du registre sur le disque). Ce programme n'a bien sur aucun interet en lui-meme, il donne juste un exemple de programmation des privileges. NB : Ce programme fait une copie de la SAM sur le disque, alors rappellez vous de l'effacer quand vous avez fini : ce n'est pas prudent de laisser trainer une copie de sa SAM n'importe ou).