Compiler les sources de PHP
1) Pourquoi compiler depuis les sources PHP a la place des paquets pré-compile ?
La première chose a analyser, lors d’une installation de PHP, serait de vous demander si*l’installation va servir pour une utilisation générale (entreprise, publique, quotidiennement) ou n’a pour but que d’exécuter du PHP de temps a autre (comme des testes en local, sur votre machine). Pour une utilisation local, il n’y a généralement pas ou peu d’intérêts.
PHP lorsqu’il est installé par le moyen de paquet pré-compilé ,va inclure beaucoup de modules dont l’utilisation n’est pas requis par votre projet. Votre installation PHP sera par conséquent moins performant, moins optimisé et moins sécurisé.
A chaque nouvelle mise a jour de PHP, vous pouvez trouver les instructions de compilation et d’install pour Linux sur linuxfromscratch.com :
Si lors de l’étape, ./configure ne renseigne pas les modules activé par défaut a ne pas installer, lors de l’étape de compilation, ruinant même l’intérêt de compiler les sources. Utilisez ./configure –help pour lister l’intégralité des modules
http://dk2.php.net/manual/fr/configure.about.php
Prenez du temps a créer votre .*/configure bien spécifique, vous pourrez réutiliser cette commande autant de fois qu’une mise a jour de PHP est disponible et facilement ajouter des modules désiré. Par exemple si vous ne désirez pas du modules FTP, Calendar et FTP (par défaut ces 2 modules sont présent), mais désirez le support mySQL, comme ceci :
./configure disable-calendar disable-ftp -with-mysqli=mysqlnd -with-mysql-sock=/var/run/mysqld/mysqld.sock --with-pdo-mysql
Plus vous limitez les modules de PHP, plus il sera rapide, moins gourmand et plus sécurisé. Le reste de la procédure d’installation est disponible sur LinuxFromScratch :
http://www.linuxfromscratch.org/blfs...neral/php.html
MySQL, Utilisez les requêtes préparées
1) Pourquoi utiliser les requêtes préparées ?
La raison est simple : contrairement aux requêtes directe qui peuvent instruire a mySQL, par le biais de certains caractères spéciaux, des commandes ou des ajout de d’instructions. Le problème survient quand le code est mal conçu et autorise une variable mal nettoyé/vérifié aux caractères spéciaux (comme les guillemets, simple et double) a modifier arbitrairement la requête SQL.
Les requêtes préparée sont différentes car vous choisissez le type de variable que vous soumettez, dés lors, même si des caractères spéciaux venait a être exécuté, mySQL les traitera comme un caractère normal. Les injections SQL ne sont donc plus possible.
Dans beaucoup de conception en PHP utilisant mySQL, les requête direct (mysqli_query) n’ont pas ou peu d’intérêt et devraient être remplacé par la méthode préparée pour éviter l’injection de SQL, car même si vous développez de maniéré sécurisé, l’erreur est humaine.
2) Se connecter au serveur de base de données avec les requêtes préparées
Il est recommande de suivre ce schéma de conception pour vos projets :
- Créer un nouveau dossier a la racine de votre dossier web
- nommez le (include ou bdd ou db…)
- Créer un nouveau fichier php : db.php
Insérez y le code suivant, en remplaçant les informations de votre base de donnée
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
<?php
/*
Créer une nouvelle connexion
Spécifier entre les guillemets les informations de votre BDD:
«localhost»: le serveur a utiliser, peut-être une IP ou un domaine
«utilisateur»: lutilisateur de la BDD
«mot de passe»: le mot de passe de votre BDD
«bdd»: le nom de la base de donnée
*/
$mysqli=new mysqli("localhost","utilisateur","mot de passe","bdd");
//Si la connexion a échouée
if*($mysqli->connect_errno) {
echo "Echec lors de la connexion à MySQL : (".$mysqli->connect_errno.")".$mysqli->connect_error;
}
?> |
3) Communiquer avec mySQL via les requêtes préparées en PHP
Créer un nouveau fichier a la racine de votre site, nommez le teste.php et insérez y le contenu suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
<?php
//Inclure le fichier de connexion a mySQL
require(__DIR__ . '/include/db.php');
//Configuration de la requête SQL, ici SELECT
$stmt = $mysqli->prepare("SELECT id, label FROM teste");
//Exécution de la requête SQL
$stmt->execute();
//Récupéreration des valeur du champ id et de label, dans un tableau
$stmt->bind_result($id, $label);
//Il faut passer en revu toutes les entrées que le tableau puisse avoir récupérer dans notre table mySQL
while ($stmt->fetch()) {
//Pour chaque entrée du tableau, nous affichons le résultat
echo "$id $label \n\r";
}
///!\ Optionnellement /!\
//On libère les résultats de la mémoire
$stmt->close();
///!\ Optionnellement /!\
//Fermer la connexion a mySQL
$mysqli->close();
?> |
Pour passer une variable PHP dans la requête préparée*:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
<?php
//Inclure le fichier de connexion a mySQL
require(__DIR__ . '/include/db.php');
//Variable
$section = "home";
//Configuration de la requête SQL, ajoutez ? par chaque variable que vous desirez passer
$stmt = $mysqli->prepare("SELECT id, label FROM teste WHERE section = ?");
//Définir le type de variable
# - Si section a un valeur numérique, renseigner le drapeau d (décimal)
# - Si section a pour valeur du texte, renseigner le drapeau s (chaîne de caractères)
# - Si Ici la variable utilisateur sera transmise a la place du ? de la requête préparée
$stmt->bind_param("s", $section);
//Exécution de la requête SQL
$stmt->execute();
//Récupération des valeur du champ id et de label, dans un tableau
$stmt->bind_result($id, $label);
//Il faut passer en revu toutes les entrées que le tableau puisse avoir récupérer dans notre table mySQL
while ($stmt->fetch()) {
//Pour chaque entrée du tableau, nous affichons le résultat
echo "$id $label \n\r";
}
///!\ Optionnellement /!\
//On libère les résultats de la mémoire
$stmt->close();
///!\ Optionnellement /!\
//Fermer la connexion a mySQL
$mysqli->close();
?> |
Pour passer plusieurs variables*:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
<?php
//Inclure le fichier de connexion a mySQL
require(__DIR__ . '/include/db.php');
//Variable strings (texte = s)
$section = "home";
//Variable decimal (= d)
$id_test = 5;
//Configuration de la requête SQL, ajoutez ? par chaque variable que vous désirez passer
$stmt = $mysqli->prepare("SELECT label FROM teste WHERE section = ? AND id = ?");
// Définir le type de variable Les variables remplaceront les ?, dans l'ordre donné
$stmt->bind_param("sd", $section, $id_test);
//Exécution de la requête SQL
$stmt->execute();
//Récupération des valeur du champ id et de label, dans un tableau
$stmt->bind_result($label);
//Il faut passer en revu toutes les entrées que le tableau puisse avoir récupérer dans notre table mySQL
while ($stmt->fetch()) {
//Pour chaque entrée du tableau, nous affichons le résultat
echo "$label \n\r";
}
///!\ Optionnellement /!\
//On libère les résultats de la mémoire
$stmt->close();
///!\ Optionnellement /!\
//Fermer la connexion a mySQL
$mysqli->close();
?> |
Si votre variable est un $_POST ou un $_GET*:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
<?php
//Inclure le fichier de connexion a mySQL
require(__DIR__ . '/include/db.php');
//Variable $_GET
$section = $_GET['section'];
//Configuration de la requête SQL, ajoutez ? par chaque variable que vous désirez passer
$stmt = $mysqli->prepare("SELECT label FROM teste WHERE section = ?");
// Définir le type de variable Pas besoin de vérifier la variable ou l'encapsule dans une fonction pour échapper les caractères spéciaux
$stmt->bind_param("s", $section);
//Exécution de la requête SQL
$stmt->execute();
//Récupération des valeur du champ id et de label, dans un tableau
$stmt->bind_result($label);
//Il faut passer en revu toutes les entrées que le tableau puisse avoir récupérer dans notre table mySQL
while ($stmt->fetch()) {
//Pour chaque entrée du tableau, nous affichons le résultat
echo "$label \n\r";
}
/!\ Optionnellement /!\
//On libère les résultats de la mémoire
$stmt->close();
/!\ Optionnellement /!\
//Fermer la connexion a mySQL
$mysqli->close();
?> |
Ce schéma est ainsi le même pour les autres types de requêtes*:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
<?php
//Inclure le fichier de connexion a mySQL
require(__DIR__ . '/include/db.php');
//Variable $utilisateur
$username = $_POST["username"];
$id = 6;
//Configuration de la requête SQL, ajoutez ? par chaque variable que vous désirez passer
$stmt = $mysqli->prepare("UPDATE teste SET user = ? WHERE id = ?");
/* Définir le type de variable */
$stmt->bind_param("sd", $username, $id);
//Exécution de la requête SQL
$stmt->execute();
//Vérifier que la requête UPDATE a bien fonctionné
if($stmt->affected_rows === 0)
echo "Erreur";
else
echo "Mis a jour avec succès";
/!\ Optionnellement /!\
//On libère les résultats de la mémoire
$stmt->close();
/!\ Optionnellement /!\
//Fermer la connexion a mySQL
$mysqli->close();
?> |
Pour la liste des autres fonction, rendez-vous sur
http://php.net/manual/fr/class.mysqli-stmt.php
Les traverser
1) Qu-est-ce que sont les traversées de répertoire ?
Les traversées de répertoire est une technique permettant d’exploiter les fonctions se servant des chemin d’accès du système ou des chemins externes (exemple url). Elle fait parti du top 10 OWASP
De nombreuse fonction PHP utilise les chemins d’accès :
- file_get_contents
- file_put_contents
- require
- include
…
Si le code est mal conçu, il est possible de changer le chemin voulu par un autre, devenant ainsi une vulnérabilités exploitable. Les serveur web mal configuré (pas de chroot, lance en root…) peuvent même permettre a l’attaquant de révéler des fichiers sensibles en dehors du répertoire racine web.
Il n’existe pas de procédé unique pour éviter ces vulnérabilités, selon le fonctionnement de votre code, vous devez être sur que le chemin d’accès utilise pour la fonction PHP est correct.
Quelques exemples (basé sur Linux) de code vulnérable pour vous aider dans la compréhension
Exemple 1 :
Utiliser l’url pour inclure du contenu, par exemple lorsque qu’un client clique sur lien menu html
1 2 3 4 5 6 7 8 9 10 11
|
<?php
//La valeur est /contact.php pour le site https://votresite.com
//Le chemin racine web est : /var/www/
$uri = $_SERVER['REQUEST_URI'];
//Include page/contact.php
include("page$uri");
?> |
Le code ci-dessus est exploitable, le client peut changer l’url pour y inclure un chemin, remonter et afficher des données sensible sur votre système.
https://votresite.com/../../etc/passwd
Pour sécuriser cette exemple, $uri pourrait être vérifier a l’aide d’un array contenant les pages php disponible.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
<?php
//La valeur est /../../etc/passwd pour le site https://votresite.com
//Le chemin racine web est*: /var/www/
$uri = $_SERVER['REQUEST_URI'];
//Page disponible
$page = ['/contact.php','/blog.php','/about.php','/homepage.php'];
//Exit si l'url est incorrect
if(!in_array($uri, $page))
exit('Erreur, la page n\'existe page.');
//Include page apres verification
include("page$uri");
?> |
Exemple 2 : Récupérer un fichier
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
<?php
//La valeur est /homepage.php?file=rapport.pdf pour le site https://votresite.com
//Le chemin racine web est*: /var/www/
$pdf = $_GET['file'];
//Vérifier que le PDF existe
if(!file_exists(__DIR__.'/files/'.$_GET['file']))
exit('Pas de PDF sous ce nom');
file_get_contents(__DIR__./files/'.$_GET['file']);
?> |
Dans cet exemple, créer un tableau avec tous les noms de fichier pdf est impossible, car il y en a plus de 5000.
Le code comporte une condition qui vérifie si, le pdf demandé existe. Cet exemple est incorrect car il est possible de contourner cette vérification et d’exploiter.
https://votresite.com/homepage.php?file=rapport.pdf%00woot
%00 est un nullbyte et peut empêcher une vérification des caractères se positionnant après le nullbyte. Bien que nombre de code ne seront pas forcement affecté directement par une traversée, un simple bug que l'application pourrait afficher est un problème a corriger.
Pour sécuriser la vérification, il faut d’insérer l’extension en dur
(le code ci dessous n'est qu'un exemple de compréhension, évitez ce genre de code en production)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
<?php
//La valeur est /homepage.php?file=rapport.pdf pour le site https://votresite.com
//Le chemin racine web est*: /var/www/
$pdf = substr($_GET['file'], 0, -4);
//Vérifier que le PDF existe
if(!file_exists(__DIR__.'/files/'.$_GET['file']).'.pdf')
exit('Pas de PDF sous ce nom');
file_get_contents(__DIR__.'/files/'.$_GET['file'].'.pdf');
?> |
Le moyen le plus efficace serait de réduire les possibilités de nom possible comprenant seulement des caractères alphanumérique
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
<?php
//La valeur est /homepage.php?file=rapport pour le site https://votresite.com
//Le chemin racine web est*: /var/www/
$pdf = $_GET['file'];
//Vérifier que les caractères de $pdf sont alphanumérique
if(!preg_match('^([a-zA-Z0-9]+)$', $pdf))
exit('Doit contenir que des caractères alphanumérique');
//Vérifier que le PDF existe
if(!file_exists(__DIR__.'/files/'.$_GET['file']).'.pdf')
exit('Pas de PDF sous ce nom');
//Récupéreration du fichier
file_get_contents(__DIR__.'/files/'.$_GET['file'].'.pdf');
?> |
Penser lors de l’utilisation de chemin d’accès en PHP, de toujours limiter les caractères de vos variables (celles qui seront utilise comme chemin d’accès), de vérifier que le chemin d’accès est correcte et qu’aucune possibilité de changement puisse être exploité
Partager