• Aucun résultat trouvé

Détection des attaques contre les données de calcul

2.2 Un modèle de détection déduit du code source

2.2.2 Détection des attaques contre les données de calcul

P ⇒ P , {P } C {Q} , Q ⇒ Q

{P } C {Q}

Il s’agit de la règle de conséquence. Elle permet de déduire des propriétés précédentes d’autres propriétés plus complexes. Prenons comme exemple le triplet suivant : {x+1 = 3} y := x+1 {y = 3}. Posons la relation d’équivalence suivante : x + 1 = 3 ⇔ x = 2. On peut alors en déduire que {x = 2} y := x + 1 {y = 3}.

{B ∧ P } CT {Q} , {¬B ∧ P } CF {Q} {P } if B then CT else CF endif {Q}

Il s’agit de la règle de condition. Dans la branche CT la pré-condition P est vérifiée et B est vraie tandis que dans la branche CF la pré-condition P est aussi vérifiée et B est faux. Par exemple, si on considère les deux triplets {true ∧ x = 0} y := x {y = 0} et {true ∧ x 6= 0} y := 0 {y = 0} alors on peut en conclure que {true} if x = 0 then y := x else y := 0 {y = 0}.

{P ∧ B} C {P }

{P } while B do C done {¬B ∧ P }

Il s’agit de la règle de boucle. Lorsque C s’exécute, la précondition est vérifiée et B est vraie tandis que la post-condition implique la pré-condition et qu’en sortie B est faux. P est appelé invariant de boucle. Par exemple, si on considère le triplet suivant {x ≥ 0 ∧ x < y} x := x + 1 {x ≥ 0}, alors on peut en conclure que {x ≥ 0} while x < y do x := x + 1 done {x ≥ 0 ∧ ¬(x < y)}.

2.2.2 Détection des attaques contre les données de calcul

Les attaques ciblant les données de calcul peuvent engendrer des compromissions aussi sévères que les attaques ciblant les données de contrôle pour divers schémas de vulnérabilités existants (voir section 2.1.2). Parmi les vulnérabilités utilisées en exemple, on trouve notamment une vulnérabilité qui touche le très déployé serveur FTP libre WU-FTPD [WUFTP] (pour les versions 2.6.0 ou antérieures). Cette faille est due à la portion de code responsable de la journalisation [C0133]. En effet, ce code ajoute des entrées dans le journal sans utiliser de chaînes de formatage. Là encore, il s’agit d’un schéma de vulnérabilité bien connu. Si une entrée contient des données contrôlables par le client, alors un utilisateur malveillant peut utiliser cela pour modifier directement la mémoire du processus.

2.2.2.1 Exploitation de la vulnérabilité

La figure 2.2 (colonne de gauche) est un exemple de code contenant une vulnéra-bilité similaire à celle décrite précédemment. Dans cet exemple, la vulnéravulnéra-bilité se situe à la ligne 10. Cette ligne de code affiche sans utiliser de chaînes de formatage l’entrée utilisateur reçue à la ligne 07. En conséquence, un utilisateur malveillant peut fournir en entrée du programme une chaîne de formatage lui permettant d’avoir un accès direct en écriture à la mémoire du processus. Dans ce cas précis, c’est la variable uid qui se trouve être une cible de choix.

01 int main(int argc, char **argv) 02 { 03 char buf[64]; 04 uid_t uid; 05 uid = atoi(argv[1]); 06 seteuid(uid); 07 while(fgets(buf, 64, stdin)) 08 { 09 seteuid(0); 10 printf(buf); 11 seteuid(uid); 12 } 13 }

01 int main(int argc, char **argv) 02 { 03 char buf[64]; 04 uid_t uid; //@ {true} 05 uid = atoi(argv[1]); //@ {uid==atoi(argv[1])} 06 seteuid(uid); //@ {uid==atoi(argv[1])} 07 while(fgets(buf, 64, stdin)) 08 { //@ {uid==atoi(argv[1])} //@ AND {s} //@ AND {s -> buf[0] != ’\0’} //@ {uid==atoi(argv[1])} //@ AND {buf[0] != ’\0’} 09 seteuid(0); //@ {uid==atoi(argv[1])} //@ AND {buf[0] != ’\0’} 10 printf(buf); //@ {uid==atoi(argv[1])} //@ AND {buf[0] != ’\0’} 11 seteuid(uid); 12 } //@ {uid==atoi(argv[1])} //@ AND {buf[0]==’\0’} 13 }

Figure 2.2 – Exemple d’annotations de Hoare dans un programme en langage C

Effectivement, en forçant sa valeur à zéro par exemple (root), un attaquant peut élever son niveau de privilèges, sans corrompre le flot d’exécution, lors de l’appel à la fonction seteuid à la ligne 11. Cet exemple montre bien comment une attaque contre les données de calcul peut violer des contraintes que les variables devraient normalement respecter. Ainsi, la variable uid devrait être constante durant toute l’exécution de la boucle (de la ligne 07 à la ligne 12) et être égale à la valeur qui lui a été assignée à la ligne 05. Afin de pouvoir détecter ce type d’attaque à l’exé-cution du programme, nous souhaitons construire ce type de contraintes de manière automatique.

2.2.2.2 Application de la logique de Hoare

Nous allons maintenant appliquer la logique de Hoare à l’exemple précédent pour déterminer s’il est possible de détecter ce type d’attaque grâce au code source. L’anal-yse statique du code source d’un programme afin travailler avec des prédicats Hoare est une approche déjà utilisée par les outils de vérification de programme. Cepen-dant, dans notre cas, nous ne pas voulons vérifier que le programme respecte bien les propriétés exprimées par des prédicats fournis en entrée. Ce que nous voulons, c’est déduire de manière automatique des propriétés.

Pour illustrer cette approche, nous avons construit les différents prédicats qui peuvent être déduits du code source en exemple dans la figure 2.2 (colonne de droite). Dans cet exemple, on suppose que l’appel à la fonction printf à la ligne 10 ne peut modifier aucune donnée interne au programme mais uniquement écrire sur la sortie standard. En reposant sur cette hypothèse, on obtient ainsi sur la valeur de la variable uid, utilisée à la ligne 11, la pré-condition suivant : uid == atoi(argv[1]) AN D buf [0]! = 0. Ce prédicat correspond exactement à la contrainte que nous souhaiterions vérifier à cet endroit précis du code afin de détecter une attaque sur la variable uid.

En effet, si nous vérifions durant l’exécution du programme que cette contrainte est bien respectée juste avant l’appel à la fonction setuid à la ligne 11, alors nous pouvons détecter une élévation de privilèges illégale par corruption de la variable uid. Cet exemple illustre bien le fait que des contraintes permettant de détecter des attaques contre les données de calcul peuvent être déduites du code source de l’application. Toutefois, les approches traditionnelles qui utilisent la logique de Hoare reposent sur la génération manuelle de prédicats qui sont ensuite vérifiés par un assistant de preuve.

De plus, il faut savoir que construire de manière automatique la spécification implémentée par un programme en utilisant les règles d’inférence de Hoare peut être très difficile voire impossible dans le cas général. C’est particulièrement le cas pour certains programmes complexes mettant en œuvre des règles d’implication complexes ou reposant sur des résultats mathématiques difficiles dont la démonstration est déjà hors de portée des assistants de vérification.

C’est pourquoi nous nous concentrons dans la section qui suit sur les techniques d’analyse statique qui sont utilisables pour construire de manière automatique notre modèle de comportement normal orienté autour des variables.