Injection de code

Un article de Haypo.

Retour aux articles relatifs à la sécurité

Sommaire

[modifier] Introduction

L'injection de code est une technique consistant à détourner l'utilisation normale d'un programme pour exécuter un code ou une commande arbitraire. Cette méthode peut prendre de nombreuses formes.

[modifier] Exemple en PHP

Programme affichant la ligne contenant un identifiant dans le fichier /etc/passwd :

<?php
$name = trim($_SERVER['argv'][1]);
if ($name != '')
    system("grep $name /etc/passwd");
else
    echo "Veuillez passer un identifiant en argument.\n";
?>

Usage normal :

$ php test.php haypo
haypo:x:1024:1000:Victor STINNER,,,:/home/haypo:/bin/bash

Injection de code :

$ php test.php 'notfound /etc/passwd && uname -a #'
Linux haypopc 2.6.12.4 #6 Thu Sep 8 01:26:27 CEST 2005 i686 GNU/Linux

On a réussi à exécuter la commande « uname -a » au lieu d'exécuter la commande initialement prévue (grep $name /etc/passwd).

[modifier] Traquer les failles

Pour pouvoir injecter du code, le minimum est de pouvoir envoyer des données au programme, soit. Bon, en même temps, je connais peu de programme (aucun ?) qui n'utilisent aucune donnée en entrée.

[modifier] Sources de données

Il existe plusieurs sources de données :

  • Événements clavier et souris
  • Pipe Unix
  • Donnée en provenance d'un réseau (socket)
  • Variable d'environement (on y pense pas souvent)
  • Signaux : ce n'est pas à proprement parler d'une source de donnée, mais on peut agir sur un programme
  • etc.

Pour un site web :

  • URL qui peut contenir des variables GET
  • Variables POST (passées dans la requête HTTP)
  • Cookies (passés dans la requête HTTP)
  • Différents champs passés dans la requête HTTP, comme par exemple Referrer
  • etc.

Si on possède le code source du programme (cas le plus simple), il suffit d'en analyser le code pour traquer les failles.

[modifier] Analyse de boîte noire

Si on possède pas le code source, il faut ... le deviner :-) Pour cela, on envoie des données auquelles le programme ne s'attend pas :

  • Chaîne vide
  • Chaîne contenant des caractères nuls
  • Chaîne trop courte / trop longue
  • etc.

Il faut alors espérer que le programme est assez bavard (cf. fuite d'information) pour arriver à comprendre comment il est construit.

Voir également l'article sur la rétro-ingénierie (analyse de programme dont on ne connait pas le code).

[modifier] Fuite d'information

Lorsqu'on n'a pas un accès au code source d'un programme qu'on veut analyser, ou même pas d'accès au programme en mémoire, on peut toujours traquer les fuites d'informations. Elles sont plus ou moins volontaires.

[modifier] Exemple : Erreur PHP

Le langage PHP est par exemple bien trop bavard lorsqu'il affiche une erreur. Il donne à la fois le chemin complet du script ayant provoqué l'erreur, mais également la ligne dans le script, et éventuellement la fonction mise en cause. Exemple :

Warning: mssql_num_rows(): supplied argument is not a valid
MS SQL-result resource in d:\inetpub\test\include\db.inc on line 54

Ce qu'on apprend par ce simple message d'avertissement :

  • Le site web utilise un serveur Microsoft SQL
  • Le serveur tourne sous Windows (chemin débutant par "d:\" et utilisation d'anti-slash dans le chemin)
  • La racine du site est certainement « d:\inetpub\test\ »
  • Il existe un fichier « d:\inetpub\test\include\db.inc » qui contient certainement de fonctions d'accès à la base de donnée

[modifier] Exemple : Injection de SQL

Dans un site utilisant PHP avec du SQL (souvent MySQL), on peut trouver des failles par injections SQL simplement en écrivant une apostrophe dans un champ de saisie ou dans une URL.

Exemple en remplaçant l'identifiant valant un par « ' » dans une URL :

You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version
for the right syntax to use near '\ at line 1
(SELECT id, nom, link FROM forum WHERE id=\';)

Informations :

  • Alors là c'est génial, on a la requête SQL en entier !
  • On voit que notre identifiant est simplement concaténé à la fin de la requête.
  • Et on voit bien que MySQL est utilisé

Voir aussi l'article sur l'injection de SQL.

[modifier] Exemple : Serveur web

Autre exemple : trouver le type de serveur web utilisé par site internet. Souvent, il suffit de provoquer une erreur 403 (accès refusé) ou une erreur 404 (page introuvable). On peut pour cela demandez l'URL « http://www.example.com/xxxxxxxxxx ». Exemple de résultat :

Not Found
The requested URL /xxxxxxxxxxxx was not found on this server.
Apache/1.3.33 Server at www.example.com Port 80

Informations recueillies :

  • C'est un serveur Apache
  • Sa version est très certainement la version 1.3.33
  • Le serveur est en écoute sur le port 80 ... bon, ça on s'en doutait un peu :-)

Avec le nom de l'application et son numéro de version, on peut facilement trouver des failles, et même des exploits près à l'emploi !

Voir aussi l'article sur nmap, logiciel qui est capable de retrouver le numéro de version d'un serveur.

[modifier] Exemples plus élaborés

La fuite d'information est un sujet très à la mode, de nombreuses recherches s'y consacrent. En particulier, le but recherché est de casser des clés RSA, ou plutôt de voler de clés privées. Exemples de techniques plus élaborées :

  • Surveiller la consommation d'énergie
  • Surveiller l'émission sonore d'un composant électronique
  • Time attack (mesurer le temps de réponse, varie selon les entrées)
  • Nombre de cache miss (attaque de l'hyper threading Intel)
  • etc.

[modifier] S'en protéger

Si vous écrivez un programme, pensez donc à limiter les informations délicates envoyées, en particulier pour les messages d'erreurs.

Certains programmes proposent également de masquer des informations. Le serveur web Apache permet par exemple de ne pas indiquer son numéro de version.

[modifier] Failles de sécurité

[modifier] Erreur de débordement : buffer overflow et integer overflow

Les erreurs de programmation les plus courantes sont les erreurs de débordement. Les cas les plus connus sont :

  • buffer overflow (débordement de tampon) : la chaîne envoyée au programme est plus longue que prévue
  • integer overflow (débordement de nombre entier) : les nombres entiers signés de taille fixe (8, 16, 32, 64 bits ou plus) ont la facheuse tendance à passer dans les nombres négatif lorqu'un calcul dépasse la valeur positive maximale

Ces failles sont très courante, allez faire un tour sur frsirt pour vous en rendre compte. Elles permettent d'injecter du code arbitraire.

[modifier] Fonctions (de la libc) connues pour être faillibles

De nombreuses fonctions sont potentiellement ouvertes pour des failles de débordement de tampon. Liste volontairement incomplète : strcpy(), strcat(), sprintf(), vsprintf(), getwd(), gets(), realpath(), ...

[modifier] Contourner la protection du type "fichier .txt uniquement"

De nombreux scripts pensent que le test sur l'extension du fichier passé en argument est suffisant pour protéger leur script. Mais il suffit d'ajouter une chaîne spéciale pour contourner ce test.

Le cas le plus classique est d'ajouter un caractère NULL (code zéro), ce qui est interprété comme la fin de la chaîne pour certaines fonctions (en particulier strlen et strcmp).

[modifier] Contourner la protection anti-"../"

Pour éviter qu'on se promène dans l'arborescence d'un serveur alors qu'il est possible d'afficher un fichier d'un répertoire donné, la première protection à laquelle on pense est d'interdir les requêtes contenant "../". Il est possible de contourner ce test :

  • Utiliser des alternatives à "../" peu communes mais valides (j'ai pas d'exemple sous la main)
  • Utiliser un chemin absolu

En PHP, on peut souvent obtenir le chemin absolu en provoquant une erreur PHP (sur le script qu'on chercher à exploiter ou un autre script) car PHP affiche le chemin absolu du script ayant provoqué l'erreur. Exemple :

Warning: mssql_num_rows(): supplied argument is not a valid
MS SQL-result resource in d:\inetpub\test\include\db.inc on line 54

[modifier] Spécifique à Perl

La fonction open() permet d'exécuter une commande, et le fichier ouvert sera le résultat de cette commande.

Exemple minimaliste (test.pl) :

#!/usr/bin/perl
open(FILE, $ARGV[0]);
foreach (<FILE>) { print $_; }
close(FILE);

Utilisation de la faille :

perl test.pl 'uname -a|'

[modifier] Autres

Il existe encore d'autres types de faille, comme par exemple :

  • Heap Overflow
  • Format String Attacks

[modifier] Limiter les risques d'injection de code

[modifier] Audit (automatique) du code source

[modifier] Protéger la pile

Le pile est souvent utilisée pour stocker du code machine qui sera ensuite exécuté. Une méthode consiste à vérifier que la pile n'a pas été verrolée avant de quitter une fonction.

Mais ceci ne protège que la pile, les failles de type heap overflow sont toujours possibles ...

[modifier] Protéger les fonctions sensibles

  • libsafe : Remplace les fonctions sensibles de la libc par une version plus sécurisés (corrige les buffer overflow et également les erreurs de formattage)

[modifier] Patcher le noyau

Patch du noyau pour éviter l'exécution de code écrit dans la pile (W^X : Write or execute) :

  • Les nouveaux processseurs AMD (à partir de 2005 environ) possèdent un flag NX permettant une protection matérielle pour W^X, l'équivalent Intel s'appelle XD
  • Patch PaX pour Linux
  • OpenBSD possède ce genre de patch depuis longtemps

Ajout de l'aléa dans l'adresse des fonctions des librairies externes rendant l'écriture d'exploit bien plus complexe :

  • Patch Linux dans la version 2.6.12 (voir l'article lwn.net à ce sujet). Au passage, ceci peut se désactiver avec « echo 0 > /proc/sys/kernel/randomize_va_space » (ouf, on peut encore jouer avec la pile ;-))
  • OpenBSD possède ce genre de fonctionnalité

Liens :

[modifier] Utiliser un langage de haut-niveau

Sans vouloir "troller", il est plus facile d'écrire une faille de sécurité (involontairement bien sûr) en langage C que dans un langage dit "de haut-niveau" tel que Python ou Java. Effectivement, dans ces langages beaucoup de tâches sont faites par le langage ou des outils intégrés. La gestion de la mémoire est par exemple releguée à un « garbage collector » qui s'occupe d'allouer et libérer la mémoire. Il existe des types de "haut-niveau" tel que les chaînes de caractères et les tableaux, ce qui évite les erreurs de programmation dans une n-ième réécriture d'un objet gêrant l'un ou l'autre des types cités.

Quelques langages dit de "haut-niveau" : PHP, Python, Perl, Java, Haskell, etc.

Bien sûr, on peut également écrire des grosses failles de sécurité en Python, et écrire un programme "sûr" en C ... Il existe des frameworks qui permettent de s'abstraire des limitations/défauts du langage C. On peut citer la bibliothèque glib de GTK qui permet d'utiliser des listes chaînées, des chaînes de caractères encodées en UTF-8, etc. très naturellement. La glib est robuste et largement testée.

[modifier] Articles connexes

[modifier] Liens externes