I. Introduction▲
Tout au long de ce tutoriel, nous allons utiliser un exemple de site Internet d'un club de sport. Les scénarios présents sont décrits par une interaction entre l'internaute et
le serveur via des formulaires susceptibles de véhiculer des failles.
Les outils utilisés ici sont le langage Java côté serveur. Le conteneur de servlets est Tomcat, installé sous Linux Debian Lenny, mais ces exemples s'appliquent aussi à d'autres environnements.
II. Qu'est-ce qu'un formulaire ? ▲
Un formulaire est un moyen d'interagir avec un serveur. Il est généralement composé des champs de saisie, des cases à cocher ou des sélections. Les informations saisies seront ensuite envoyées vers le serveur qui va les traiter, et dans certains cas rendre une réponse à l'internaute.
Les formulaires servent à véhiculer les informations saisies par l'utilisateur généralement via le protocole HTTP qui lui emprunte un port quelconque. Un serveur sécurisé est aussi un serveur qui ferme les ports non utilisés. Si vous n'avez pas de service sur un port quelconque, il faut le fermer pour éviter des intrusions via cette porte. C'est là que la rigueur intervient pour empêcher toute manœuvre malveillante.
Pour illustrer les différentes attaques, mais aussi proposer des solutions, nous allons tout au long de ce tutoriel prendre comme cas le site internet d'un club de sport.
Voici un exemple de formulaire qui permet d'envoyer des informations au serveur. Il s'agit du nom, prénom et de la discipline sportive choisie par l'adhérent.
Voici le code HTML que décrit ce formulaire :
<html>
<head>
<meta http-equiv
=
"Content-Type"
content
=
"text/html; charset=ISO-8859-1"
>
<title>Inscription à une activité</title>
</head>
<body>
<FORM action
=
"http://localhost:8080/formulaire/Controlleur"
method
=
"Post"
>
Nom<INPUT name
=
"nom"
>
Prénom<INPUT name
=
"prenom"
>
<P> Adresse <TEXTAREA style
=
"WIDTH: 184px; HEIGHT: 56px"
name
=
"adresse"
></TEXTAREA>
Activité <SELECT name
=
activite>
<OPTION value
=
"basket"
selected>basket</OPTION>
<OPTION value
=
"handball"
>
handball</OPTION>
<OPTION value
=
"escrime"
>
escrime</OPTION>
<OPTION value
=
"Gymnastique"
>
Gymnastique</OPTION>
<OPTION value
=
"Danse"
>
Danse</OPTION>
<OPTION value
=
"Musculation"
>
Musculation</OPTION>
</SELECT></P>
<INPUT type
=
"submit"
value
=
"Envoyer"
>
<INPUT type
=
"reset"
value
=
"Reset"
>
</FORM>
</body>
</html>
Une fois que l'utilisateur actionne le bouton envoyer, les informations seront transmises sur le serveur via la méthode Post. Ces informations seront interceptées par un programme côté serveur qui va les interpréter et dans la majorité des cas, les enregistrer dans une base de données ou les transmettre par mail.
Voici le code de la servlet Controlleur.java qui va recevoir les informations envoyées par le formulaire puis les oriente vers affiche.jsp pour les afficher.
import
java.io.IOException;
import
javax.servlet.RequestDispatcher;
import
javax.servlet.ServletException;
import
javax.servlet.http.HttpServlet;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
import
javax.servlet.http.HttpSession;
public
class
Controlleur extends
HttpServlet {
protected
void
doGet
(
HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException
{
doPost
(
request, response);
}
protected
void
doPost
(
HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
HttpSession session =
request.getSession
(
true
);
//Récupération des paramètres envoyés via la méthode POST
String cNom =
request.getParameter
(
"nom"
);
String cPrenom =
request.getParameter
(
"prenom"
);
String cAdresse =
request.getParameter
(
"adresse"
);
String cActivite =
request.getParameter
(
"activite"
);
//On sauvegarde les données dans une session
session.setAttribute
(
"nom"
, cNom);
session.setAttribute
(
"prenom"
, cPrenom);
session.setAttribute
(
"adresse"
, cAdresse);
session.setAttribute
(
"activite"
, cActivite);
//On redirige les informations vers affiche.jsp afin de les afficher
RequestDispatcher dispat =
request.getRequestDispatcher
(
"affiche.jsp"
);
dispat.forward
(
request,response);
}
}
Une fois ces informations envoyées depuis le formulaire via la méthode Post, elles seront interceptées par la méthode doPost. Ces informations seront mises dans la session puis envoyées vers affiche.jsp pour les afficher. Cette façon de communiquer entre l'internaute et le serveur permet une interactivité simple, mais les formulaires présentés tels quels sont loin d'être sécurisés. Nous allons aborder quelques attaques susceptibles de se produire quand il y a interaction entre internaute et serveur.
III. Les différentes menaces du web▲
III-A. Attaques par Spam▲
III-A-1. Robots générateurs de spams▲
Le spam est un courrier envoyé généralement à des fins commerciales et publicitaires. Mais aussi, il peut avoir un but malhonnête comme les arnaques afin de soustraire de l'argent ou des données personnelles.
Actuellement, il existe des programmes, nommés Robots qui parcourent les pages web à la recherche d'éventuels formulaires. Le principe consiste à extraire les champs des formulaires avec les différents paramètres pour ensuite les utiliser à d'autres fins, sans l'intervention d'un être humain lors du traitement.
Imaginons que le formulaire précédent, une fois validé, sera transmis par mail au secrétariat du club du sport.
Si un robot s'empare de ces informations, il pourra utiliser ce formulaire pour envoyer un texte à des fins commerciales ou publicitaires. C'est cette possibilité que nous allons essayer de vous exposer.
Reprenons le formulaire précédent. Nous allons tenter d'envoyer les mêmes informations sur le même serveur via la méthode doPost, mais cette fois sans utiliser le formulaire tel qu'il a été présenté précédemment.
Voici le code Java de la classe TestForm.java qui permet de simuler cette étape.
package
classes;
import
java.io.BufferedReader;
import
java.io.IOException;
import
java.io.InputStreamReader;
import
java.io.OutputStreamWriter;
import
java.net.URL;
import
java.net.URLEncoder;
import
java.util.Enumeration;
import
java.util.Hashtable;
public
class
TestForm {
public
void
envoyerEnPost
(
String lien, Hashtable<
String, String>
paramsEnvoi) throws
IOException{
OutputStreamWriter out =
null
;
BufferedReader in =
null
;
try
{
StringBuilder donnees =
new
StringBuilder
(
""
) ;
Enumeration<
String>
lesCles =
paramsEnvoi.keys
(
);
Enumeration<
String>
lesValeurs =
paramsEnvoi.elements
(
);
while
(
lesCles.hasMoreElements
(
))
{
donnees.append
(
URLEncoder.encode
(
lesCles.nextElement
(
).toString
(
), "UTF-8"
));
donnees.append
(
"="
+
URLEncoder.encode
(
lesValeurs.nextElement
(
).toString
(
), "UTF-8"
)+
"&"
);
}
String donneeStr =
donnees.toString
(
);
//On efface le dernier caractère &
donneeStr =
donneeStr.substring
(
0
, donneeStr.length
(
)-
1
);
//Ici on crée une connexion avec le lien passé en paramètre
URL url =
new
URL
(
lien);
java.net.URLConnection conn =
url.openConnection
(
);
conn.setDoOutput
(
true
);
//On écrit les données via l'objet OutputStream
out =
new
OutputStreamWriter
(
conn.getOutputStream
(
));
out.write
(
donneeStr);
out.flush
(
);
//Ce code sert uniquement à contrôler la réponse renvoyée par le serveur
in =
new
BufferedReader
(
new
InputStreamReader
(
conn.getInputStream
(
)));
String ligne;
while
((
ligne =
in.readLine
(
)) !=
null
)
{
System.out.println
(
ligne);
}
}
catch
(
Exception e)
{
e.printStackTrace
(
);
}
finally
{
out.close
(
);
}
}
}
Cette classe consiste à envoyer des données sur le serveur et précisément vers la servlet située à l'adresse décrite par la variable lien. La Hashtable paramsEnvoi contient la liste des paramètres du précédent formulaire.
L'idée est de concaténer ces paramètres avec le séparateur & et de les mettre dans la variable donnees puis les envoyer via l'objet OutputStreamWriter.
L'objet InputStreamReader nous servira uniquement pour des besoins de test afin d'intercepter le résultat renvoyé par le serveur.
Voici un exemple d'une JSP décrit par le fichier testerEnvoi.jsp qui permet d'invoquer la classe précédente avec les mêmes paramètres du formulaire du club de sport.
<%
@
page language=
"java"
contentType=
"text/html; charset=ISO-8859-1"
pageEncoding=
"ISO-8859-1"
%>
<!
DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd"
>
<%
@page
import
=
"classes.TestForm"
%>
<%
@page
import
=
"java.util.Hashtable"
%>
<%
@page
import
=
"java.util.Enumeration"
%>
<
html>
<
head>
<
title>
Test formulaire sans méthode Post</
title>
</
head>
<
body>
<%
//Mettre les informations du formulaire dans une Hashtable
TestForm tester =
new
TestForm
(
);
Hashtable<
String, String>
hashtable =
new
Hashtable<
String, String>(
);
hashtable.put
(
"nom"
, new
String
(
"Koci"
) );
hashtable.put
(
"prenom"
, new
String
(
"Clémentine"
) );
hashtable.put
(
"email"
, new
String
(
"Tinna@domaine.com"
) );
hashtable.put
(
"activite"
, new
String
(
"Danse"
) );
try
{
//On invoque la méthode décrite précédemment avec les informations contenues dans hashtable.
tester.envoyerEnPost
(
"http://localhost:8080/formulaire/Controlleur"
, hashtable);
}
catch
(
Exception ex)
{
System.out.println
(
"Exception : "
+
ex );
}
%>
</
body>
</
html>
III-A-2. Halte au spam via les formulaires▲
Afin d'éviter l'envoi des données du formulaire par un robot, il faut ajouter une étape où l'être humain devra intervenir. Il existe plusieurs solutions pour mettre en place ce concept. L'idée la plus répondue est de mettre une image constituée à partir d'une suite de lettres et de chiffres sur le formulaire. Le principe consiste qu'à chaque appel de la page du formulaire, une image déformée sera générée et renvoyée en même temps que le formulaire. L'internaute est obligé de saisir ce qu'il voit sur l'image pour valider l'envoi du formulaire. Voici un exemple d'une image renvoyée par le serveur où il faut taper le mot "prapalis" pour valider l'envoi.
Pour protéger le formulaire du club de sport, nous allons ajouter une image afin que chaque utilisateur qui remplit les champs doive saisir les caractères qu'il voit sur l'image.
La classe suivante GenerImage.java permet de générer une image à partir d'une autre image en ajoutant des caractères non alphabétiques (lignes) afin que l'image soit déformée.
import
java.util.Random;
import
java.awt.Color;
import
java.awt.Graphics2D;
import
java.awt.image.BufferedImage;
import
java.io.File;
import
java.io.IOException;
import
javax.imageio.ImageIO;
public
class
GenerImage {
public
void
generAleat
(
){
int
chiffre =
0
;
//On génère le texte de l'image à partir de la chaîne cAlphabet
StringBuilder cAlphabet =
new
StringBuilder
(
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
);
StringBuilder chaine =
new
StringBuilder
(
""
);
//On utilise la classe Random qui permet de générer un nombre aléatoire
Random r =
new
Random
(
);
for
(
int
i =
1
; i<=
5
; i++
){
//On génère un chiffre entre 0 et la taille de la chaîne cAlphabet
chiffre =
r.nextInt
(
cAlphabet.length
(
));
//On prend le caractère situé à la position chiffre
chaine =
chaine.append
(
cAlphabet.charAt
(
chiffre) );
}
//Une fois la chaîne générée on appelle la méthode ecrireSurImage
ecrireSurImage
(
chaine.toString
(
));
}
// Cette méthode permet de transformer un texte en image
public
void
ecrireSurImage
(
String chaine){
BufferedImage im =
null
;
try
{
im =
ImageIO.read
(
new
File
(
"image.png"
));
Graphics2D g2d =
im.createGraphics
(
);
//Changement de la couleur
g2d.setPaint
(
Color.red.darker
(
));
g2d.setFont
(
g2d.getFont
(
).deriveFont
(
20
f));
//Ecriture sur les Graphics de l'image
g2d.drawString
(
chaine,5
, (
int
)(
im.getHeight
(
)/
1.5
));
//On dessine des lignes
g2d.drawLine
(
0
, 0
, im.getWidth
(
), im.getHeight
(
));
g2d.drawLine
(
0
,im.getWidth
(
), im.getHeight
(
),0
);
g2d.drawLine
(
0
,im.getWidth
(
)/
2
, im.getHeight
(
)/
2
,0
);
g2d.drawLine
(
im.getHeight
(
)/
2
,im.getHeight
(
), im.getWidth
(
),0
);
g2d.dispose
(
);
ImageIO.write
(
im, "png"
, new
File
(
"image1.png"
));
}
catch
(
IOException e)
{
e.printStackTrace
(
);
}
}
}
Détaillons le code de cette classe :
Dans la méthode main, on génère à chaque fois un nombre entre 0 et la taille de l'alphabet. Puis on extrait le caractère correspondant dans la chaîne cAlphabet.
La variable chaine permet de constituer une chaîne de 5 caractères qu'on passe en paramètre à la méthode ecrireSurImage.
La méthode ecrireSurImage permet de transformer un texte en image grâce à la méthode drawString de la classe Graphics2D.
Afin de déformer l'image, il suffit de dessiner quelques petites barres à travers cette image avec la méthode drawLine.
Une fois notre image générée, elle sera chargée à chaque fois qu'on fait appel au formulaire.
Lors d'un submit, le serveur comparera le contenu de l'image saisi avec la valeur de la variable chaine constituée précédemment. Si les deux valeurs sont identiques, cela signifie qu'un être humain est intervenu et qu'il s'agit bel et bien d'un envoi non automatique.
Cependant, il existe des robots capables de traduire l'image même déformée vers le format texte (exemple des programmes OCR fournis avec les scanners). Mais plus on déforme l'image, plus il est difficile pour un robot de lire le contenu de l'image.
Voici à quoi ressemblera le nouveau formulaire :
Toutefois, il existe des outils puissants qui permettent de réaliser le principe décrit précédemment. Captcha est l'un de ces outils.
III-A-3. Captcha comme exemple▲
Captcha est un utilitaire puissant et multiplateforme qui permet de réaliser le même principe que précédemment, mais avec plusieurs particularités importantes. Lors de votre inscription sur developpez.com, étape nécessaire pour la contribution au forum, il y a de fortes chances que vous ayez rencontré ce type d'image.
En plus du principe décrit par l'exemple précédent, on retrouve dans ce formulaire une option qui permettra d'émettre un son du texte affiché, ainsi qu'un bouton pour recharger et un autre pour l'aide. Mais la particularité la plus importante est signalée par le texte "Stop spam. Read books". Qu'en est-il au juste ?
Contrairement au premier exemple décrit par notre formulaire, ici l'internaute reçoit deux mots au lieu d'un. La première partie servira pour valider le formulaire, chose habituelle jusqu'à présent. Mais qu'en est-il de la deuxième partie du mot ?
Afin d'aider à transcrire en numérique les journaux et ouvrages anciens, Captcha est l'une des solutions venues au secours de la numérisation.
En effet, lors de la numérisation de ces ouvrages anciens, certains mots ne sont pas reconnus. Les logiciels qui permettent de scanner les documents ne sont pas arrivés à déchiffrer ces mots. Une solution est née et consiste à recenser toutes les images des mots non reconnus afin de constituer une base de données. Puis à chaque demande du formulaire, on transfère à l'internaute un mot constitué par une image déformée et un mot extrait de la base de données des mots non reconnus. Une fois les deux mots saisis - celui généré par le serveur pour validation et celui extrait dans la base donnée des mots non reconnus - le formulaire sera validé, mais aussi, encore une fois la base de données est mise à jour pour enregistrer le mot déchiffré. Avec ce principe tout internaute ayant validé un formulaire, participe à l'aide de certains organismes ou universités afin d'améliorer la transcription des anciens ouvrages en numérique.
Pour utiliser Captcha dans ses pages web, il suffit d'utiliser une API mise à disposition et qui existe pour plusieurs langages comme JAVA, PHP, Python, PERL, Ruby, ASP.NET.
Ce système de filtrage ne garantit pas une sécurisation des formulaires à 100%. Il existe des robots pouvant scanner ces images et les déchiffrer de temps en temps. Mais cela contribue à freiner efficacement ce genre d'attaque.
Réduire les spams via les formulaires est possible, mais dire qu'on peut les anéantir complètement est un mythe.
III-B. Attaque par injection SQL▲
Une attaque par injection SQL consiste à exploiter une faille de sécurité d'une application qui interagit directement avec une base de données.
Ce genre d'attaque est l'une des plus utilisées sur le net. Il existe beaucoup de sites Internet, professionnels ou autres, qui laissent passer ce genre de failles.
Le principe consiste à saisir du code SQL dans les champs du formulaire à la place des données ordinaires.
Prenons un exemple simple et concret : le formulaire suivant servira à l'authentification d'un adhérent sur le site. Le login et le mot de passe sont obligatoires. Le bouton Envoyer permet d'envoyer vers le serveur les valeurs saisies. Ces informations seront traitées et comparées avec les données stockées dans une base de données côté serveur.
L'une des erreurs commises généralement par les développeurs, c'est de ne pas séparer les parties distinctes du programme, comme le fait le Modèle MVC cher à Java.
Nous allons simuler cette attaque et détailler les étapes de progression de la menace.
Cette attaque devient menaçante quand le code côté serveur ressemble à peu près au code suivant qui décrit une manière simple d'authentification.
protected
void
doPost
(
HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
//Récupération des paramètres envoyés via la méthode POST
String cLogin =
request.getParameter
(
"Login"
);
String cPassword =
request.getParameter
(
"Password"
);
String laConnexion =
"Select * from animateur where email ='"
+
cLogin +
"'"
+
" and password='"
+
cPassword+
"'"
;
try
{
seConnecter
(
); //On suppose que cette méthode permet de se connecter à la base
Statement stat_req =
(
Statement) connecter.createStatement
(
);
ResultSet rs1 =
stat_req.executeQuery
(
laConnexion);
if
(
rs1.next
(
)){
System.out.println
(
"Vous êtes connectés"
);
}
}
catch
(
Exception ex)
{
System.out.println
(
"Ereur "
+
ex);
}
}
La méthode doPost récupère les paramètres du formulaire et les compare à ceux stockés en base. Voici la requête qui permet de réaliser l'authentification en comparant les informations saisies et celles présentes dans la base.
String laConnexion =
"Select * from animateur where email ='"
+
cLogin +
"'"
+
" and password='"
+
cPassword+
"'"
;
Imaginons, maintenant que l'internaute saisit son login normalement, mais à la place du mot de passe saisit l'expression suivante :
toto' OR '1'='1
Dans ce cas la requête précédente devient :
String laConnexion =
"Select * from animateur where email ='"
+
cLogin +
"'"
+
" and password='"
+
toto' OR '
1
'='
1
+
"'"
;
La requête n'aura pas la même signification que la première. L'expression toto' OR '1'='1 permet de retourner vrai quelques soit le mot de passe saisi, puisque '1' est toujours égal à '1'. L'accès à la base est ainsi réalisé.
Cette requête n'est qu'un exemple parmi tant d'autres. On peut même insérer toute une requête en ajoutant le mot clé Union.
Solution : Afin d'éviter ce genre d'attaque, il est utile de respecter les normes de programmation en séparant les traitements de toutes les parties distinctes du programme. Mais surtout, effectuer des validations avant toute interaction avec la base de données.
Lorsque les informations du formulaire servent à alimenter une base de données, ou une boîte mail, il est primordial d'intercepter et de valider ces données avant leur sérialisation.
L'utilisation des frameworks comme Struts est aussi une solution efficace et relativement sécurisée.
III-B-1. Bien gérer les exceptions▲
Si les exceptions dans le programme sont mal interceptées, cela risque de renvoyer au client un message expliquant l'erreur, et qui pourra comporter des données sensibles, comme les noms des variables, les noms des fichiers de sérialisation comme les bases de données et même les fichiers de paramétrage.
Quelle sera l'ampleur de l'impact si un nom d'une table de données s'affiche ? Ceci aidera le hacker à utiliser ces noms pour mieux concevoir ses requêtes.
try
{
Traitement 1
}
}
catch
(
Exception ex)
{
//Afficher la page d'erreur 404 sans afficher le contenu de l'erreur
}
finally
{
Traiteemnt2
}
III-C. Eviter les attaques par bombing▲
Le mot bombing signifie bombardement. Le principe consiste à envoyer plusieurs requêtes successives sur le serveur afin de saturer une table, une boîte mail ou un disque.
Revenons au site du club de sport. Le formulaire suivant permet à l'internaute de saisir ses cordonnées, notamment son mail afin de s'inscrire à la news letter.
Imaginons que les données envoyées par ce formulaire serviront à alimenter une base de données ou un fichier qui permet de stocker ces informations. Tout ce qui est logique jusqu'à présent.
Voici la partie du code de la méthode doPost qui récupère les informations envoyées via le formulaire.
protected
void
doPost
(
HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
//Récupération des paramètres envoyés via la méthode POST
String cNom =
request.getParameter
(
"nom"
);
String cPrenom =
request.getParameter
(
"prenom"
);
String cEmail =
request.getParameter
(
"email"
);
//On sauvegarde ces informations dans un fichier texte.
//Cela peut être une base de données ou autre structure qui permet la sauvegarde.
//Cette partie permet d'ouvrir le fichier en écriture et d'ajouter à la fin du fichier
//le texte contenu dans la variable texte.
FileWriter writer =
null
;
String texte =
cNom+
" "
+
cPrenom+
" "
+
cEmail +
"
\n
"
;
try
{
//Le paramètre permet d'ajouter le texte à la fin du fichier
writer =
new
FileWriter
(
"SavNewsLetter.txt"
, true
);
writer.write
(
texte);
}
catch
(
IOException ex)
{
ex.printStackTrace
(
);
}
finally
{
if
(
writer !=
null
){
writer.close
(
);}
}
}
Rien de plus simple que ce que fait le code de la méthode doPost. Elle récupère les informations du formulaire, puis alimente le fichier texte SavNewsLetter.txt.
Pour des raisons de simplicité du tutoriel, nous avons utilisé le fichier texte à la place d'une base de données ou de la boîte mail.
Maintenant, essayons d'envoyer un grand nombre de requêtes à la servlet en utilisant la class TestForm.java qui va interagir avec la méthode doPost, et observons le fichier résultats SavNewsLetter.txt. Pour cela il suffit d'ajouter une boucle au code testerEnvoi.jsp pour envoyer plusieurs requêtes sur le serveur et de paramétrer la variable nbreRequetes comme suit :
<%
//Ici envoi de 100 requêtes sur le serveur
double
nbreRequetes =
100
;
for
(
int
i=
0
;i<=
nbreRequettes; i++
)
{
//Mettre les informations du formulaire dans une Hashtable
TestForm tester =
new
TestForm
(
);
Hashtable<
String, String>
hashtable =
new
Hashtable<
String, String>(
);
hashtable.put
(
"nom"
, new
String
(
"Koci"
) );
hashtable.put
(
"prenom"
, new
String
(
"Clémentine"
) );
hashtable.put
(
"email"
, new
String
(
"Tinna@domaine.com"
) );
hashtable.put
(
"activite"
, new
String
(
"Danse"
) );
try
{
//On invoque la méthode décrite précédemment avec les informations contenues dans hashtable.
tester.envoyerEnPost
(
"http://localhost:8080/formulaire/Controlleur"
, hashtable);
}
catch
(
Exception ex)
{
System.out.println
(
"Exception : "
+
ex );
}
}
%>
Un nombre de requêtes élevé pourra remplir un espace sur disque et ainsi faire tomber le serveur.
Solution : Il existe plusieurs solutions pour éloigner ce genre d'attaque. Pour éviter ce cas de figure, il faut gérer la session d'une manière efficace. Par exemple, il suffit d'autoriser uniquement quelques envois de formulaire pour chaque session donnée, ou ne pas procéder à la sérialisation des informations quand plusieurs requêtes suspectes sont interceptées. On entend par requêtes suspectes les requêtes envoyées en nombre important sur une durée donnée.
III-D. Limiter les tentatives d'authentification▲
Beaucoup de sites ont une rubrique "accès privé", pour permettre à une partie des utilisateurs d'accéder à des services particuliers.
Généralement l'accès s'effectue après une authentification via un formulaire.
Prenons un exemple : Pour accéder au forum de developpez.com, vous devez avoir un compte enregistré.
L'image suivante affiche le formulaire de connexion au forum.
D'ailleurs je vous le conseille, si vous n'avez pas encore de compte, il faut s'inscrire car le forum est une mine d'or.
Revenons à notre sujet. Comment un hacker pourra faire pour accéder à un espace privé même s'il n'a pas de compte ?
Il existe une manière parmi tant d'autres qui permet d'outrepasser les règles habituelles. L'attaque par force brute est l'une de ces méthodes très répandues. Le principe consiste à envoyer toutes les probabilités possibles de mot de passe pour un login donné, si le site autorise la multi-authentification.
Quoi que cela parait moins évident pour la plupart des sites, il existe des cas où la tentative pourra aboutir.
Ne tentez pas le coup sur developpez.com. Le principe est tout autre. Le site est aussi sécurisé que les coffres du FMI(Fond Monétaire International).
Continuons avec le site qui décrit la salle de sport. La direction décide d'attribuer à chaque adhérent un espace privé, notamment afin de choisir les horaires, ou modifier les informations personnelles comme l'adresse mail ou les coordonnées personnelles.
Voici le formulaire HTML décrit par le fichier authentif.jsp
<html>
<head>
<meta http-equiv
=
"Content-Type"
content
=
"text/html; charset=ISO-8859-1"
>
<title> Veuillez vous authentifier</title>
</head>
<body>
<form action
=
"http://localhost:8080/formulaire/Controlleur"
method
=
"Post"
>
<P>Login_____ : <INPUT name
=
"login"
size
=
15></P>
<P>Password__ : <INPUT name
=
"password"
size
=
15 type
=
"password"
></P>
<P><INPUT type
=
"submit"
value
=
"s'identifier"
name
=
"button1"
>
<INPUT type
=
"button"
value
=
"Reset"
name
=
"button2"
></P>
</form>
</body>
</html>
Et voici la servlet Authentification chargée de recevoir les informations d'authentification. Le code est simplifié pour des raisons didactiques.
import
java.io.IOException;
import
javax.servlet.ServletException;
import
javax.servlet.http.HttpServlet;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
public
class
Authentification extends
HttpServlet {
private
static
final
long
serialVersionUID =
1
L;
protected
void
doGet
(
HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException
{
doPost
(
request, response);
}
protected
void
doPost
(
HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException
{
//Récupération des paramètres envoyés via la méthode POST
String cLogin =
request.getParameter
(
"login"
);
String cPassword =
request.getParameter
(
"password"
);
//Appel de la méthode pour tester le login et le password
if
(
authentifier
(
cLogin, cPassword))
{
System.out.println
(
"Vous êtes connectés"
);
}
else
System.out.println
(
"Login ou mot de passe incorrect"
);
}
//Cette méthode permet de tester l'exactitude du login et du mot de passe
private
boolean
authentifier (
String Login, String MotDePass){
return
Login.equalsIgnoreCase
(
"Koci"
) &&
MotDePass.equals
(
"meinPass"
);
}
}
Comment un hacker peut deviner votre mot de passe ? S'il a déjà en possession votre login, sa charge de travail sera divisée en deux. Généralement c'est ce qui se produit, tous vos amis ont votre mail mais pas votre mot de passe.
Nous allons passer à notre servlet une combinaison de mots de passe. Nous supposons que nous connaissons déjà le login.
Les classes suivantes permettent de créer des combinaisons possibles de mot de passe. On entend par mot de passe une chaîne de caractères d'une taille fixe composée d'une combinaison de caractères de l'alphabet avec casse et des chiffres.
La classe suivante Probabilite permet de générer des combinaisons de chaînes de caractères possibles à partir d'une chaîne constituée par les caractères de l'alphabet.
import
java.io.IOException;
import
java.util.ArrayList;
import
java.io.PrintWriter;
import
java.io.FileWriter;
public
class
Probabilite {
public
static
void
main
(
String[] args){
int
tailleAlphabet =
Integer.parseInt
(
args[0
]); //Taille de l'alphabet
int
taillePass =
Integer.parseInt
(
args[1
]); //Taille max du mot de passe
//matrice qui va comprendre les deux tableaux d'alphabet
String[][] Alphabet=
new
String[tailleAlphabet][tailleAlphabet];
String[] AlphabetTab =
new
String[tailleAlphabet];
//Pour des raisons pédagogiques nous simplifions l'alphabet aux caractères miniscules.
//Dans la réalité un mot de passe peut contenir des majuscules et les chiffres ainsi
//que quelques caractères speciaux.
String[] AlphabetIn =
{
"a"
, "b"
, "c"
,"d"
,"e"
,"f"
,"g"
,"h"
,"i"
,"j"
,"k"
,"l"
,"m"
,"n"
,"o"
,"p"
,
"q"
,"r"
,"s"
,"t"
,"u"
,"v"
,"w"
,"x"
,"y"
,"z"
}
;
String[] AlphabetIn2 =
{
"a"
, "b"
, "c"
,"d"
,"e"
,"f"
,"g"
,"h"
,"i"
,"j"
,"k"
,"l"
,"m"
,"n"
,"o"
,"p"
,
"q"
,"r"
,"s"
,"t"
,"u"
,"v"
,"w"
,"x"
,"y"
,"z"
}
;
PrintWriter out; //Ce fichier permet de tracer les probabilités.
for
(
int
e =
0
; e<
tailleAlphabet; e++
){
AlphabetTab[e] =
AlphabetIn[e];
}
try
{
out =
new
PrintWriter
(
new
FileWriter
(
"rapport.txt"
));
for
(
int
e1=
0
;e1<
tailleAlphabet;e1++
){
for
(
int
e2=
0
;e2<
tailleAlphabet;e2++
){
Alphabet[e1][e2] =
AlphabetIn[e1] +
AlphabetIn2[e2];
ArrayList<
String>
carArraylist =
new
ArrayList<
String>(
);
carArraylist.add
(
Alphabet[e1][e2]);
GenerProbab propabsM =
new
GenerProbab (
taillePass);
propabsM.traiter
(
AlphabetTab,carArraylist, out);
}
}
out.println
(
"Fin de traitements "
);
out.close
(
);
}
catch
(
IOException ex)
{
System.out.println
(
"Erreur "
+
ex);
}
}
}
import
java.io.IOException;
import
java.io.PrintWriter;
import
java.util.*;
public
class
GenerProbab {
private
double
taillePass;
public
GenerProbab
(
int
taillePass) {
this
.taillePass =
taillePass;
}
public
void
traiter
(
String[] alphabet, ArrayList<
String>
aTableau2,
PrintWriter out) throws
IOException {
ArrayList<
String>
TAbFinal =
new
ArrayList<
String>(
);
for
(
int
e =
0
; e <
alphabet.length; e++
) {
for
(
int
j =
0
; j <
aTableau2.size
(
); j++
) {
TAbFinal.add
(
aTableau2.get
(
j) +
alphabet[e]);
System.out.println
(
TAbFinal.get
(
TAbFinal.size
(
) -
1
));
// Ici on recupère la chaîne et on la considère comme mot de
// passe.
// Puis il suffit d'adapter la classe pour envoyer à la servlet
// Authentification
// le login "Koci" et la valeur de la variable renvoyée par
// TAbFinal.get(TAbFinal.size()-1) comme mot de passe
out.println
(
TAbFinal.get
(
TAbFinal.size
(
) -
1
));
}
}
System.gc
(
);
String laChaine =
(
String) TAbFinal.get
(
TAbFinal.size
(
) -
1
);
int
laTaille =
laChaine.length
(
);
if
(
laTaille <
taillePass) {
traiter
(
alphabet, TAbFinal, out);
}
}
}
Pour des besoins de test, les deux classes ont étés simplifiées. Il suffit d'adapter le code pour que les deux classes soient appelées pour utiliser le login d'une personne afin de générer les différentes possibilités du mot de passe.
Pour exécuter le programme précédent, il faut passer à la classe Probabilite qui contient la méthode main, deux paramètres qui sont respectivement la taille de l'alphabet et la taille maximale du mot de passe.
Dans la réalité, la taille de l'alphabet pour générer un mot de passe est fixe et contient tous les caractères minuscules et majuscules, les chiffres de 0 à 9, ainsi que quelques caractères spéciaux.
Exemple :
java Probabilite 26
10
Cette commande permet de générer tous les cas possibles du mot de passe d'une taille maximum de 10 caractères à partir de l'alphabet contenant les 26 caractères [a-z].
Les classes précédentes se basent sur l'objet ArrayList, donc sur une structure de données stockée en mémoire. Le traitement génère un nombre de probabilité en fonction des capacités mémoire de votre ordinateur.
Cependant vous pouvez définir la taille louée par la JVM ou l'augmenter de la façon suivante :
java Probabilite 26
10
-
Xms128M -
Xmx512M -
XX:MaxPermSize =
256
m
Une autre solution consiste à utiliser une structure de données qui permet la sérialisation de tous les cas possibles, comme les fichiers textes. Il existe aussi plusieurs algorithmes qui permettent de réaliser ce genre de traitements.
Solution : Comment éviter ce genre d'attaque ?
Afin d'éviter une multitude de requêtes, il est primordial de limiter le nombre de tentatives de connexion à un nombre donné dans un délai fixe. Exemple, n'autoriser que 3 connexions consécutives au bout de 3 minutes données. Cela implique qu'il faut prendre en compte la gestion de la session des utilisateurs.
Cette faille est très répandue sur le net, y compris auprès des professionnels. Le Webmail de Yahoo était un exemple. Vous pouvez lire la news publié par developpez.com à cette adresse : Exploitation en masse d'une faille vieille de deux ans sur le webmail Yahoo
L'exemple suivant est généré par google Mail après 3 tentatives de mot de passe échouées pour un login donnée.
Effectivement, après 3 tentatives d'échec de connexion, googlemail génère un Captcha pour s'assurer que ce n'est pas un programme automatique qui essaye de se connecter, mais il y a bel et bien une intervention humaine.
La méthode décrite par la classe Probabilite a ses limites, surtout si le développeur et l'utilisateur respectent les règles de codages et de création des mots de passe.
Si le mot de passe est de 5 caractères, en utilisant un alphabet comme décrit dans la classe, c'est-à-dire 26 caractères minuscules on obtient plus de 18200 cas allant du mot "aaa" à "zzzzz".
Il est clair que les conditions seront plus compliquées si les normes de codage des mots de passe sont respectées. Imaginez le temps d'exécution et le nombre de combinaisons pour un alphabet de tous les caractères possibles avec un mot de passe d'une longueur dépassant 15 caractères.
Afin de compliquer les choses au hacker, il est utile de coder les mots de passe en utilisant des chaînes longues avec des minuscules, majuscules et chiffres, et de changer le mot de passe régulièrement.
IV. Les outils▲
Afin de renforcer la sécurité du site, il est utile de procéder aux tests d'une manière rigoureuse, en simulant les différentes attaques possibles. A cet effet, sous Linux, il existe plusieurs outils qui permettent de tracer les pages web et de simuler des attaques. Certains de ces outils ont des versions sous d'autres systèmes d'exploitation. Dans ce tutoriel, nous allons nous attarder sur deux plugins de Firefox ainsi que les fameux programmes WebGoat et wireshark.
IV-A. Firebug▲
Firebug est un plugin de Firefox qui permet de tracer sa page web, allant d'une simple consultation du code, à l'affichage des erreurs en passant par le débogage du code JavaScript.
Son installation est simple. Il suffit d'ajouter l'extension Firebug du navigateur, en la téléchargeant ici.
L'image suivante montre Firebug analysant le code source d'une page web.
IV-B. Web Developper▲
Web Developper est une vraie boîte à outils qui permet de manipuler les pages web en profondeur. Son installation est simple, comme pour Firebug, il suffit d'ajouter l'extension Web Developper dans le navigateur (téléchargeable ici).
Entre autres, cet outil permet de gérer JavaScript, les cookies et CSS, et les modifier. On peut même réaliser des conversions des actions Post en Get et vice versa. Il permet aussi de debugger le code, d'afficher les commentaires et manipuler les formulaires.
Cet outil est vraiment très puissant, mais aussi très utile lors des développements web.
L'image suivante montre les différentes possibilités de manipulation d'une page web avec Web Developper.
IV-C. WebGoat▲
WebGoat est une application puissante, écrite en Java. Cet outil permet de simuler des attaques contre des applications web, au point qu'il rend les choses amusantes. WebGoat se présente sous forme de différentes leçons pour exploiter des vulnérabilités comme Injection SQL ou de commande, détournement de session, manipulation des Web Services, détournement des requêtes AJAX, décryptage des mots de passe envoyés via le protocole http, ainsi que plein d'autres astuces. Chaque leçon contient un ou plusieurs exemples pratiques. Pour voir la solution, il suffit de cliquer sur le menu Show Solution. Les copies d'écrans suivantes montrent un aperçu général de la page d'accueil de WebGoat, l'autre montre la liste générale des attaques vulnérables proposées.
Installation:
Pour installer WebGoat, il suffit de télécharger le zip ici, le décompresser dans un répertoire. Toutefois, assurez-vous que vous avez les droits de lecture et écriture sur le répertoire.
Pour information WebGoat installe un serveur Tomcat.
Pour exécuter WebGoat, allez dans le répertoire d'installation et lancer la commande suivante dans un terminal :
$
sh ./webgoat.sh start8080
Lancer votre navigateur et saisissez le lien suivant :
$
http://localhost:8080
/webgoat/attack
Une fenêtre vous invite à rentrer votre login et mot de passe. Saisissez le couple guest/guest et la page d'accueil s'affiche.
Vous pouvez commencer vos cours grâce à un menu répertoriant tout type de vulnérabilité comme le montre la figure précédente.
Le principe de Webgoat est d'interagir avec le serveur Tomcat installé fourni automatiquement avec l'application.
Afin de mieux utiliser WebGoat, il est recommandé d'installer wireshark.
IV-D. Wireshark▲
wireshark est un outil qui permet d'analyser le trafic réseau. S'il est implémenté dans la plupart des distributions, c'est parce que c'est un outil conçu au départ pour Linux/Unix dont l'installation est très simple. Il existe aussi des versions pour Mac OS X et Windows.
Avec une distribution basée sur Debian, il suffit de l'installer à partir des paquets synaptiques ou se contenter de la commande :
# apt-get install wireshark
Lors de la première utilisation, il faut commencer par initialiser la variable d'environnement contenant le chemin vers les librairies avec la commande :
# export LD_LIBRARY_PATH=/usr/local/lib
Pour démarrer wireshark il suffit de lancer la commande suivante dans le terminal :
$
wireshark
Toutefois, il faut éviter de lancer wireshark sous le compte root.
Passons aux choses plus amusantes ! Nous allons prendre un exemple qui permet d'exploiter une faille réseau, en utilisant WebGoat et wireshark.
IV-D-1. Exemple d'utilisation : Décryptage d'un mot de passe▲
Nous allons démontrer combien c'est facile de déchiffrer un mot de passe, passé via un réseau surveillé.
Nous allons reproduire la leçon Insecure Login qui consiste à voir en clair un mot de passe envoyé via le protocole http.
Une fois que vous avez démarré WebGoat et wireshark, ouvrez la page
http://localhost:8080
/webgoat/attack
Allez dans la rubrique Insecure Communication puis Insecure Login. Un formulaire vous propose de saisir votre nom et votre mot de passe comme le montre la figure suivante :
Cliquez sur le bouton submit, regardez attentivement la trace du flux réseau sur votre terminal ou dans la fenêtre de wireshark. Vous allez voir afficher le login, mais aussi le mot de passe en clair.
Les figures suivantes montrent ces informations sur l'interface wireshark et dans le terminal.
Nous remarquons facilement le login jack et le mot de passe sniffy sur les deux images.
Le protocole HTTP passe les informations en clair et si le mot de passe n'est pas crypté, il sera visible pour un hacker qui surveille votre réseau.
Pour comprendre comment éviter ce genre de faille, voici un exemple réalisé avec le site developpez.com, au moment où je me connecte sur le forum avec mon login kmdkaci et mon mot de passe, tout en lançant la capture du flux réseau grâce à wireshark.
Les captures d'écrans suivantes montrent mon login, mais on remarque que le mot de passe est déduit à partir d'une fonction de hachage.
Solution :
Afin d'éviter que les mots de passe soient envoyés en clair, il est primordial de procéder au hachage ou cryptage des chaînes. Il existe plusieurs méthodes qui permettent d'effectuer ces opérations.
- Avec JavaScript, vous pouvez créer une fonction de hachage qui transformera le mot de passe en une suite de caractères incompréhensibles comme le montre la figure précédente, et cela en se basant sur des algorithmes comme MD5.
Pour rappel, leMD5 (Message Digest 5) est une méthode utilisée en cryptographie qui permet d'obtenir une empreinte d'un message quelconque.
L'un des soucis que l'on pourra rencontrer est que JavaScript est du coté client, votre algorithme est visible par l'internaute, même si la méthode MD5 reste relativement sécurisée, on préfère plutôt la méthode basée sur SHA-1.
- Il existe aussi une possibilité d'utiliser une applet java comme formulaire d'entrée, dont le code comporte un algorithme de cryptage.
L'applet pourra interagir directement avec la base de données, en envoyant les informations cryptées, ou remettre à JavaScript ces informations cryptées pour les envoyer sur le serveur.
Pour rappel, l'applet Java peut communiquer avec JavaScript grâce aux plugins Netscape, comme JSObject. Voir le forum
- Il reste la solution d'utilisation du protocole HTTPS qui demeure la méthode la plus sécurisée.
V. Conclusion▲
Nous avons vu, tout au long de ce tutoriel quelques failles de sécurité qui peuvent être laissées dans les codes, y compris chez les professionnels. Ces failles peuvent s'avérer vulnérables, et peuvent constituer une menace dangereuse pour le système d'informations.
Comme vous l'avez sûrement remarqué, ce tutoriel ne traite que quelques failles de sécurité. Il est impossible de citer tous les types d'attaques susceptibles de se propager sur le net. Mais ces quelques lignes donnent quelques aperçus de failles existantes et surtout vont permettre d'inciter les développeurs à devenir vigilants.
VI. Liens▲
- Cours rubrique sécurité de developpez.com
- Linux Magazine France
- Projet Webgoat
- Projet wireshark
VII. Remerciements▲
Un grand merci à Bérénice MAUREL pour la correction orthographique et sa patience, ainsi que Baptiste Wicht pour la relecture technique et ses conseils.