Michael Howard et Keith Brown
Cet article suppose que vous maîtrisez C++, C# et SQL.
Résumé : Lorsqu'il s'agit de problèmes de sécurité, de nombreuses situations peuvent entraîner des problèmes. Vous faites probablement confiance à tout le code exécuté sur votre réseau, donnez à tous les utilisateurs l’accès aux fichiers importants et ne prenez jamais la peine de vérifier si le code de vos machines a changé. Il se peut également que vous n'ayez pas installé de logiciel antivirus, que vous ne parveniez pas à sécuriser votre propre code et que vous accordiez trop d'autorisations à trop de comptes. Vous pouvez même être négligent en utilisant un certain nombre de fonctions intégrées qui permettent des intrusions malveillantes, et vous pouvez laisser les ports du serveur ouverts sans aucune surveillance. Nous pouvons évidemment donner bien d’autres exemples. Quels sont les problèmes vraiment importants (c'est-à-dire les erreurs les plus dangereuses qui doivent faire l'objet d'une attention immédiate pour éviter de compromettre vos données et vos systèmes) ? Les experts en sécurité Michael Howard et Keith Brown vous proposent dix conseils pour vous aider.
-------------------------------------------------- ----------------------------------
Les questions de sécurité impliquent de nombreux aspects. Les risques de sécurité peuvent provenir de n’importe où. Vous avez peut-être écrit un code de gestion des erreurs inefficace ou été trop généreux en accordant des autorisations. Vous avez peut-être oublié quels services s'exécutent sur votre serveur. Vous pouvez accepter toutes les entrées de l'utilisateur. Et ainsi de suite. Pour vous donner une longueur d'avance sur la protection de votre ordinateur, de votre réseau et de votre code, voici dix conseils que vous pouvez suivre pour une stratégie réseau plus sécurisée.
1. Faire confiance aux entrées des utilisateurs vous met en danger
Même si vous ne lisez pas le reste, rappelez-vous ceci : « Ne faites pas confiance aux entrées des utilisateurs. » Le problème se pose si vous supposez toujours que les données sont valides et non malveillantes. La plupart des vulnérabilités de sécurité impliquent que des attaquants transmettent des données écrites de manière malveillante aux serveurs.
Faire confiance à l'exactitude des entrées peut entraîner des débordements de tampon, des attaques de scripts intersites, des attaques de code d'insertion SQL, etc.
Discutons en détail de ces vecteurs d’attaque potentiels.
2. Empêcher le débordement de tampon
Lorsqu'un attaquant fournit une longueur de données supérieure à celle attendue par l'application, un débordement de tampon se produit et les données débordent dans l'espace mémoire interne. Le débordement de tampon est principalement un problème C/C++. Ils constituent une menace, mais généralement faciles à corriger. Nous n’avons constaté que deux buffer overflows qui n’étaient pas évidents et difficiles à corriger. Le développeur ne prévoyait pas que les données fournies en externe seraient plus volumineuses que la mémoire tampon interne. Le débordement provoque la corruption d'autres structures de données en mémoire, ce qui est souvent exploité par les attaquants pour exécuter du code malveillant. Les erreurs d’index de tableau peuvent également provoquer des dépassements de capacité et des dépassements de capacité de mémoire tampon, mais cela est moins courant.
Jetez un œil à l’extrait de code C++ suivant :
void DoSomething(char *cBuffSrc, DWORD cbBuffSrc) {
char cBuffDest[32];
memcpy(cBuffDest,cBuffSrc,cbBuffSrc);
}
Quel est le problème ? En fait, il n'y a rien de mal avec ce code si cBuffSrc et cbBuffSrc proviennent d'une source fiable (comme un code qui ne fait pas confiance aux données et vérifie donc leur validité et leur taille). Cependant, si les données proviennent d'une source non fiable et n'ont pas été vérifiées, alors un attaquant (source non fiable) peut facilement rendre cBuffSrc plus grand que cBuffDest et également définir cbBuffSrc pour qu'il soit plus grand que cBuffDest. Lorsque memcpy copie les données dans cBuffDest, l'adresse de retour de DoSomething est modifiée et, comme cBuffDest est adjacent à l'adresse de retour sur le cadre de pile de la fonction, l'attaquant peut effectuer certaines opérations malveillantes via le code.
La façon de compenser est de ne pas faire confiance aux entrées de l'utilisateur et aux données contenues dans cBuffSrc et cbBuffSrc :
void DoSomething(char *cBuffSrc, DWORD cbBuffSrc) {
const DWORD cbBuffDest = 32 ;
char cBuffDest[cbBuffDest];
#ifdef _DEBUG
memset(cBuffDest, 0x33, cbBuffSrc);
#endif
memcpy(cBuffDest, cBuffSrc, min(cbBuffDest, cbBuffSrc));
}
Cette fonction démontre trois propriétés d'une fonction correctement écrite qui peuvent réduire les débordements de tampon. Premièrement, l’appelant doit fournir la longueur du tampon. Bien sûr, vous ne pouvez pas faire aveuglément confiance à cette valeur ! Ensuite, dans une version de débogage, le code détecte si le tampon est réellement suffisamment grand pour contenir le tampon source. Sinon, une violation d'accès peut être déclenchée et le code peut être chargé dans le débogueur. Lors du débogage, vous serez surpris du nombre de bugs que vous trouvez. Enfin et surtout, les appels à memcpy sont défensifs dans la mesure où ils ne copient pas plus de données que ce que le tampon cible peut contenir.
Dans le cadre du Windows® Security Push chez Microsoft, nous avons créé une liste de fonctions de gestion de chaînes sécurisées pour les programmeurs C. Vous pouvez les trouver dans Strsafe.h : Safer String Handling in C (anglais).
3. Empêcher les scripts intersites
Les attaques de scripts intersites constituent un problème propre au Web. Elles peuvent endommager les données des clients via une vulnérabilité cachée dans une seule page Web. Imaginez les conséquences de l'extrait de code ASP.NET suivant :
<script language=c#>
Response.Write("Bonjour," + Request.QueryString("nom"));
</script>
Combien de personnes ont vu un code similaire ? Mais étonnamment, il y a des problèmes ! En règle générale, les utilisateurs accéderont à ce code à l'aide d'une URL similaire à la suivante :
http://explorationair.com/welcome.aspx?name=Michael
Le code C# suppose que les données sont toujours valides et contiennent simplement un nom. Cependant, un attaquant pourrait abuser de ce code en fournissant du script et du code HTML comme noms. Si vous entrez l'URL suivante
http://northwindtraders.com/welcome.aspx?name=<script>alert(' Bonjour !');
</script>
Vous obtiendrez une page Web avec une boîte de dialogue disant "Bonjour !" Vous vous dites peut-être : « Et alors ? » Imaginez qu'un attaquant puisse inciter un utilisateur à cliquer sur un lien comme celui-ci, mais que la chaîne de requête contenait un script et du code HTML vraiment dangereux, obtenant ainsi le cookie de l'utilisateur et l'envoyant à un site Web appartenant à l'attaquant ; l'attaquant a désormais accès à vos informations privées sur les cookies, ou pire.
Pour éviter cela, il existe deux manières. La première consiste à se méfier de la saisie et à limiter strictement ce que contient le nom d'utilisateur. Par exemple, vous pouvez utiliser des expressions régulières pour vérifier que le nom ne contient qu'un sous-ensemble commun de caractères et qu'il n'est pas trop grand. L'extrait de code C# suivant montre comment réaliser cette étape :
Regex r = new Regex (@"^[w]{1,40}$");
if (r.Match(strName).Success) {
// bien! La chaîne est ok
} autre {
// pas bon ! Chaîne invalide
}
Ce code utilise des expressions régulières pour vérifier qu'une chaîne ne contient que 1 à 40 lettres ou chiffres. C'est le seul moyen sûr de déterminer si une valeur est correcte.
Il n'y a aucun moyen pour HTML ou script de tromper cette expression régulière ! N'utilisez pas d'expressions régulières pour rechercher des caractères non valides et rejetez les demandes si de tels caractères non valides sont trouvés, car il est facile de manquer quelque chose.
La deuxième précaution consiste à encoder en HTML toutes les entrées en sortie. Cela réduit les balises HTML dangereuses à des caractères d'échappement plus sûrs. Vous pouvez utiliser HttpServerUtility.HtmlEncode dans ASP.NET ou Server.HTMLEncode dans ASP pour échapper à toute chaîne potentiellement problématique.
4. Ne demandez pas les autorisations sa
La dernière attaque de confiance en entrée dont nous parlerons est l'insertion de code SQL. De nombreux développeurs écrivent du code qui prend en compte les entrées et utilise ces entrées pour créer des requêtes SQL qui communiquent avec un magasin de données principal tel que Microsoft® SQL Server™ ou Oracle.
Jetez un œil à l'extrait de code suivant :
void DoQuery(string Id) {
SqlConnection sql=nouveau SqlConnection (@"data source=localhost;" +
"ID utilisateur=sa;mot de passe=mot de passe;");
sql.Open();
sqlstring= "SELECT hasshipped" +
" FROM expédition WHERE id='" + Id + "'";
SqlCommand cmd = new SqlCommand(sqlstring,sql);
•••
Ce code présente trois défauts sérieux. Tout d'abord, il établit une connexion du service Web à SQL Server en tant que compte d'administrateur système sa. Vous en verrez bientôt les pièges. Deuxième point, faites attention à la pratique intelligente consistant à utiliser « mot de passe » comme mot de passe du compte sa !
Mais le véritable problème réside dans la concaténation des chaînes qui construit les instructions SQL. Si l'utilisateur saisit 1001 pour l'ID, vous obtenez l'instruction SQL suivante, qui est entièrement valide.
SELECT hasshipped FROM shipping WHERE id = '1001'
Mais les attaquants sont bien plus créatifs que cela. Ils entreraient un "'1001' DROP table shipping --" pour l'ID, qui exécuterait une requête comme celle-ci :
SELECT a été expédié FROM
expédition OÙ id = '1001'
DROP table shipping -- ';
Cela change la façon dont la requête fonctionne. Non seulement ce code essaie de déterminer si quelque chose a été expédié, mais il procède également à la suppression (suppression) du tableau d'expédition ! Opérateur -- est l'opérateur de commentaire en SQL, qui permet aux attaquants de construire plus facilement une série d'instructions SQL valides mais dangereuses !
À l'heure actuelle, vous vous demandez peut-être comment un utilisateur peut supprimer la table de la base de données SQL Server. Bien sûr, vous avez raison, seuls les administrateurs peuvent faire un tel travail. Mais ici, vous vous connectez à la base de données en tant que sa, et sa peut faire ce qu'il veut sur la base de données SQL Server. Ne vous connectez jamais à SQL Server en tant que sa à partir d'une application ; l'approche correcte consiste à utiliser l'authentification intégrée Windows, le cas échéant, ou à vous connecter en tant que compte prédéfini avec les autorisations appropriées.
Résoudre les problèmes de code d’insertion SQL est simple. À l'aide de procédures stockées et de paramètres SQL, le code suivant montre comment créer une telle requête - et comment utiliser des expressions régulières pour confirmer que l'entrée est valide, puisque notre transaction spécifie que l'ID d'expédition ne peut comporter que 4 à 10 chiffres :
Regex r = nouvelle Regex (@"^d{4,10}$");
si (!r.Match(Id).Succès)
throw new Exception("ID invalide");
SqlConnection sqlConn= new SqlConnection(strConn);
chaîne str="sp_HasShipped" ;
SqlCommand cmd = new SqlCommand(str,sqlConn);
cmd.CommandType = CommandType.StoredProcedure ;
cmd.Parameters.Add("@ID",Id);
Les débordements de tampon, les scripts intersites et les attaques de code par insertion SQL sont tous des exemples de problèmes d'entrée fiables. Toutes ces attaques sont atténuées par un mécanisme qui considère que toutes les entrées sont nuisibles, sauf preuve du contraire.
5. Faites attention au code de cryptage !
Jetons un coup d’œil à quelque chose qui pourrait nous surprendre. J'ai découvert que plus de trente pour cent du code de sécurité que nous avons examiné présentait des failles de sécurité. La vulnérabilité la plus courante est peut-être votre propre code de cryptage, qui est probablement vulnérable. Ne créez jamais votre propre code de cryptage, c'est une tâche insensée. Ne pensez pas que simplement parce que vous disposez de votre propre algorithme de cryptage, les autres ne peuvent pas le déchiffrer. Les attaquants ont accès aux débogueurs, et ils ont également le temps et les connaissances nécessaires pour déterminer le fonctionnement des systèmes, les interrompant souvent en quelques heures. Vous devez utiliser Win32® CryptoAPI, l'espace de noms System.Security.Cryptography fournit un certain nombre d'algorithmes de chiffrement excellents et testés.
6. Réduisez le risque d'être attaqué
Si plus de 90 % des utilisateurs ne le demandent pas, une fonctionnalité ne doit pas être installée par défaut. Internet Information Services (IIS) 6.0 suit cette recommandation d'installation, que vous pouvez lire dans l'article de Wayne Berry « Les innovations dans les services Internet Information vous permettent de protéger étroitement les données sécurisées et les processus serveur », publié ce mois-ci. L'idée derrière cette stratégie d'installation est que vous ne ferez pas attention aux services que vous n'utilisez pas, et si ces services sont en cours d'exécution, ils pourraient être exploités par d'autres. Si une fonctionnalité est installée par défaut, elle doit s'exécuter selon le principe de la moindre autorisation. Autrement dit, n'autorisez pas les applications à s'exécuter avec des privilèges d'administrateur, sauf si cela est nécessaire. Il est préférable de suivre ce conseil.
7. Utiliser le principe de la moindre autorisation
Les systèmes d'exploitation et les Common Language Runtimes ont une politique de sécurité pour plusieurs raisons. De nombreuses personnes supposent que la principale raison d'être de cette politique de sécurité est d'empêcher les utilisateurs de causer intentionnellement des dommages : accéder à des fichiers auxquels ils ne sont pas autorisés à accéder, reconfigurer le réseau en fonction de leurs besoins et autres comportements flagrants. Oui, ce type d’attaque interne est courant et doit être évité, mais il existe une autre raison de s’en tenir à cette stratégie de sécurité. Autrement dit, construire des barrières défensives autour du code pour empêcher les utilisateurs de faire des ravages sur le réseau par des actions intentionnelles ou (comme cela arrive souvent) non intentionnelles. Par exemple, une pièce jointe téléchargée par courrier électronique, lorsqu'elle est exécutée sur la machine d'Alice, est limitée aux ressources auxquelles Alice peut accéder. Si une pièce jointe contient un cheval de Troie, une bonne stratégie de sécurité consiste à limiter les dégâts qu'il peut causer.
Lorsque vous concevez, créez et déployez des applications serveur, vous ne pouvez pas supposer que toutes les demandes proviennent d'utilisateurs légitimes. Si un méchant vous envoie une requête malveillante (espérons-le non) et que votre code se comporte mal, vous souhaitez que votre application dispose de toutes les défenses possibles pour limiter les dégâts. Par conséquent, nous pensons que votre entreprise met en œuvre des politiques de sécurité non seulement parce qu’elle ne fait pas confiance à vous ou à votre code, mais également pour se protéger contre les codes extérieurs malveillants.
Le principe de la moindre autorisation stipule que les autorisations minimales requises par le code doivent être accordées dans les plus brefs délais. Cela dit, érigez à tout moment autant de murs de protection que possible autour de votre code. Lorsque quelque chose de grave se produit – comme le garantit la loi de Murphy – vous serez heureux que ces murs de protection soient en place. Par conséquent, voici quelques méthodes spécifiques pour exécuter du code en utilisant le principe de moindre autorisation.
Choisissez un environnement sécurisé pour votre code serveur qui lui permet uniquement d'accéder aux ressources nécessaires pour faire son travail. Si certaines parties de votre code nécessitent des autorisations élevées, envisagez d'isoler cette partie du code et de l'exécuter séparément avec des autorisations plus élevées. Pour séparer en toute sécurité ce code qui s'exécute avec différentes informations d'authentification du système d'exploitation, il est préférable d'exécuter ce code dans un processus distinct (exécuté dans un environnement sécurisé avec des privilèges plus élevés). Cela signifie que vous aurez besoin d'une communication inter-processus (telle que COM ou Microsoft .NET remoting) et que vous devrez concevoir l'interface avec ce code pour minimiser les allers-retours.
Si vous séparez le code en assemblys dans un environnement .NET Framework, tenez compte des niveaux d'autorisation requis pour chaque élément de code. Vous constaterez que c'est un processus simple : séparez le code qui nécessite des privilèges plus élevés dans un assembly distinct qui lui donne plus de privilèges, tout en laissant la plupart des assemblys restants s'exécuter avec des privilèges inférieurs afin que vous puissiez ajouter plus de gardes autour de votre code. Ce faisant, n'oubliez pas qu'en raison de la pile Code Access Security (CAS), vous limitez les autorisations non seulement de votre propre assembly, mais également de tout assembly que vous appelez.
De nombreuses personnes créent leurs propres applications afin que de nouveaux composants puissent être connectés à leurs produits après les avoir testés et mis à la disposition des clients. Sécuriser ce type d'application est très difficile car vous ne pouvez pas tester tous les chemins de code possibles pour trouver des bugs et des failles de sécurité. Cependant, si votre application est hébergée, le CLR fournit une excellente fonctionnalité qui peut être utilisée pour fermer ces points d'extensibilité. En déclarant un objet d'autorisation ou un ensemble d'autorisations et en appelant PermitOnly ou Deny, vous ajoutez une marque à votre pile qui bloquera l'octroi d'autorisations à tout code que vous appelez. En procédant ainsi avant d'appeler un plug-in, vous pouvez limiter les tâches que le plug-in peut effectuer. Par exemple, un plugin de calcul de paiements échelonnés ne nécessite aucun accès au système de fichiers. Ceci n’est qu’un autre exemple de moindre privilège, par lequel vous vous protégez au préalable. Assurez-vous de noter ces restrictions et sachez que les plug-ins dotés de privilèges plus élevés peuvent utiliser des instructions Assert pour contourner ces restrictions.
8. Soyez conscient des modèles d’échec
et acceptez-les. D’autres détestent autant que vous écrire du code de gestion des erreurs. Il existe de nombreuses raisons pour lesquelles le code peut échouer, et il peut être frustrant d'y penser simplement. La plupart des programmeurs, y compris nous, préfèrent se concentrer sur le chemin d'exécution normal. C’est là que le travail se fait réellement. Effectuons cette gestion des erreurs aussi rapidement et sans douleur que possible, puis passons à la ligne suivante du code réel.
Malheureusement, cette émotion n’est pas sans danger. Au lieu de cela, nous devons accorder plus d’attention aux modèles de défaillance dans notre code. Ce code est souvent écrit avec peu d’attention approfondie et n’est souvent pas entièrement testé. Vous souvenez-vous de la dernière fois où vous étiez absolument sûr d'avoir débogué chaque ligne de code d'une fonction, y compris chaque petit gestionnaire d'erreurs qu'elle contenait ?
Un code non testé conduit souvent à des failles de sécurité. Trois choses peuvent vous aider à atténuer ce problème. Tout d’abord, accordez à ces petits gestionnaires d’erreurs la même attention qu’à votre code normal. Tenez compte de l’état du système lors de l’exécution de votre code de gestion des erreurs. Le système est-il dans un état efficace et sûr ? Deuxièmement, une fois que vous avez écrit une fonction, parcourez-la et déboguez-la minutieusement plusieurs fois, en veillant à tester chaque gestionnaire d'erreurs. Notez que même avec de telles techniques, des erreurs de timing très subtiles peuvent ne pas être découvertes. Vous devrez peut-être transmettre un paramètre d'erreur à votre fonction ou ajuster l'état du système d'une manière ou d'une autre pour permettre à votre gestionnaire d'erreurs de s'exécuter. En prenant le temps de parcourir votre code, vous pouvez ralentir et avoir suffisamment de temps pour voir votre code et l'état de votre système pendant son exécution. En parcourant soigneusement le code dans le débogueur, nous avons découvert de nombreuses failles dans notre logique de programmation. Il s'agit d'une technologie éprouvée. Veuillez utiliser cette technique. Enfin, assurez-vous que votre suite de tests provoque l'échec de votre fonction. Essayez d'avoir une suite de tests qui examine chaque ligne de code de la fonction. Cela peut vous aider à repérer des modèles, en particulier lorsque vous automatisez vos tests et les exécutez à chaque fois que vous créez votre code.
Il y a une chose très importante à dire sur les modes de défaillance. Assurez-vous que votre système est dans l'état le plus sûr possible lorsque votre code échoue. Une partie du code problématique est présentée ci-dessous :
bool accessGranted = true; // Trop optimiste !
essayer {
// Voir si nous pouvons accéder à c:test.txt
nouveau FileStream (@"c:test.txt",
FileMode.Open,
FileAccess.Read).Close();
}
catch (SecurityException x) {
// Accès refusé
accessGranted = faux ;
}
attraper (...) {
// Quelque chose d'autre s'est produit
}
Même si nous utilisons le CLR, nous sommes toujours autorisés à accéder au fichier. Dans ce cas, aucune SecurityException n’est levée. Mais que se passe-t-il si, par exemple, la liste de contrôle d'accès discrétionnaire (DACL) du fichier ne nous autorise pas l'accès ? À ce stade, un autre type d’exception sera généré. Mais en raison des hypothèses optimistes de la première ligne de code, nous ne le saurons jamais.
Une meilleure façon d'écrire ce code est d'être prudent :
bool accessGranted = false; // Soyez prudent !
essayer {
// Voir si nous pouvons accéder à c:test.txt
nouveau FileStream (@"c:test.txt",
FileMode.Open,
FileAccess.Read).Close();
// Si on est toujours là, tant mieux !
accessGranted = vrai ;
}
catch (...) {}
Ce sera plus stable car peu importe la façon dont nous échouons, nous retomberons toujours au mode le plus sûr.
9. L'usurpation d'identité est très vulnérable
Lors de l'écriture d'applications serveur, vous vous retrouverez souvent à utiliser, directement ou indirectement, une fonctionnalité pratique de Windows appelée emprunt d'identité. L'emprunt d'identité permet à chaque thread d'un processus de s'exécuter dans un environnement de sécurité différent, généralement celui du client. Par exemple, lorsqu'un redirecteur de système de fichiers reçoit une demande de fichier sur le réseau, il authentifie le client distant, vérifie que la demande du client ne viole pas la DACL sur le partage, puis attache l'indicateur du client au thread traitant la demande. . pour simuler le client. Ce thread peut ensuite accéder au système de fichiers local sur le serveur en utilisant l'environnement de sécurité du client. C'est pratique puisque le système de fichiers local est déjà sécurisé. Il effectue une vérification d'accès en tenant compte du type d'accès demandé, de la DACL sur le fichier et de l'indicateur d'usurpation d'identité sur le thread. Si la vérification d'accès échoue, le système de fichiers local le signale au redirecteur du système de fichiers, qui envoie ensuite une erreur au client distant. C'est sans aucun doute pratique pour le redirecteur du système de fichiers, car il transmet simplement la requête au système de fichiers local et lui permet d'effectuer ses propres contrôles d'accès, comme si le client était local.
Tout cela est très bien pour une simple passerelle comme un redirecteur de fichiers. Mais les simulations sont souvent utilisées dans d’autres applications plus complexes. Prenons l'exemple d'une application Web. Si vous écrivez un programme ASP non géré classique, une extension ISAPI ou une application ASP.NET et que vous avez la spécification suivante dans son fichier Web.config
<identity impersonate='true'>
alors votre environnement d'exécution aura deux environnements de sécurité différents : Vous aurez un une balise de processus et une balise de fil De manière générale, la balise de fil sera utilisée pour la vérification des accès (voir Figure 3). En supposant que vous écrivez une application ISAPI qui s'exécute dans un processus de serveur Web et que la plupart des requêtes ne sont pas authentifiées, votre balise de fil peut être IUSR_MACHINE, mais votre balise de processus est SYSTEM ! Supposons que votre code puisse être exploité par un mauvais acteur via un débordement de tampon. Pensez-vous qu'il se contentera de fonctionner sous IUSR_MACHINE ? Bien sûr que non. Son code d'attaque appelle très probablement RevertToSelf pour supprimer l'indicateur d'usurpation d'identité dans l'espoir d'augmenter son niveau de privilège. Dans ce cas, il réussira facilement. Il peut également appeler CreateProcess. Il ne copie pas la balise du nouveau processus à partir de la balise d'emprunt d'identité, mais à partir de la balise de processus afin que le nouveau processus puisse s'exécuter en tant que SYSTEM.
Alors comment résoudre ce petit problème ? En plus de garantir qu’aucun débordement de tampon ne se produise en premier lieu, rappelez-vous le principe de la moindre autorisation. Si votre code ne nécessite pas de privilèges aussi grands que SYSTEM, ne configurez pas votre application Web pour qu'elle s'exécute dans un processus de serveur Web. Si vous configurez simplement votre application Web pour qu'elle s'exécute dans un environnement d'isolation moyenne ou élevée, votre balise de processus sera IWAM_MACHINE. Vous ne disposez en réalité d’aucune autorisation, cette attaque n’a donc presque aucun effet. Notez que dans IIS 6.0 (qui deviendra bientôt un composant de Windows .NET Server), le code écrit par l'utilisateur ne s'exécutera pas en tant que SYSTEM par défaut. Partant du principe que les développeurs commettent des erreurs, toute aide que le serveur Web peut fournir pour réduire les autorisations accordées au code est utile en cas de problème de sécurité dans le code.
Voici un autre écueil que les programmeurs COM peuvent rencontrer. COM a une mauvaise tendance à ignorer les threads. Si vous appelez un serveur COM en cours et que son modèle de thread ne correspond pas au modèle du thread appelant, COM effectue l'appel sur un autre thread. COM ne propage pas l'indicateur d'emprunt d'identité sur le thread appelant, le résultat est donc que l'appel est exécuté dans le contexte de sécurité du processus plutôt que dans le contexte de sécurité du thread appelant. Quelle surprise!
Voici un autre exemple des pièges de la simulation. Supposons que votre serveur accepte les requêtes envoyées via des canaux nommés, DCOM ou RPC. Vous authentifiez les clients et empruntez leur identité, en ouvrant les objets du noyau en leur nom par emprunt d'identité. Et vous avez oublié de fermer l'un des objets (comme un fichier) lorsque le client s'est déconnecté. Lorsque le prochain client arrive, vous l’authentifiez et usurpez son identité, et devinez ce qui se passe ? Vous pouvez toujours accéder aux fichiers qui ont été « manqués » par le client précédent, même si le nouveau client n'a pas eu accès au fichier. Pour des raisons de performances, le noyau effectue des contrôles d'accès sur un objet uniquement lors de sa première ouverture. Vous pouvez toujours accéder à ce fichier même si vous modifiez ultérieurement votre environnement de sécurité, car vous usurpez l'identité d'un autre utilisateur.
Les situations mentionnées ci-dessus visent toutes à vous rappeler que la simulation offre une commodité aux développeurs de serveurs, mais que cette commodité comporte de grands dangers cachés. Lorsque vous exécutez un programme avec un indicateur fictif, veillez à porter une attention particulière à votre code.
10. Écrivez des applications que les utilisateurs non administrateurs peuvent réellement utiliser.
C'est en réalité un corollaire du principe de moindre autorisation. Si les programmeurs continuent de développer du code qui nécessite un administrateur pour fonctionner correctement sous Windows, nous ne pouvons pas espérer améliorer la sécurité du système. Windows dispose d'un ensemble très solide de fonctionnalités de sécurité, mais les utilisateurs ne peuvent pas en profiter s'ils doivent être administrateur pour les faire fonctionner.
Comment pouvez-vous vous améliorer ? Tout d’abord, essayez-le vous-même, sans l’exécuter en tant qu’administrateur. Vous découvrirez bientôt la difficulté d’utiliser un programme qui n’a pas été conçu dans un souci de sécurité. Un jour, j'ai (Keith) installé un logiciel fourni par le fabricant de mon appareil portable qui synchronisait les données entre mon ordinateur de bureau et mon appareil portable. Comme d'habitude, je me suis déconnecté de mon compte utilisateur normal, je me suis reconnecté à l'aide du compte administrateur intégré, j'ai installé le logiciel, puis je me suis reconnecté à mon compte normal et j'ai essayé d'exécuter le logiciel. En conséquence, l'application affiche une boîte de dialogue indiquant qu'un fichier de données requis n'est pas accessible, puis affiche un message de violation d'accès. Mes amis, il s'agit du produit logiciel d'un important fabricant d'appareils portables. Y a-t-il une excuse pour cette erreur ?
Après avoir exécuté FILEMON depuis http://sysinternals.com (en anglais), j'ai rapidement découvert que l'application essayait d'ouvrir un fichier de données pour un accès en écriture qui était installé dans le même répertoire que le milieu exécutable de l'application. Lorsque les applications sont installées dans le répertoire Program Files comme prévu, elles ne doivent pas tenter d'écrire des données dans ce répertoire. Program Files a une politique de contrôle d'accès si restrictive pour une raison. Nous ne voulons pas que les utilisateurs écrivent dans ces répertoires, car cela permettrait à un utilisateur de laisser facilement un cheval de Troie à un autre utilisateur pour qu'il l'exécute. En fait, cette convention est l'une des exigences de base en matière de signature de Windos XP (voir http://www.microsoft.com/winlogo [anglais]).
Nous entendons trop de programmeurs donner des excuses pour expliquer pourquoi ils choisissent de s'exécuter en tant qu'administrateur lors du développement du code. Si nous continuons à ignorer ce problème, nous ne ferons qu’empirer les choses. Amis, vous n'avez pas besoin de droits d'administrateur pour modifier un fichier texte. Les droits d'administrateur ne sont pas non plus requis pour modifier ou déboguer un programme. Lorsque vous avez besoin de privilèges d'administrateur, utilisez la fonctionnalité RunAs du système d'exploitation pour exécuter 玎com.asp?TARGET=/winlogo/">http://www.microsoft.com/winlogo [English]).
Nous entendons trop cela. Les programmeurs donnent des excuses pourquoi ils choisissent de s'exécuter en tant qu'administrateur lors du développement du code. Si nous continuons à ignorer ce problème, cela ne fera qu'empirer les choses. La modification d'un fichier texte ne nécessite pas de privilèges d'administrateur. Ou le débogage d'un programme ne nécessite pas de privilèges d'administrateur. privilèges, utilisez la fonctionnalité RunAs du système d'exploitation pour exécuter un programme distinct avec des privilèges élevés (voir la colonne Security Briefs de novembre 2001 [anglais]. Si vous écrivez des outils pour les développeurs, vous avez une responsabilité supplémentaire pour ce groupe. arrêter ce cercle vicieux d'écriture de code qui ne peut être exécuté qu'en tant qu'administrateur. L'objectif doit changer fondamentalement.
Pour plus d'informations sur la manière dont les développeurs peuvent facilement s'exécuter en tant que non-administrateurs, consultez le site Web de Keith à l'adresse http://www.develop. com/kbrown (en anglais). Consultez le livre de Michael Writing Secure Code (Microsoft Press, 2001), qui fournit des conseils sur la façon d'écrire des applications qui fonctionnent correctement dans un environnement non-administrateur.