03.02.99              - H@CKOFF No10 - * Les trous de la secu * - 

¤º°`°º¤ø,¸¸,ø¤º°`°º¤ø,¸¸,ø¤º°`°º¤ø,¸¸,ø¤º°`°º¤º°`°º¤ø,¸¸,ø¤º°`°º¤ø,¸¸,ø¤º°`°º¤ø,¸¸,ø

          _/    _/    _/_/      _/_/_/  _/    _/    _/_/    _/_/_/_/  _/_/_/_/      
         _/    _/  _/    _/  _/        _/  _/    _/    _/  _/        _/
        _/_/_/_/  _/_/_/_/  _/        _/_/      _/    _/  _/_/_/    _/_/_/
       _/    _/  _/    _/  _/        _/  _/    _/    _/  _/        _/
      _/    _/  _/    _/    _/_/_/  _/    _/    _/_/    _/        _/

¤º°`°º¤ø,¸¸,ø¤º°`°º¤ø,¸¸,ø¤º°`°º¤ø,¸¸,ø¤º°`°º¤º°`°º¤ø,¸¸,ø¤º°`°º¤ø,¸¸,ø¤º°`°º¤ø,¸¸,ø

Comment trouver les trous de securite

Texte traduit de l'anglais
par Tobozo.
Texte original
provenant d'ici.

Montage photo par PHILIPPE MURARO


	***********************************************************************
 	*     Yo welcome dans ce Hackoff No 10 consacre a la securite et      *
	* ses trous sur les systemes X (Unix, Linux, etc..). Voici la version *
	* francaise d'un document etrangement ressemblant mais dont le ton a  *
	* ete traitreusement detourne a des fins culturelles. Si vous pensez  *
	* trouver ici une methode pour forcer l'entree d'un serveur Kro$oft,  * 
	* ou d'une autre merde,  appuyez toot sweet sur ATL/F4 et n'y revenez *
	* pas! Ce document est plutot une explication sur comment et pourquoi *
	* il est possible de forcer la securite sur certains serveurs de      *
	* type X, ainsi que quelques explications sur certains bugs connus.   *
	* L'aspirine n'est pas fourni et la qualite n'est pas garantie.       *
	***********************************************************************

Note: Je n'ai trouve aucun trou de securite dans ceux que vous trouverez ici, ce texte n'est donc qu'une pincee de theorie. Il est aussi pauvrement organise et inspire d'une version anglaise se trouvant a l'adresse suivante http://www.dnaco.net/~kragen/security-holes.html. Les suggestions sont bienvenues ainsi que les infos sur d'eventuelles erreurs qui se seraient aisement glissees dans le contenu (peut etre en emprumtant un trou de securite qui n'est pas cite ici).

Si un programme a un bug qui se manifeste uniquement dans des circonstances extremes, on peut parler d'ennuis mineurs. On apprend a contourner ou a eviter ces circonstances extremes et le bug n'est plus un probleme. Il est possible de dupliquer le processus de taquinage du bug en ecrivant un programme qui le reproduit, c'est une autre application de la programmation.

Mais il arrive que des programmes soient impliques dans des liens de securite. Ils prennent des donnees entrantes d'autres programmes qui n'on pas les memes restrictions d'acces.

Quelques exemples : un logiciel de lecture de courrier prend des donnees provenant de tous les expediteurs et en meme temps accede a l'affichage, ce qui n'est pas le cas des expediteurs. Les piles TCP/IP de deux machines connectees a l'internet peuvent echanger des donnees auxquelles l'utilisateur moyen n'a pas acces depuis sa couche session.

Tous les programmes qui agissent ainsi doivent etre concus avec prudence. L'existence d'un simple bug dans le code peut permettre un potentiel acces a une personne -- non confiante -- d'effectuer des operations non autorisees. Un bug qui a cette propriete est appele "un trou de securite" ou plus vulgairement "vulnerabilite".

Voici quelques categories de trous.

Problemes psychologiques

Quand un programmeur se penche sur un projet lors de l'elaboration d'un logiciel classique, son but est de rendre certaines choses possibles (si l'utilisateur fait les choses correctement). Lors de l'elaboration d'un logiciel plus sensible au niveau de la securite, le but est de rendre certaines choses impossibles, sans se soucier de ce qu'un utilisateur non confiant puisse faire. Cela signifie que certaines parties du logiciel doivent fonctionner parfaitement et en toutes circonstances.

Les cryptologistes et les programmeurs en temps reel sont habitues a travailler dans ces circonstances. La plupart des autres developpeurs et analystes ne le sont pas, et sont plutot habitues a travailler d'une facon qui rend leurs logiciels assez faibles au niveau de la securite (pas besoin de donner de noms).

Detournement de trou

La plupart des trous viennent de l'execution d'un meme programme dans differents environnements. Ce qui a l'origine n'etait qu'un petit ennui -- ou parfois une convenance -- devient un trou de securite.

Par exemple, une machine est equipee d'un interpreteur PostScript qui a l'origine etait prevu pour offrir un apercu avant impression des documents. L'interpreteur n'a pas de role primordial quant a la securite; l'interpreteur PostScript ne possede pas de capacites que vous ne possedez deja. Mais en supposant qu'on l'utilise pour visualiser les documents d'autres utilisateurs, des utilisateurs inconnus, voire non-confiants, alors oudain la presence d'un interpreteur PostScript devient une menace !! N'importe qui peut vous envoyer un document qui formattera votre HDD ou meme copier certains fichiers a un endroit ou ils deviendront accessibles.

C'est une cause de vulnerabilite dans la plupart des piles TCP/IP Unix -- developpees dans un reseau ou tous les utilisateurs etaient supposes dignes de confiance et maintenant deployees sur des reseaux ou personne ne l'est.

Meme probleme avec sendmail. Avant de devenir celebre, ce bug offrait une ouverture beante a qui voulait s'introduire. (qui a dit qu'unix n'etait pas seduisant ?).

A un niveau plus subtil, certaines fonctions qui sont parfaitement securisees quand elles ne croisent pas des liens de confiance peuvent devenir desastreuses dans le cas inverse. L'instrucion gets() en est un exemple parfait. En utilisant gets() dans une situation de controle d'entree, on fait appel a un tampon memoire plus grand que celui prevu initialement, et le tour est joue. Si le programme est crashe en lui donnant trop de donnees en entree, la solution est de "ne pas le faire" ou encore d'elargir le tampon et recompiler.

Mais quand la donnee vient d'une source qui n'est pas digne de confiance, gets() peut depasser la capacite du tampon et forcer le programme a faire quasiment n'importe quoi. Le resultat se manifeste generalement par un crash, mais il est souvent possible d'inserer des donnees qui vont faire executer des codes executables par le programme.

Ce qui nous amene a . . .

Fuites des tampax (trous dus aux debordements des tampons)

Un debordement de tampon se produit quand on ecrit une ligne (generalement de caracteres) en un seul jet, sans tenir compte de la limite en longueur, ce qui produit un ecrasement de donnees dans la zone memoire qui se situe juste apres cette limite.

Des problemes de securite sur des debordements de tampon peuvent se produire dans plusieurs situations :

  • en lisant des donnees entrantes directement dans le tampon;
  • en copiant des donnees entrantes depuis un grand tampon vers un petit tampon;
  • en effectuant differentes operations d'entrees dans une ligne tampon;

Ne pas oublier : ce n'est pas un trou de securite si l'entrant est considere comme deja digne de confiance. Simplement il s'agit d'une potentielle source d'ennui.

Ce probleme particulièrement méchant sur la plupart des environnements Unix; si l'alignement des donnees est une variable locale presente dans certaines fonctions, il est plus que probable que l'adresse de retour se situe quelque part après elle sur la pile. Ceci semble être le trou à la mode à exploiter; des milliers et des milliers de trous de cette nature ont été trouvés dans les dernières années.

Les tampons d'autres applications peuvent egalement être depasses et ainsi produire des trous de sécurité -- en particulier si elles sont proches des pointeurs de fonctions ou d'informations cruciales.

Les choses a rechercher sont :

  • les fonctions dangereuses sans verification de limites : strcpy, strlen, strcat, sprintf, gets;
  • les fonctions dangereuses avec verification de limites : strncpy, snprintf -- parmi ces dernieres, certaines omettront d'ecrire un NULL a la fin de la chaine, ce qui peut aboutir a une copie ulterieure du resultat pour y inclure d'autres donnees -- des donnees sensibles -- et eventuellement crasher le programme; ce probleme n'existe pas avec strncat, et il n'est pas certain qu'il existe egalement avec snprintf, mais il est sur qu'il existe avec strncpy;
  • une mauvaise utilisation de strncat peut aboutir sur l'ecriture d'un octet NULL apres la fin de la chaine.
  • le crash eventuel d'un programme implique dans la securite (tous les crashs sont provoques par le bug d'un pointeur et dans la majorite des cas, ces bugs viennent d'un debordement de tampon...)
  • En essayant de nourrir un programme implique dans la securite avec des grosses entrees -- en variables d'environnement, en parametrant des lignes de commandes, dans un fichier en lecture, ou en connexion reseau (partant du principe que ces informations viennent d'une source non digne de confiance). Meme si le programme analyse les paquets en entree, il suffit d'envoyer des paquets plus gros (enormes). Surveiller les crashs et regarder a quelle adresse du prog se situe ledit crash pour verifier si cela correspond aux paquets envoyes.
  • controleurs de limites incorrectement regles. Si ces controles sont disperses au milieu de centaines de lignes de codes au lieu d'etre centralises en deux ou trois endroits, il est fort probable (et c'est une chance) que l'un d'eux soit faux.

Une solution de couverture est de compiler tous les programmes impliques dans la securite avec un controleur de limite activé.

Confusion politique

Quand on donne un nom de fichier a ouvrir a un programme, le programme demande a l'OS d'ouvrir ce fichier. En se basant sur le fait que ce prog s'execute avec vos privileges, si la meme demande est faite sans les privileges, l'OS refusera l'acces. Pas de probleme.

Cependant en donnant un nom de fichier a un prog implique dans la securite -- un script CGI, un programme SetUid ou SetGid, un serveur reseau -- il ne va pas necessairement se relier aux systemes de protection internes a l'OS. Simplement parce qu'il peut faire des choses que vous ne pouvez pas. Dans le cas d'un serveur web, les differences sont minimales (acces a des infos privees sur des fichiers).

La plupart des programmes de ce type font une verification sur les donnees recues. Ils peuvent ainsi tomber dans plusieurs pieges :

  • Ils effectuent une verification sur un delai qui peut etre facilement concurrencable. Si le programme commence par faire un stat() sur le fichier pour verifier les droits en ecriture, et ensuite (selon votre choix) fait un open() dessus, le delai de verification est suffisant pour s'octroyer ou modifier les droits a condition d'effectuer les operations simultanement. Une solution possible est de faire un stat() ou un lstat() sur le fichier avant de l'ouvrir, de l'ouvrir sans le detruire.
  • Ils verifient en analysant le nom du fichier, mais la methode est differente de celle de l'OS, ce qui a souvent pose des problemes sur les serveurs web de chez microbof; l'OS fait une analyse methodique et sophistiquee du nom du fichier pour determiner le type d'acces. Les serveurs Web font la meme verification sauf quand la requete fait reference a un acces en lecture. Si l'acces par defaut laisse le droit en lecture, alors changer le nom du fichier peut "duper" le serveur qui croira a un different type de fichier tout en vous laissant un acces en lecture sur un fichier de nom different mais qui finalement pointe sur un fichier dont l'acces n'est normalement pas autorise.

    C'est un double probleme d'analyse, il sera explique plus bas car il est egalement implique dans situations d'echec d'ouverture.

  • Ils verifient d'une maniere tellement complexe que la methode elle meme comporte ses trous, en general a cause du manque de comprehension de la part de l'auteur.
  • Ils ne verifient pas tout (probleme assez repandu).
  • Ils verifient avec une methode trop simple. Par exemple, les vieux serveurs web Unix vous laissent telecharger quasiment n'importe quel fichier dans le repertoire public_html d'un utilisateur (a moins que l'OS ne l'interdise). Mais en faisant un symlink ou un hardlink sur un fichier d'un autre utilisateur, il devient possible d'y avoir egalement acces.

A plusieurs niveaux, les programmes qui ont des privileges que vous n'avez pas echouent generalement dans leur role de limitation. setfsuid(), setreuid(), etc., sont des commandes qui peuvent aider.

Un autre probleme connu : des librairies standard verifient les variables d'environnement pour ouvrir les fichiers et ne sont pas assez evolues pour donner des privileges au meme moment (reellement!). Ce qui nous force a reclasser les analyses de fichiers pour verifier l'integrite.

Certains OS purgent la memoire avec les mauvais privileges, donc avec un crash setuid, il devient aise d'ecraser un fichier que le proprietaire du programme pourrais ecraser aussi. (les vidages memoire avec les privileges utilisateur allouent souvent des droits en lecture qui ne font pas partie des droits originaux).

Echecs d'ouverture

La plupart des systemes impliques dans la securite echouent dans leur mission en certaines circonstances. Cela peut se produire de deux differentes facons :
  • Ils peuvent donner des droits qu'ils ne devraient pas donner (echec d'ouverture)
  • Ils peuvent refuser un acces qu'il devraient autoriser (echec de fermeture)

Par exemple, un porte electronique qui reste fermee grace a un processus magnetique est en echec d'ouverture en cas de panne de courant -- quand l'electro aimant n'est plus alimente la porte s'ouvre -- Un porte electronique qui se bloque grace a un piston activable par aspiration lors d'une ouverture est une porte a echec de fermeture -- sans alimentation, la porte est condamnee --

Les scripts CGI executent generalement d'autres programmes en leur envoyant des donnees sur les utilisateurs dans leurs lignes de commandes. Afin d'eviter au shell d'interpreter certaines de ces donnees (sur un systeme Unix) comme des instructions d'un programme ou comme acces a des fichiers etc, le script CGI supprime les caracteres non habituels -- comme "<", "|", ' ', '"' etc.. Cela est rendu possible en echec d'ouverture en creant une liste de "mauvais caracteres" qui se font supprimer automatiquement lors de la reception. Si par erreur il en manque un dans la liste, c'est un trou de securite. Ce trou peut etre cree en faisant une liste en echec de fermeture de "bons caracteres" qui ne pourront etre supprimes du script. Dans ce cas un oubli est plutot un inconvenient qu'un trou de securite. Un exemple (en Perl) a cette adresse : http://www.geek-girl.com/bugtraq/1997_3/0013.html.

Les systemes a echec de fermeture conviennent mieux a la securite que les systemes a echec d'ouverture, meme s'ils plantent souvent.

La plupart des programme de securite sur Mac ou Kro$oft sont bases sur l'echec en ouverture -- si le programme peut etre neutralise, l'acces devient total. Par contre, si on desactive le programme "login" sur un systeme Unix, inutile d'esperer y rentrer.

Manque de ressources

Beaucoup de programme sont ecrits avec la pretention evidente qu'assez de ressources resteront disponibles (voir problemes psychologiques plus haut). Mais personne ne songe a ce qui va se passer quand les ressources deviennent rares, et souvent le pire arrive...

Un petit coup d'oeil...

  • ce qui arrive en cas de manque de memoire et d'echec d'allocation de plage est un retour NULL de malloc ou new.
  • s'il est possible pour un utilisateur "untrusted" de surcharger les ressources (ex : denial-of-service, bug connu du programme meme s'il est pris en charge)
  • si le programme vient a manquer de fd (et dans l'eventualite) open() retournera la valeur -1
  • si le programme ne peut pas executer fork(), ou si les child sont fermes pendant l'initialisation du process suite a un manque de ressources.

Faire confiance a des canaux non confiants

En envoyant des passwords en clair sur un reseau Ethernet LAN avec des utilisateurs "untrusted", en creant un fichier accessible en ecriture par tous et en essayant ulterieurement de relire ce fichier, en creant un fichier dans le rep /tmp avec O_TRUNC mais sans O_EXCL, etc cela est comparable a faire confiance a un intermediaire non confiant en lui laissant faire quasiment ce qu'il veut. Si un attaquant peut se substituer a ce canal non confiant, il peut alors forcer la securite en alterant les donnees de ce canal, et ce sans demande d'acces (ce qui peut avoir des consequences facheuses -- si l'attaquant cree son fichier dans /tmp en faisant un lien symbolique sur le fichier trusted, cela peut autoriser l'eventualite d'une destruction du contenu d'un fichier privilegie au lieu d'une simple creation de fichier temporaire. gcc contient certains bugs de ce type, ce qui peut laisser l'occasion a un attaquant d'inserer du code dans les programmes que vous compilez) , meme s'ils ne peuvent faire cela, ils sont neanmoins capables de lire des donnees auxquelles ils n'ont normalement pas acces.

Defauts debiles

Si certains reglages par defaut ne sont pas securises c'est qu'en general personne n'y prete attention. Par exemple en installant un pack rpm et en creant des fichiers de config accessibles a tous, rien n'est detectable avant de partir activement a la recherche des trous de securite sur le systeme.

Grosses interfaces

Plus l'interface est simple, plus elle est securisee. Logiquement, une maison avec une seule porte ne comporte qu'un seul risque d'intrusion, et partant du principe qu'il faut fermer a clef en sortant ou avant de se coucher, le risque d'oubli est proportiennellement plus grand quand on considere un plus grand nombre de portes d'entrees.

Ainsi les serveurs reseau tendent a etre plus securises que les programmes de type setuid. Ces derniers rappatrient toutes sortes d'infos de sources souvent indignes de confiance -- variables d'environnement, descripteurs de fichiers, carte de memoire virtuelle, lignes de commandes et probablement meme des entrees de fichiers. Les serveurs reseau ne rappatrient que des entrees reseau en socket (et parfois des entrees de fichiers)

qmail est un exemple de petite interface de securite. On peut considerer qu'une tres faible partie du programme (10 lignes de code au grand maximum) s'execute en tant que root. Le reste s'execute en tant qu'utilisateur special qmail ou en tant que recipient mail.

En interne a qmail, la routine de verification de debordement memoire est centralisee en deux petites fonctions, au travers desquelles passent toutes les fonctions utilisees pour modifier les chaines. C'est un autre exemple d'interface de securite minimisee. Le pourcentage d'erreur de verification et beaucoup plus faible.

Plus il a de daemons installes, plus l'interface de securite entre l'internet et la machine sera vaste.

Avec un firewall, l'interface securite entre le reseau et l'internet se reduit a une machine seulement.

La difference entre la visualisation d'une page html non confiante et une page javascript non confiante se situent aussi au niveau d'une machine; en effet l'interpreteur JavaScript possede des routines bien plus lourdes que pour l'interpreteur HTML

Programmes frequemment exploites

Les programmes frequemment exploites dans le passe etaient connus pour leur trous et le seront probablement aussi dans l'avenir pour les memes raisons. Le /bin/mail a ete remplace sur BSD par mail.local justement pour cette raison.

En matiere d'audit, il est toujours de bon alloi d'examiner de tres pres ce type de programme, mais parfois il est plus judicieux de reecrire totalement le code et de ne pas utiliser ledit programme en premier lieu.

Options de securite mal definies

Tous les systemes securises sont divises en plusieurs compartiments de securite. Par exemple, un systeme Linux a plusieurs compartiments connus sous le nom d' "utilisateurs", et un compartiment connu sous le nom de "kernel", ainsi qu'un compartiment connu sous le nom de "reseau" --lequel est subdivise en compartiments "connexions reseau". Des relations de confiance sont definies entre ces differents compartiments qui font reference au setup et a la config du systeme. (par exemple l'utilisateur toto fait confiance a la connexion reseau apres envoi du mot de passe)

Cette relation de confiance doit etre renforcee sur chaque interface entre les compartiments de securite. Dans le cas d'une personne qui execute une librairie de terminal, il y aura une relation supposee exclusive en lecture seule avec la base de donnee des librairies. Afin de refuser l'acces a l'unix shell dans tous les cas, il faudra faire preuve d'imagination ou lire entre ces lignes (on se comprend).

Mirabilis ICQ fait confiance a tout l'internet pour envoyer des infos correctes sur les utilisateurs, ce qui en fait un programme absolument pas securise.

A une certaine epoque, les capsules TCP faisaient confiance aux donnees provenant d'un reverse-dns, en les envoyant vers le shell (mais plus maintenant).

Netscape Communicator insere parfois un mot de passe en clair dans l'url elle meme stockee dans la liste de l'historique, quand la requete est envoyee au travers d'un proxy. Les programmes JavaScript et serveurs web ont acces a ces donnees.

Les cas negliges

Ne faites jamais confiance a la logique. les ensembles if-else et switch-case sont dangereux car difiiciles a tester. Si vous trouvez une portion de code que personne n'a jamais executee, c'est qu'elle est certainement incorrecte. Si vous trouvez une combinaison logique de flux de donnees -- par exemple, en considerant deux routines, chacune executant une ou deux taches, et qu'en sortie de la premiere se trouve la seconde, cela donne 4 combinaisons -- qui n'a pas ete testee, cela peut aussi etre un trou.

Regarder les "else" quand vous trouvez des "if". Par defaut regarder les etats switch, en s'assurant qu'ils sont parametres en echec de fermeture.

gcc -pg -a fait generer au programme un fichier bb.out qui peut aider a determiner a quel point les tests sont effectif dans leur tache de couverture de toutes les branches du code.

Il est facile de croire que les derniers trous de secu decouverts l'ont ete par cette methode...

Stupidite en capitales

Beaucoup de gens font confiance a un code que quelques personnes seulement ont deja revu. Quelques personnes seulement, ca n'est pas assez et dans ce cas le code contient certainement des bugs. Si le code en question joue un role dans la securite, c'est certainement une bonne occasion pour casser ladite securite. 3Com par exemple a diffuse des hubs CoreBuilder et SuperStack II qui avaient une "porte secrete", sans prevenir leur client (a la derniere minute).

Problemes avec le document

Orthographe/fiabilite/experience

Ce texte a ete transcrit/redige sans aucune experience pratique, il ne s'agit ni d'un howto ni d'une reference en la matiere. De plus la langue anglaise comportant ses caprices, il est possible d'y trouver des erreurs sur les termes (envoyer un mail si des corrections sont a faire).

Mais pour ceux qui debutent dans le securite sur Linux, cela peut neanmoins representer un point de depart.

Quelques liens en anglais

SunWorld Online a fait un article sur la Creation des programmes de securite. Sun n'etant pas specialiste en matiere de securite, il y a des infos a prendre et a laisser.

BUGTRAQ annonce presque en direct la decouverte des nouveaux trous de la securite avec tous les details. geek-girl.com en garde quelques archives depuis 1993. Tres utile pour apprendre sur les trous de la secu et surtout redoutable pour avoir la liste de tous les trous connus.

Des infos sur les methodes de "bricolage" du code par Adam Shostack (notamment utilise par certaines entreprises pour revoir les codes des firewalls) : http://www.homeport.org/~adam/review.html.

Prevention (by COPS): http://www.homeport.org/~adam/setuid.7.html, un guide sur comment et pourquoi prevoir les trous de securite.

John Cochran de l'EDS diffuse la checklist d'AUSCERT : ftp://ftp.auscert.org.au/pub/auscert/papers/secure_programming_checklist

Texte traduit de l'anglais
par Tobozo.
Texte original
provenant d'ici.