Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

[Long] Pratiques de codage php et webapps

120 réponses
Avatar
John Gallet
Bonjour,


NB1 : xpost fr.comp.lang.php et fu2 fr.comp.securite (les deux sont
modérés).

Après moult tergiversations, je laisse le fu2 sur fr.comp.securite car
on dépasse le cadre du langage PHP et que les failles à débattre sont
majoritairement
humaines et non liées au langage. J'encourage plus que vivement les
habituels lecteurs/contributeurs de fclphp à suivre la discussion sur
fcs (et à le
consulter régulièrement une fois cette bonne habitude prise ;-)...)

Je me porte volontaire pour essayer de faire une synthèse de la
discussion qu'on pourra intégrer dans l'une des deux FAQs et référencer
depuis l'autre.

NB 2 : Cet article étant long et fcs étant modéré, je rappelle aux
contributeurs qui seraient tentés de le citer intégralement que leur
réponse n'a aucune chance d'être publiée. Si vous avez un doute sur la
bonne manière de répondre sur un forum usenet, usez et abusez de
http://www.giromini.org/usenet-fr/repondre.html


Suite aux divers questions/trolls sur la sécurité des applications
écrites en PHP dans une optique web, je lance un petit débat sur les
pratiques de codage en PHP apportant ou non un vrai "plus" de sécurité.

J'entends par "faille de sécurité" une erreur de codage ou de conception
qui permet de passer outre une procédure d'authentification, d'avoir
accès à des données non publiques, ou de modifier/détruire des
données/des scripts, exclusivement dans une optique web, avec php comme
langage dans mon esrit à l'origine, mais on pourra élargir à d'autres
langages/plateformes de web dynamique comme perl, jsp, asp, .net etc...

Les questions sont les suivantes :

Question 1 :

Quelles sont les principales failles existantes dans les scripts PHP que
vous avez rencontrées ? Quels risques induisaient-elles ? Comment les
avez vous corrigées ?

Question 2 :

Quelles sont les principales fausses vérifications de sécurité que vous
connaissez ? Comment peut-on les contourner (indiquer la difficulté
pour y arriver) ou pourquoi ne sont-elles pas fiables ou non applicables
sur le principe même ?

Question 3 :

Pensez vous à des failles théoriques potentielles que vous n'avez pas
encore vérifiées en pratique ?

------------
Je commence bien entendu :

Question 1 (failles existantes):
a) variables non itilialisées en register_globals=On (injection de
variables)
Risque : principalement accès non autorisés, mais tout est possible.
Correction : initialiser ses variables (Sans blague...), ou utiliser des
fonctions/des objets car ils snt insensibles à l'injection de variables.
Piège : croire qu'on est toujours en register_globals=Off et coder comme
un cochon.
Correction : idem.

b) include dynamiques (ex include($toto);)

Risque : exécution sur sa machine de n'importe quel code souhaité par
l'attaquant (installation de back-doors, défigurations, etc....
Correction : ne pas utiliser d'includes dynamiques ou vérifier que le
fichier est bien local si hébergement dédié. Renforcer les restrictions
d'include_path. Attention, depuis php5, file_exists peut éventuellement
renvoyer TRUE sur des fichiers distants (à restester, je n'ai pas poussé
plus loin que le "tip php5" du manuel).
Piège : essayer de se renforcer avec include($toto.'.php');
Contournement : $toto="[target]/script_sans_extension"; par exemple.

c) injection SQL.
Risque : accès non autorisés, corruption de données
Correction : filtrage des variables, échappement de ' et " par le
caractère ad hoc pour la base de données (\ pour mysql, ' pour sybase
etc...)

d) confiance dans les variables venant de l'extérieur. Par exemple,
recalculer une facture à payer en utilisant un prix transmis par un
champ HIDDEN ou calculé en javascript. Ne pas revalider la donnée parce
qu'elle l'a été en JavaScript.
Risque : multiples. Accès non autorisés, corruption de données, etc...
Correction : ne faire confiance qu'à des données conservées côté serveur
(refaire une requête sgbd pour obtenir le prix de l'article, les frais
de port, etc...). Faire avant tout les validations de cohérence des
données côté serveur et non en javascript.

e) uploads de fichiers.
Outre les failles du langage php lui même qui apparaissent parfois à ce
sujet, les tutoriels que j'ai vus n'insistent pas assez sur le besoin de
faire attention aux extensions autorisées par rapport aux extensions
parsées sur le serveur. Si le serveur considère comme du code php le
fichier toto.php.txt, il faut interdire tout nom de fichier contenant
.php. dans son nom. Ceci doit venir en complément d'une liste
restrictive d'extensions explicitement autorisées (.jpg, .gif, .doc
etc...). Je suis plus particulièrement intéressé sur ce point par les
vérification purement serveur permettant de vérifier le type de fichier
traité.

f) utilisation de header("Location:...)
Algo (erronné)
1. vérification de cohérence
2.1 si problème alors header("Location:bad.php"); // jusqu'ici tout va
bien
2.2 si ok alors header("Location:ok.php"); // et plouf dommage, il
suffit d'appeler directement ok.php avec n'importe quels arguments et
tout passe.
Correction :
2.1 si problème require('erreur.php'); exit();
2.2 (sinon) require('traitement.php'); // rappel : toute variable locale
est alors définie dans traitement.php

g) appels systèmes non filtrés
Dans le même genre que les includes dynamiques, passer directement la
saisie de l'utilisateur à exec() ou system(). Personnellement, j'ai
tendance à interdire tout exécution de code directe, filtrée ou par (je
remplace les actions possibles par des cases à cocher et j'exécute ce
qu'il faut). Peut-être est-ce par trop parano et que 'lon peut autoriser
certaines choses.
Risques : donner la main sur votre machine à un attaquant.
Correction : ne jamais passer quoi que ce soit qui vient de l'extérieur
en argument, mais c'est parfois trop restrictif.

Question 2 (fausses vérifications):

a) vérifier que la donnée a bien été transmise par la méthode POST sous
prétexte qu'elle vient d'un formulaire.
Contournement : il suffit d'envoyer une donnée vérolée par post, que ce
soit en modifiant du html ou en utilisant la librairie CURL par exemple
pour de l'attaque massive. C'est le contenu de la donnée qu'il faut
vérifier, pas son mode de transmission.

b) Vérifier que les données viennent bien "de mon site" en utilisant
HTTP_REFERRER.

Une idée (qui n'est pas de moi) et que je n'ai pas réussi à mettre en
oeuvre : injection SQL par des entiers ou plus généralement injection
SQL insensible aux habituelles vérifications sur les quotes.

Soit la requête : "UPDATE .... WHERE id=$i " avec id de type entier
(typiquement : autoincrement)
But de la manip : injecter dans $i une chaîne transformant la requête en
(par exemple):
"UPDATE ... WHERE id=0 OR 1=1"
(requête qui va corrompre les données en impactant tous les rangs de la
table)
Moyen sous mysql : utiliser la fonction mysql CHAR et complèter la
chaîne en hexa. Mais je n'ai pas réussi à le faire, je me prends ou du
syntax error ou une chaîne non interprêtée.

Espérant faire avancer le shimili... le shcibi... le biniou.

JG

10 réponses

1 2 3 4 5
Avatar
loufoque
John Gallet a dit le 22/11/2004 12:16:

Soit la requête : "UPDATE .... WHERE id=$i " avec id de type entier
(typiquement : autoincrement)
But de la manip : injecter dans $i une chaîne transformant la requête en
(par exemple):
"UPDATE ... WHERE id=0 OR 1=1"
(requête qui va corrompre les données en impactant tous les rangs de la
table)


Il suffit de faire intval($i).

Moyen sous mysql : utiliser la fonction mysql CHAR et complèter la
chaîne en hexa. Mais je n'ai pas réussi à le faire, je me prends ou du
syntax error ou une chaîne non interprêtée.


C'est pas vraiment le plus simple...

Avatar
John Gallet
Désolé, une partie de l'article est resté dans le clavier (#@! de copié
collé à la souris en x-window...)

b) Vérifier que les données viennent bien "de mon site" en utilisant
HTTP_REFERRER.


[début oubli]

Contournement : il est simple de stipuler n'importe quelle valeur pour
HTTP_REFERRER, que ce soit avec wget ou curl par exemple.

c) vérifier que l'IP de l'internaute fait partie d'une certaine plage
valide
Contournement : ip spoofing ou plus simplement en réseau local :
débrancher la bonne machine et piquer son IP.
Un peu plus compliqué en général que les autres pratiques décrites ici,
mais faisable.

d) vérifier que l'IP de l'internaute n'a pas changé au cours d'une
session
Problème structurel : on perd l'IP d'origine après avoir traversé un
certain nombre d'équipements réseau. De plus, certains FAI utilisant des
proxys à outrance "changent" l'ip visible de la même machine à
l'arrivée. Problème plus rare mais constaté en production, il a fallu
supprimer la vérification (plus précisement,logguer l'erreur mais ne
plus déconnecter violemment) car à cause de ces proxys, l'ip visible de
la même machine changeait toutes les 3 ou 4 requêtes.

Question 3 (failles potentielles):

[fin oubli]

Une idée (qui n'est pas de moi) et que je n'ai pas réussi à mettre en
[fin normale]


a++
JG

Avatar
Paul Gaborit
À (at) 22 Nov 2004 12:29:58 GMT,
loufoque écrivait (wrote):
John Gallet a dit le 22/11/2004 12:16:

Soit la requête : "UPDATE .... WHERE id=$i " avec id de type entier
(typiquement : autoincrement)
But de la manip : injecter dans $i une chaîne transformant la requête en
(par exemple):
"UPDATE ... WHERE id=0 OR 1=1"
(requête qui va corrompre les données en impactant tous les rangs de la
table)


Il suffit de faire intval($i).


Et dans ce cas, il suffit d'envoyer '0) OR 1=(1' comme valeur de $i.

La bonne méthode pour gérer cela est d'avoir une fonction qui va encoder la
variable pour que ce soit une chaîne SQL correcte (pour le SGBD utilisé). La
reqûete doit être de la forme :

"UPDATE .... WHERE id='$i'"

Et les quotes dans $i doivent être préfixées par un (les aussi
d'ailleurs... et peut-être d'autres caractères selon le SGBD).

--
Paul Gaborit - <http://www.enstimac.fr/~gaborit/>


Avatar
Nicob
On Mon, 22 Nov 2004 13:13:54 +0000, John Gallet wrote:

c) vérifier que l'IP de l'internaute fait partie d'une certaine plage
valide
Contournement : ip spoofing ou plus simplement en réseau local :
débrancher la bonne machine et piquer son IP.


En réseau local, il sera probablement plus simple/discret de faire mumuse
avec ARP (cf. http://www.arp-sk.org/) que d'aller éteindre/débrancher
une machine.

Quant à la vérif de l'IP source, certains (phpBB) avaient eu la bonne
idée de se fier à X-Forwarded-For plutôt qu'à la source des paquets
TCP. Ce qui menait à un "spoof" très aisé de l'IP source logguée :

http://security.nnov.ru/search/document.asp?docida10


Nicob

Avatar
John Gallet
Bonsoir,

La bonne méthode pour gérer cela est d'avoir une fonction qui va encoder la
variable pour que ce soit une chaîne SQL correcte (pour le SGBD utilisé).


Oui mais non. Un entier n'a pas à être mis entre ' dans un sgbd, même si
la plupart l'acceptent.

1> create table test_int(entier int)
2> go
1> insert into test_int values ('1')
2> go
Msg 257, Level 16, State 1:
Line 1:
Implicit conversion from datatype 'VARCHAR' to 'INT' is not allowed.
Use the
CONVERT function to run this query.

(Sybase)

En fait ce paragraphe là était plutôt pour s'amuser un peu à trouver des
failles et non comment les contrer. Sur un type non char, on ne peut se
protéger à mon sens qu'en filtrant ses variables. Pour un entier, on
accepte exclusivement des chiffres, pour un float on accepte aussi le
caractère - s'il est en première position et dès qu'on a trouvé un . ou
une , on ignore les autres (par exemple).

a++;
JG

Avatar
Alain Thivillon
c) injection SQL.
Risque : accès non autorisés, corruption de données
Correction : filtrage des variables, échappement de ' et " par le
caractère ad hoc pour la base de données ( pour mysql, ' pour sybase
etc...)


Ca ne suffit pas, il peut y avoir des injections SQL plus compliquées,
utilisant des entiers, ou indirectes (réutilisation de morceaux de la
base pour générer d'autres requêtes).

Le mieux est d'utiliser pear pour cela et les requêtes paramétrées. PHP
(4 en tout cas) manque d'une vraie couche d'abstraction des appels aux
bases de données comme l'est JDBC ou DBI ou ADO.

Il manque aussi dans votre liste les sessions non vérifiées
systématiquement, la mise a disposition par des scripts authentfiés de
listes de données elles mêmes non protégées , et surtout surtout tous
les problemes de XSS par réflexion ou par stockage.

--
Nom d'un chat de nom d'un chat !

Avatar
John Gallet
Bonsoir,

[injection sql]
Correction : filtrage des variables,
Ca ne suffit pas,

Je présume que cetet remarque s'adressait à l'échapement des quotes et

non au filtrage ?

il peut y avoir des injections SQL plus compliquées,
utilisant des entiers,
J'en cause plus loin, mais je suis justement preneur d'un exemple qui

marche. J'ai eut beau tripatouiller des hexas dans tous les coins, pas
réussi.

ou indirectes (réutilisation de morceaux de la
base pour générer d'autres requêtes).
Je suis aussi preneur d'un exemple. Un truc avec join ?


Le mieux est d'utiliser pear pour cela et les requêtes paramétrées. PHP
(4 en tout cas) manque d'une vraie couche d'abstraction des appels aux
bases de données comme l'est JDBC ou DBI ou ADO.


Je ne me suis pas penché sur le code proposé par pear (pear::db I
presume ?), je suis plus que d'accord sur l'absence d'une couche de
filtrage en entrée de php, que ce soit en direction d'un sgbd ou non.

Il manque aussi dans votre liste les sessions non vérifiées
systématiquement,
Bonne remarque, c'était "tellement évident" que je n'y aurais pas pensé.


la mise a disposition par des scripts authentfiés de
listes de données elles mêmes non protégées ,
Je ne vois pas bien, concrètement sur un exemple ?


et surtout surtout tous
les problemes de XSS par réflexion ou par stockage.
Je suis là aussi preneur d'un exemple concret ou d'une url expliquant

simplement les choses. Je n'ai jamais utilisé de cookies ou de JS de ma
vie car je m'y refuse farouchement, mais ça ne me met pas à l'abri de
tout.

a++
JG


Avatar
marc.quinton-PAS-DE-
encore quelques idées :

vol d'identité :
--------------

pas de rapport direct avec la sécurité a part entiere, mais le
publipostage avec transmission d'un ID est quelque part une
sorte d'intrusion dans votre environnement.

Le message publiposté présente des liens et au moindre clic, vous
etres fichés avec un ID et une correspondance avec une adresse email,
une adresse IP, un FAI, un OS et un navigateur, sans compter les quelques
cookies qui seraient en accessibilité globale.

Le plus intéressant, c'est que cet ID peut etre véhiculé via le téléchargement
d'un images, donc sans aucune action de l'usager.

Ces techniques mettent en oeuvre des mécanismes qu'il est facile a implémenter
sur plateforme LAMP.


vol de session:
--------------

en général, lorsque qu'une personne se connecte via un navigateur a un
système informatique, dès qu'il est reconnu, on lui passe via un cooki
une clé lui permettant d'etre identifié a chaque requette. En php, il
suffit de placer un joli print_r($_COOKIE) (ou $_SESSION) pour se rendre
compte de se qui est secretement echangé entre l'utilisateur et le serveur.

La clé transmise doit etre assez générique et aléatoire pour ne pas
donner l'envie a des utilisateurs malicieux de transmettres d'autres clés.
Genre : je vois user:dupont ; j'ai tres envie de remplacer dupont par admin
et voir ce que ca donne. si je vois id:123123131ABCDEF232323, j'ai pas
franchement envie d'essayer une autre combinaison parce que j'ai vraiment
autre chose a faire.

Il existe des mécanismes (librairie Pear) permettant a chaque requete
de changer dynamiquement et de maniere transparente la clé attendue pour
la prochaine requete. C'est tres pratique et assez sécurisant ...

Théoriquement, la clé qui est neregistrée sous forme de cookie est transmise
de maniere systématique quelque soit la requete. Parfois, cette clé
peut aussi etre transmise via l'URL. C'est parfois le cas avec le
Webmail de Free.fr. Au moindre clic sur un site externe, on transmet en
meme temps via l'information de type "referer", la clé permettant
d'acceder a son propre systeme.

Sur un site Web ou il est possible d'intervenir via les formum, il devient
assez dangeureux de se promener avec le mode admisitrateur sur les
forum public, ou toute tentative de code html malicieux est possible.
Il est probable que des clé sortent de leur contexte local dans ce cadre.

Si un auteur un peu malicieux pose ce code html :

hello, je suis un petit malin : <img src="...site_externe.../script.php" title="c'est un smiley">

il est fort probable que dans certaines circonstance un peu facheuses, le fameux script
php soit capable de retrouver des informations contextuelles propres a pénétrer le site.

donc attention aux mails au format html et aux forums embarquant du html.


il manque aussi un passage sur les XCSS si je ne m'abuse. (cross scripting machin-chose).
Avatar
Paul Gaborit
À (at) 22 Nov 2004 15:29:31 GMT,
John Gallet écrivait (wrote):
La bonne méthode pour gérer cela est d'avoir une fonction qui va encoder la
variable pour que ce soit une chaîne SQL correcte (pour le SGBD utilisé).


Oui mais non. Un entier n'a pas à être mis entre ' dans un sgbd, même si
la plupart l'acceptent.
[...]

Implicit conversion from datatype 'VARCHAR' to 'INT' is not allowed.
Use the
CONVERT function to run this query.



Exact. Donc le mieux serait une interface générique (comme le DBI de Perl) qui
connait le type de chaque champ et convertit les valeurs aux vols en vérifiant
leur type. Ça doit bien exister dans PHP (que je ne pratique plus depuis
longtemps) mais je ne la connais pas.

--
Paul Gaborit - <http://www.enstimac.fr/~gaborit/>


Avatar
Fabien LE LEZ
On 22 Nov 2004 15:29:31 GMT, John Gallet :

Sur un type non char, on ne peut se
protéger à mon sens qu'en filtrant ses variables.


Transformer explicitement la variable en entier (par exemple) est tout
aussi simple :

$i= (int)$i;

Autre méthode, pour tous les types numériques :

$i= 0 + $i;


--
;-)

1 2 3 4 5