IE8 Le filtre XSS qui créer les failles

Vous l’utilisez peut être sans le savoir , mais i8 embarque un filtre XSS : qui est censé vous protéger de cette faille plutôt répandu sur les sites peu regardants.
Le hic c’est que le filtre semble permettre d’executer du code sur les sites à la base sains…

Le filtre agit très simplement, quand il détecte une possible vulnérabilité il modifie la page que vous visitez pour vous protéger.
C’est cette modification de page que certains semble pouvoir détourner pour venir vous voler vos information par exemple contenu dans un cookie.

Comme à son habitude Microsoft ne dit mot , mais Google à bel et bien confirmé la vulnérabilité:

« We’re aware of a significant flaw affecting the XSS Filter in IE8, and we’ve taken steps to help protect our users by disabling the mechanism on our properties until a fix has been released. »

Source

La solution la plus simple restant de désactiver ce filtre en envoyant un header spécifique :

X-XSS-Protection: 0

Et de bien tester toutes les failles possible sur votre site:

Mysql recherche fulltext sur table InnoDb

Le moteur de stockage InnoDb à de nombreux avantages mais pas celui de permettre la recherche fulltext pourtant extrêmement pratique lorsque l’on souhaite faire un petit moteur de recherche sur son application !
Ne pouvant me séparer de l’intégrité référentielle du moteur innodb, il m’a fallut trouver une solution.
Etant en mysql 5 le plus simple à été de créer une table de « recherche » avec le moteur myisam.
C’est une table allégée qui ne contient que les id et les champs textes à indexer.

Afin de garder cette table constamment à jour par rapport à la table originelle , il suffit de créer 3 triggers pour les 3 grandes opérations :

--Insertion CREATE TRIGGER bug_search_insert AFTER INSERT ON bugs FOR EACH ROW INSERT INTO bugs_search SET idBug = NEW.id, description = NEW.description
--Update CREATE TRIGGER bug_search_update AFTER UPDATE ON bugs FOR EACH ROW UPDATE bugs_search SET idBug = OLD.id, description = NEW.description WHERE id = OLD.id
--Delete CREATE TRIGGER bug_search_delete AFTER DELETE ON bugs FOR EACH ROW DELETE FROM bugs_search WHERE id = OLD.id

Ainsi chaque modification dans la table au moteur innodb sera répercutée dans la table au moteur myIsam. On perd évidemment en performance puisque que l’on doit travailler sur une table supplémentaire, mais on gagne la recherche fulltext sans sacrifier l’intégrité référentielle !

Détails de la création d’un trigger :

CREATE TRIGGER (1) (2) ON (3) FOR EACH ROW (4)

1- Nom du trigger , doit être unique
2- Moment de déclenchement, par exemple AFTER INSERT , BEFORE UPDATE ….
3- Table « déclenchante »
4- Requête à exécuter pour chaque ligne

Sqlite : optimiser un COUNT() long

Sqlite à pour politique de ne pas stocker de meta data à propos du nombre de ligne des tables. C’est un choix justifiable car garder ces infos pour toutes les tables est couteux en ressources et ralenti donc forcément chaque INSERT/DELETE.
Parcourir une table lors du COUNT n’est donc pas très problématique sauf quand les tables atteignent un niveau important de données.
Mauvaise surprise pour moi lorsque ma page à mis plus de 1 min à s’afficher (base de 7Go et 500k enregistrements).
Pour résoudre le problème il faut émuler cette meta data avec 2 triggers et une tables supplémentaire.

On pourra par exemple créer une table « totaux » avec 3 champs « id », »table » et « valeur ». Il y’aura donc un enregistrement par table avec pour valeur le nombre de lignes de cette table.

Ne reste qu’à creer les deux triggers :

CREATE TRIGGER "count_delete_matable" AFTER DELETE ON maTable BEGIN UPDATE totaux SET valeur = valeur-1 WHERE table='maTable'; END;CREATE TRIGGER "count_insert_matable" AFTER INSERT ON maTable BEGIN UPDATE totaux SET valeur = valeur+1 WHERE table='maTable'; END;

Ainsi après chaque enregistrement ajout ou supprimer le total sera automatiquement in/decrémenté.
Ne reste en suite qu’à remplacer les

SELECT COUNT(*) FROM maTable

par

SELECT valeur FROM totaux WHERE table='maTable'

Et là Ô magie , la page passe de plus d’1min à moins d’une demi seconde 🙂

Conversion ressource image vers string

La bibliothèque GD permet beaucoup de chose mais pas de sauvegarder une image en chaine de caractère. Ça peut pourtant être pratique pour sérialiser une image (les ressources ne sont pas sérialisables) ou encore ne pas avoir un créer un fichier sur le disque.

La solution est d’utiliser la bufferisation de la sortie standard de php pour récupérer la ressource sous forme de string :

ob_start();imagejpeg($imageBase);$string = ob_get_contents();ob_end_clean();

Un petit exemple de mise en situation : Créer une archive zip contenant des images issues d’une fusion de plusieurs images contenues dans un BLOB d’une base de données :

$zip = new ZipArchive();

$op = $zip->open('test.zip',ZipArchive::CREATE);

foreach($ids as $id){ ob_start(); $data  = $bd->getById($id); $imageBase  = $bd->generateImage($data); imagejpeg($imageBase);

$zip->addFromString($data->id.'.jpg',ob_get_contents()); ob_end_clean(); }$zip->close();

Il est ainsi possible de créer à la volée l’archive sans avoir à écrire les fichiers sur le disque. La consommation mémoire est en revanche plus élevée. A titre d’exemple , une archive avec 380 images d’environs 20Ko aura consommé 18Mo de mémoire.

Event Delegation ou comment utiliser la propagation des événements

De prime abord la gestion des événements en javascript semble assez simple. Cependant cela devient un peu plus pointu dès que l’on veut optimiser les performances.

L’un des problèmes que l’on rencontre souvent est une page lente à charger et consommant beaucoup de mémoire à cause de nombreux événements à charger.

Le cas classique est un tableau avec des événements sur chaque case du tableau. Si on à un tableaux de 10×10 celà nous fait … 100 événements à attacher.

Pour solutionner le problème et gagner énormément de temps la délégation d’événement ou Event delegation permet de se baser sur la propagation des événements pour n’en utiliser qu’un seul.
Ainsi on attache un événement sur l’élément père et on conditionne le traitement lors de la capture.
Voici un avant/après avec prototype permettant d’attacher un événement sur tous les éléments ayant la classe « change » :

Avant :

$$('.change').each(function(elem){Event.observe(elem,'click',function(){alert('Capture')})});

Ici on passe en revu tous les éléments de la page pour trouver tout ceux ayant la classe « change » et on leur attache un événement particulier.

Avec la délégation d’événement , on à un peu plus de code mais c’est nettement plus rapide :
Après :

Event.observe($('container'),'click',function(e){if(e.element().hasClassName('change')) alert('Capture');});

On attache donc un seul et unique événement sur le conteneur des éléments devant recevoir l’événement. L’événement se propageant les éléments fils , le reçoivent également. Il ne reste donc plus qu’à conditionner le code pour que l’action de l’événement ne s’exécute que sur les class choisie.

Se prémunir des failles CSRF en PHP

Quand on parle sécurité pour un site web, la première chose qui nous vient à l’esprit c’est l’injection sql, ou encore le vol de sessions qui sont les grand classique du hack.
Cependant une autre faille moins connue mais tout aussi dangereuse vous guète : La faille Cross-Site Request Forgeries (CSRF).

Le principe

Il est très simple , on utilise l’utilisateur pour exécuter un fichier auquel on à normalement pas accès. Voici un exemple très simple :
Admettons que sur votre site , dans votre admin vous ayez une page du type supprimer_utilisateur.php?idUser=xxx . Vous vous pensez à l’abri car il faut être admin pour accéder à cette page et c’est là toute la subtilité de la faille.
Un de vos membres pas très sympathique poste un message sur votre forum, ce message contient un lien vers une image ou toute chose capable d’exécuter du code. Vous allez donc naïvement consulter le lien de votre membre et sans vous en rendre compte vous aurez exécuté votre page de suppression avec les paramètres voulu par le hacker.
C’est ce qui rend cette faille très dangereuse, c’est totalement invisible pour la personne piégée !

Comment limiter les risques ?

  • La première chose est de limiter les paramètre en GET et leur préférer le POST plus discret (mais tout aussi modifiable).
  • Demander des confirmations à l’utilisateur sur les actions critiques. Avant de lancer une suppression demander à l’utilisateur si il est réellement certains , voir même lui demander une passphrase secrète.
  • Utiliser des tokens pour toutes les requêtes.

Ce qui ne marche pas : Ce baser sur le référant contenu dans la requête HTTP. C’est modifiable comme toute la requête.

Les Tokens

Ou jetons en bon français est la solution la plus robuste. Elle consiste à attribuer un jeton à l’utilisateur avant d’effectuer une requête (typiquement en arrivant sur le formulaire) et de vérifier ce jeton au moment d’exécuter l’action. Les jetons étant regénéré constamment et ayant une durée de vie courte il est impossible de le prédire à l’avance et donc d’exécuter des requêtes sur votre site depuis des sites/domaines qui ne sont pas prévus pour.

Une classe PHP à la rescousse :

class Util_Token
{
 /**
  * Type d'erreur retournée à la vérification du token
  * 1 = Token non passé en paramètre
  * 2 = token recu != token généré
  * 3 = token expiré
  * @var int
  */
 static public $error = 0;
 
 /**
  * Génère un token et le stocke en session
  *
  * @param int $ttl Durée de vie du token en minute
  */
 static public function genToken($ttl = 15)
 {
  if(!isset($_SESSION))
   session_start();
   
  $token  = hash('sha1',uniqid(rand(),true));
  $rand   = rand(1,20);
  //Sha1  = 40 caractères => 20 de longeur max
  $token  = substr($token,$rand,20);
  $ttl *=60;
  $_SESSION['csrf_protect']    = array();
  $_SESSION['csrf_protect']['ttl']  = time()+$ttl;
  $_SESSION['csrf_protect']['token']  = $token;
 }
 
 /**
  * Récupère le token
  *
  * @return string
  */
 static public function getToken()
 {
  if(isset($_SESSION['csrf_protect']) && !empty($_SESSION['csrf_protect']))
   return $_SESSION['csrf_protect']['token'];
  else
   throw new Util_ExceptionHandler('No token available');
 }
 
 /**
  * Récupère le timestamp de durée de vie
  *
  * @return int
  */
 static public function getTTL()
 {
  if(isset($_SESSION['csrf_protect']) && !empty($_SESSION['csrf_protect']))
   return $_SESSION['csrf_protect']['ttl'];
  else
   throw new Util_ExceptionHandler('No token available'); 
 }
 
 /**
  * Vérifie la validité du token
  *
  * @return boolean
  */
 static public function checkToken()
 {
  if(!isset($_SESSION))
   throw new Util_ExceptionHandler('Can\'t check token if there is no session available');
   
  if(isset($_REQUEST['csrf_protect']) && !empty($_REQUEST['csrf_protect']))
  {
   if($_REQUEST['csrf_protect'] == $_SESSION['csrf_protect']['token'])
   {
    if($_SESSION['csrf_protect']['ttl']-time()>0)
    {
     return true;
    }
    else
    {
     self::$error = 3;
    }
   }
   else
   {
    self::$error = 2;
   }
  }
  else
  {
   self::$error = 1;
  }
  return false;
 }
 
 /**
  * Retourn le code erreur
  *
  * @return int
  */
 static public function getError()
 {
  return self::$error;
 }
}

Concrètement comment ça marche ?
Tout d’abord il faut avoir une session d’active , puis générer un token :

Util_Token::genToken();

On récupère ainsi une chaine basée sur un hash d’une id unique. Ce token est enregistré en session.
Il vous reste ensuite à le passer en paramètre lors de vos requêtes dans input hidden ou directement dans votre chaine de paramètre dans le cas de requête ajax par exemple.

Puis sur la page exécutant la requête il vous suffit de vérifier le token avant toute action :

if(Util_Token::checkToken())    //Executer du codeelse    // Mauvais token ou token expiré

Notez que dans son état actuelle la classe nécessite que le paramèter du token sois nommé csrf_protect.

getElementsByClassName en javascript

Un des gros avantages avec les librairies javascript telles que prototype , jquery ou encore mootools c’est leur système de sélection d’élément dans le DOM.
Par exemple pour sélectionner tous les liens avec la class css « clickme » il suffit de faire :
[javascrip]var elements = $$(‘a.clickme’);[/javascript]
La syntaxe peut varier d’une librairie à l’autre mais dans l’esprit tout est récupérable avec de simples sélecteurs css.
Le problème étant qu’intégrer une librairie de plusieurs kilos pour une simple sélection est une mauvaise idée.
Voici donc une petite fonction getElementsbyClassName en javascript natif qui comme le selecteur $$ permet de retrouver tous les tags d’une page portant une class spécifique.

<pre>/**
* Récupère les éléments d'une classe précise
* @param string tag Nom de la balise
* @param string cssClass Nom de la class Css
* @return array
*/
function getElementByClassName(tag,cssClass){

var foundElements     = new Array();
var allElements        = document.getElementsByTagName(tag);
for(var i=0;i<allElements.length;i++)
    if(allElements[i].className == cssClass)
        foundElements.push(allElements[i]);

return foundElements;
}</pre>

Cette fonction renvoi donc un tableau des éléments retrouvés dans la page. Très simple elle ne prend cependant pas en compte les éléments avec plusieurs class css , mais c’est très facilement intégrable 🙂

Configurer Xdebug et eclipse PDT avec Wamp

Eclipse PDT est un EDI pour PHP très intéressant. Au coté de Zend studio qui lui est payant c’est un peu le visual studio de PHP.
Outre les options habituelles , on peut avec PDT debugger du code pas à pas comme on le ferait avec n’importe quel langage compilé. PDT prend en charge deux débuggers en natif : Xdebug et Zend Debugger , c’est le premier qui nous intéressera .

Installation de XDebug :

La première chose à faire est de télécharger la Dll de xdebug sur le site officiel. Placez alors le fichier récupéré dans le dossier d’extension de PHP, par défaut C:\wamp\bin\php\php-x.x.x\ext\.
Dans le menu contextuel de wamp demandez l’affichage du php.ini
A la fin du fichier (mais avant les dernières lignes commentées) rajoutez ces lignes de config :

[xdebug]
zend_extension_ts= »D:/wamp/bin/php/php5.2.5/ext/php_xdebug-2.0.5-5.2.dll »
xdebug.remote_enable=1
xdebug.remote_host= »127.0.0.1″
xdebug.remote_port=9000
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.show_local_vars=1
xdebug.profiler_enable_trigger=1
xdebug.profiler_output_dir= »D:/wamp/logs/xdebug/ »

Veillez à bien changez les différents chemins pour qu’il correspondent à votre installation. Attention à ne pas modifier la directive xdebug.remote_host. Si vous mettez autre chose que 127.0.0.1 (même localhost) vous risquez de ne rien voir fonctionner.

Configuration de PDT

La première chose à faire est de configurer l’executable php que PDT va utiliser. Pour celà rendez vous dans Windows -> préférences -> PHP ->PHP Executables.
Si vous n’en n’avez pas de configurer , créer en un nouveaux. La seule subtilité est qu’il faut spécifier le chemin du php.ini qui se trouve dans le dossier d’apache car c’est celui ci que wamp utilise.


Toujours dans la configuration php de PDT rendez vous dans la rubrique debug. Configurez alors PDT comme suit :
PHPDebugger : XDebug
Server : Votre serveur par défaut
Executables : l’executables précédemment crée.


A cet instant la configuration est terminée. Si avec un phpinfo(); vous voyez bien une entrée pour Xdebug c’est quasi gagné.

Debugger

Pour débugger rien de bien difficile. il suffit de faire un clic droit sur un fichier puis « Debug as » et choisir l’une des deux options. Vous devriez alors passer en vue debug dans eclipse et avoir accès au point d’arrêt, au mode pas à pas, à l’espion de variables … :


Si jamais au moment de lancer un debug vous voyez que le chargement (en bas à droite) est bloqué sur « Waiting a Xdebug session » c’est très probablement que la navigateur par défaut est mal configuré dans eclipse (ou que xdebug n’est pas installé). Pour être tranquille choisissez le navigateur par défaut du système.

Bon debug !

Zoom Firefox : Anti productivité à l’état pur

Vous connaissez sans doute la fonction zoom de Firefox, très pratique elle permet d’agrandir une page à volonté.
Sauf qu’il peut arriver que cette fonction soit la source d’une perte de temps colossale :

On me demande d’effectuer une légère modification sur un template css en place depuis quelques semaines. Pas de problème je rajoute mes modifications, je test , et là PAF c’est le drame, un décalage d’un pixel dans le header du template…
Je m’affole pas et retire mes modifications , sauf que … le décalage est toujours là. Je vide le cache de mon navigateur , celui du serveur … rien n’y fait.
Gnééééééé >< , je test sur d’autres navigateur pas de problème ! Puis finalement au bout d’une heure de prise de tête je me rend compte que la police sous FF est légèrement plus grande que sous les autres navigateurs. Je me lance alors dans une manipulation hautement technique : Ctrl + 0
Et voilà le décalage disparait …

Zoom firefox ou comment perdre 1h pour avoir fait mumuse avec ma roulette de souris ^_^’

Multilingue Javascript

Développer un site multilingue est assez simple , il existe certain standard tel que gettext sur lesquels il est possible de s’appuyer. En revanche quand arrive le délicat problème du javascript les choses se compliquent.
On peut utiliser la solution bourine du « tableau de traduction » :

<pre>var tabLangue = new Array;
tabLangue["pres_error"]          = "Le formulaire n'est pas correctement rempli";
tabLangue["pres_duree"]          = "La durée doit être une valeur numérique";</pre>

Mais dès qu’il faut intercaler des variables dans les traductions ça devient un enfer!.

J’ai finalement pris mon courage à deux mains et ai pondu une petite classe basée sur [prototype|http://www.prototypejs.org|en] (1.6) qui permet justement d’intégrer très facilement des variables dans les chaines à traduire. Ceux qui ont déjà utilisé gettext ne devrait pas être dépaysé :

var i18n = new I18n();
i18n.load(lang_fr);// On charge l objet de traduction
alert(i18n._('confirm',12)); // clé + argument pour remplacer les %x
alert(i18n._('players','Jean','50')) ;

Les objets de traduction se présentent sous cette forme :

var = lang_fr{
  'confirm' : 'Voulez vous supprimer %0 utilisateurs',
  'players'  : 'Le player de %0 lui à couté %1 euros',
}

Si la classe vous intéresse vous pourrez trouver la source ici : Internationalisation