Protection contre les failles csrf : cross site request forgeries

Soyez le premier à donner votre avis sur cette source.

Vue 9 636 fois - Téléchargée 521 fois

Description

La faille de type CSRF consiste à dévier une requette pour faire effectuer à l'utilisateur une manipulation non voulue en exploitant ses droits d'identification.

Plus concrètement, un administrateur ouvre une session d'administration, puis navigue, soit sur le site actuel, soit sur un autre site. Dans les deux cas, un hackeur pourrait injecter par exemple dans la page navigué par l'administrateur une image pointant vers un lien de supression. Avec une injection de JS on pourrait éventuellement exploiter du POST.

Le tout se sert du principe que l'utilisateur posséde une session ouverte, donc par conséquent effectue les actions sans le savoir.

La protection consiste à créer des jetons validant l'accès à une page. Ceci permet de vérifier que l'utilisateur à réellement visité la page d'administration, puis cliqué sur le lien de suppression qui n'est plus générique puisqu'il contient l'info du jeton.

Une fois le jeton utilisé il est aussi tôt invalidé, donc pas possible d'actualiser une demande déjà traitée.

Le jeton est généré à chaque chargement, invalidant l'ancien token, vous ne pouvez lancer plusieurs pages d'actions en même temps (cas d'un iframe caché).

Le token est pris en fonction de l'IP de celui qui navigue, donc au cas où la session serait volée, le token serait de toutes les façons invalide.

Source / Exemple :


<?php
	include('csrf.class.php');
	
	// CHECK FOR GET
	if (isset($_GET['action'])) {
		if (!CrossSite::check()) {
			die('UNAUTH');
		}
		echo '<h1>Doing '.$_GET['action'].'</h1>';
	}
	
	// CHECK FOR POST
	if (isset($_POST['send'])) {	
		if (!CrossSite::check()) {
			die('UNAUTH : token');
		}		
		if (!CrossSite::checkSameScript()) {
			die('UNAUTH : another script');
		}
		if (!CrossSite::checkVars()) {
			echo '-- Possible tentative d\'injection : ';
		}
		echo '<h1>Posting '.$_POST['value'].'</h1>';
	}
	
?>
Try this (on localhost) :
<a href="http://localhost<?php echo $_SERVER['SCRIPT_NAME']; ?>?action=delete&<?php echo CrossSite::getLinkToken(); ?>">
	Do an delete action
</a>
<hr />
Same link from 127.0.0.1 :
<a href="http://127.0.0.1<?php echo $_SERVER['SCRIPT_NAME']; ?>?action=delete&<?php echo CrossSite::getLinkToken(); ?>">
	Do an delete action
</a>
<hr />
<form method="post">
	Send a form data : 
	<input type="text" name="value" value="<script>alert('toto');</script>" />
	<input type="submit" name="send" />
	<?php echo CrossSite::getInputToken(); ?>
</form>

Conclusion :


Bonne prog à tous,
Akh

Codes Sources

A voir également

Ajouter un commentaire

Commentaires

Messages postés
146
Date d'inscription
vendredi 28 mai 2010
Statut
Membre
Dernière intervention
21 juillet 2013
3
Je vais essayer.

Mais rappelons aussi que le .htacces est aussi efficace pour combler certaines failles (XSS, ...).

Cdt.
Messages postés
7
Date d'inscription
dimanche 20 juillet 2008
Statut
Membre
Dernière intervention
20 avril 2010

Salut j'ai regardé un peu le code source de ta classe, dans la méthode escapeValue, il serait plus judicieux de supprimer les commandes JavaScript par une fonction de remplacement insensible à la classe, le mieux serait même d'utiliser une regex ; )
Messages postés
1293
Date d'inscription
mardi 9 novembre 2004
Statut
Membre
Dernière intervention
21 mai 2015

tiens...

function uv_getVar($var_name) {
if (isset($_SERVER[$var_name])) {
return $_SERVER[$var_name];
}
if (isset($_ENV[$var_name])) {
return $_ENV[$var_name];
}
if(($env = @getenv($var_name)) !== false) {
return $env;
}
if (function_exists('apache_getenv')) {
if(($env = @apache_getenv($var_name, true)) !== false) {
return $env;
}
}
return '';
}

function uv_getIP()
{
$proxy_ip = '';
$i = -1;
$where = array(
'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED',
'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED',
'HTTP_VIA', 'HTTP_X_COMING_FROM', 'HTTP_COMING_FROM'
);

while(!empty($proxy_ip) && isset($where[++$i])) {
$proxy_ip = uv_getVar($where[$i]);
}

if (empty($proxy_ip))
return uv_getVar('REMOTE_ADDR');
else
{
$is_ip = preg_match('|^([0-9]{1,3}\.){3,3}[0-9]{1,3}|', $proxy_ip, $regs);
if (!empty($is_ip) && (count($regs) > 0))
return $regs[0];
else
return $proxy_ip;
}
}

@ tchaOo°
Messages postés
1293
Date d'inscription
mardi 9 novembre 2004
Statut
Membre
Dernière intervention
21 mai 2015

Slt...

- je n'avais pas vu pour le singleton (j'ai pas épluché la source en détail) du coup c'est pas du token dont je parle mais de la clef tokenKey() qui plus est ça fait peut être beaucoup une string de 32 char comme index (ça prend de la place pour rien dans une requête GET) et je ne suis pas sûr que le gain soit énorme niveau sécu

- tu sais comme moi que récupérer l'ip via php (et même tout court) est pas super fiable, d'autant plus quand l'internaute est derrière un proxy ou un réseau cependant tu as des variables transmise par les proxy notament les x_forwarded et http_via qui peuvent être utile si renseignées... tu pourra trouver un exemple dans le code source de phpmyadmin... après il reste toujours une marge d'erreur mais c'est mieux que de se baser uniquement sur remote_addr

- j'avais pas fait gaffe... j'ai rien dis...

- oui tu peut facilement modifier le user_agent mais encore faut il connaitre le user_agent de la personne que tu vise ce qui est faisable mais c'est une variable de plus... de toute façon si un gars réussis à récupérer le user_agent exacte (versioning & co) de sa cible il y a fort a parier qu'il aura récupéré l'ip aussi donc bon... c'est pas pour ce que ça change

concernant l'ip elle même maintenant... tu récupère le host via un classique gethostbyaddr() tu extrait l'ip qui est généralement renvoyée par le serveur BAS sur lequel est relié l'internaute du coup tu te retrouve avec le non du FAI et généralement avec le code du NRA (des fois même le n° de carte DSLAM et/ou les vp/vc DSLAM/BAS mais c'est plus rare faut pas trop compter dessus) que tu peux coupler avec les deux premiers blocks de l'ip qui sont propres au FAI le tout mixé via md5 avec, éventuellement, le user agent et là ton token reste fiable tout en évitant le plus possible les pb dû aux ip dynamique... c'est un poil moins fiable que l'ip au complet car certains FAI on une résolution ip => host un peu pauvre en infos (pas de nom de NRA par ex) mais ça reste, à mon sens, suffisamment fiable pour la plupart des utilisations... tu peux aussi te servir de uniqid() pour corser le tout...

uniqid(md5($uvars),true)

mais là ça devient de la parano je pense... .. . ;o)

@ tchaOo°
Messages postés
276
Date d'inscription
dimanche 22 juillet 2001
Statut
Modérateur
Dernière intervention
5 décembre 2013

Bjr Kankrelune,

Merci pour ton com, juste quelques remarques suite au tiennes :

- Le token est généré (md5) qu'une seule fois au chargement de la page, c'est un singleton.

- La récup de l'IP ne prend pas en compte les proxy, parcontre je ne vois pas d'autres moyens pour récupérer l'adresse IP de celui qui demande la page - si tu pouvais m'en dire plus ...

- Pour le check du token, je suis d'accord avec toi, pas besoin de vérifier s'il y a injection. C'est pour ça que la fonction check qui valide le token n'execute pas checkVars qui est une fonction indépendante. Je l'ai implémentée en tant que fonction utilitaire car sur certains formulaires les actions ne sont pas faites que par des personnes de confiance.

Concernant la remarque sur les FAI tu n'as pas tord, mais malgré tout le user_agent ne me plait pas car le client peut y mettre ce qu'il veut.
Pour le remote host, la variable n'est pas toujours renseignée, faut qu'apache soit configuré (mon code doit être générique et tourner sur un max de config). Concernant l'histoire d'ip et hostname si tu connais une lib d'autres fonctions, je suis vraiment preneur.

Bonne prog,
akh
Afficher les 15 commentaires

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.