<?php
|
/*---------------------------------------------------------------*/
|
/*
|
Titre : Connexion + sécurité contre le vol de cookies + bruteforce
|
|
URL : https://phpsources.net/code_s.php?id=658
|
Auteur : lovenunu
|
Date édition : 17 Juil 2012
|
Date mise à jour : 24 Sept 2019
|
Rapport de la maj:
|
- refactoring du code en PHP 7
|
- fonctionnement du code vérifié
|
*/
|
/*---------------------------------------------------------------*/
|
|
$db_server = 'localhost'; // Adresse du serveur MySQL
|
$db_name = ''; // Nom de la base de données
|
$db_user_login = 'root'; // Nom de l'utilisateur
|
$db_user_pass = ''; // Mot de passe de l'utilisateur
|
|
// Ouvre une connexion au serveur MySQL
|
$conn = mysqli_connect($db_server,$db_user_login, $db_user_pass, $db_name);
|
|
// Pour que ce code fonctionne, il faut évidement que la table membre
|
// existes,
|
// et qu'elle contienne les colones login et pass
|
|
class security {
|
|
global $conn;
|
|
// Méthode de sécurisation des variables contre les injections sql
|
public function secure($value) {
|
$value = urlencode($value);
|
$value = preg_replace("#\'#", "", $value);
|
$value = preg_replace("#\-#", "", $value);
|
|
return $value;
|
}
|
|
// Simplification de la lecture d'un fichier
|
public function read_file($file) {
|
$file = fopen($file, 'r');
|
$buf = "";
|
while($line = fgets($file)) {
|
$buf.=$line;
|
}
|
fclose($file);
|
return $buf;
|
}
|
|
// Simplification de l'écriture d'un fichier
|
public function write_file($file, $text) {
|
file_exists($file) ? unlink($file): $ret=1;
|
$file = fopen($file, 'a');
|
fputs($file, $text);
|
fclose($file);
|
return TRUE;
|
}
|
|
// Anti bruteforce
|
public function anti_brute($rep, $lim) {
|
// Si le dossier demandé n'existe pas, on le créé.
|
if(!is_dir($rep))
|
mkdir($rep);
|
|
// On récupère l'ip de l'utilisateur pour trier les tentatives de
|
// connexion
|
$ip = (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) ? $_SERVER[
|
'HTTP_X_FORWARDED_FOR']: $_SERVER['REMOTE_ADDR'];
|
// Ici on stocke dans $ip l'adresse publique du client, même si il passe par
|
// un
|
// proxy.
|
// Adresse du fichier à écrire
|
$file=$rep."/".$ip;
|
|
if(file_exists($file)) {
|
|
// Si le fichier existes ( c'est à dire que l'utilisateur avec l'ip $ip s'est
|
// déjà trompé.
|
$tmp = $this->read_file($file); // On lit le fichier
|
$tmp = explode("-", $tmp);
|
// On l'explose pour récupérer d'un coté le nombre de tentatives, et le
|
// timestamp de la dernière
|
$i = $tmp[0]; $time = $tmp[1];
|
|
|
// Si la dernière tentative était il y a 24h, on remet le compteur à 0
|
if($time <= (time()-(24*3600))) {
|
unlink($file);
|
$this->anti_brute($rep, $lim);
|
}
|
|
|
// Si le nombre de tentative est en dessous de la limite, on incrémente le
|
// compteur et on met à jour le fichier
|
if($i < $lim) {
|
$this->write_file($file, ($i+1)."-".time());
|
// Si la limite est atteinte, on tue le script.
|
} else if($i >= $lim) {
|
$this->write_file($file, $lim."-".time());
|
die("Security error | Limit: ".$lim." reached");
|
}
|
} else {
|
// Première erreur, on met à jour le fichier
|
$this->write_file($file, "1-".time());
|
}
|
|
return TRUE;
|
}
|
|
// Un méthode un peu bourrin, mais n'est-on jamais trop sur ?
|
public function hash_password($value) {
|
$value= md5("salt1").$value.md5("salt2");
|
//On parse le mot de passe avec deux grains de sel.
|
$value=sha1($value); // On passe cette valeur au sha1
|
$value=md5("salt3").$value.md5("salt4"); // Encore des grains de sel.
|
$value=sha1($value).md5($value);
|
// Et un final: on concatène le sha1 et le md5 de la valeur précédente.
|
|
return $value;
|
}
|
|
|
// Encore un peu bourrin, mais comme à§a, même une personne motivée et
|
// avec
|
// d'énormes rainbow tables sera déstabilisée !
|
public function parse_cookie($login, $pass) {
|
//On doit fournir le $pass déjà hashé pour que cela soit utile
|
$login = md5("salt1").$login.md5("salt2");
|
// Quelques grains de sel pour le login
|
$login = base64_encode(md5($login)); // Encore quelques fonctions
|
$ip = (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) ? $_SERVER[
|
'HTTP_X_FORWARDED_FOR']: $_SERVER['REMOTE_ADDR'];
|
// Ici on stocke dans $ip l'adresse publique du client, même si il passe par
|
// un
|
// proxy.
|
$cookie = md5("salt3").$login.sha1(md5($ip)."00001").$pass;
|
// Tant qu'à hasher, au le faire en gros ?
|
$cookie = md5("salt4".base64_encode($cookie)."salt5");
|
// Encore en toujours !
|
|
return $cookie;
|
}
|
|
// Définition des cookies
|
public function connect($login, $pass) {
|
$expire = time()+24*3600*365;
|
// La date d'expiration est une année après
|
|
// On définie les cookies login et parse, le premier nous sert à resortir
|
// le nom
|
// du membre, le second à vérifier sa connexion
|
setcookie("login", $login, $expire);
|
setcookie("parse", $this->parse_cookie($login, $pass), $expire);
|
}
|
|
// Vérification des cookies
|
public function check_cookie($login,$parse) {
|
$sql = mysqli_query($conn, "SELECT password FROM membres WHERE login='".
|
$login."'");
|
$sql = mysqli_fetch_array($sql);
|
|
if(!empty($sql) and $this->parse_cookie($login,$sql['password']) ==
|
$parse)
|
return TRUE;
|
else
|
return FALSE;
|
}
|
|
// Vérification des entrées de connexion
|
public function check_connect($login, $pass) {
|
$ret = 0; //variable de retour
|
$sql = mysqli_query($conn, "SELECT password FROM membres WHERE login ='"
|
.$login."'"); // On récupère les ressources mysql
|
$sql = mysqli_fetch_array($sql); // On en fait un array
|
// Si ce tableau est vide: le compte n'existe pas.
|
if(empty($sql)) {
|
$ret=1;
|
// On retourne une valeur si on veut pouvoir traiter les erreurs séparement.
|
// 1:
|
// le login n'existe pas
|
// Si le tableau n'est pas vide, on vérifie le mot de passe
|
} else {
|
if($this->hash_password($pass) == $sql['password']) {
|
$this->connect($login,$this->hash_password($pass));
|
} else {
|
$this->anti_brute("membres", 20);
|
// on applique l'anti brute-force
|
$ret=2; // 2: Le pass est faux
|
}
|
}
|
return $ret;
|
}
|
}
|
|
|
|
// Nouvel objet security
|
$s = new security();
|
|
/*
|
Pour ajouter un utilisateur text:
|
mysql_query("INSERT INTO membres VALUES(NULL,'admin',
|
'".$s->hash_password('password')."')");
|
login: admin
|
password: password
|
*/
|
|
// Connexion active
|
$cookie_login = (isset($_COOKIE['login']) and !empty($_COOKIE['login'])) ? $s->
|
secure($_COOKIE['login']): FALSE;
|
$cookie_parse = (isset($_COOKIE['parse']) and !empty($_COOKIE['parse'])) ?
|
$_COOKIE['parse']: FALSE;
|
|
// Essai de connexion
|
$login = (isset($_POST['login']) and !empty($_POST['login'])) ? $s->secure(
|
$_POST['login']):FALSE;
|
$pass = (isset($_POST['pass']) and !empty($_POST['pass'])) ? $_POST['pass']:
|
FALSE;
|
|
// Tentative de connexion
|
if($login and $pass)
|
$erreur= ($s->check_connect($login, $pass)) ? "Erreur de connexion":"";
|
|
// Vérification des cookies
|
if($cookie_login and $cookie_parse)
|
$erreur= (!$s->check_cookie($cookie_login, $cookie_parse)) ?
|
"Erreur de cookie":"";
|
|
|
// Tout le système est près, maitenant on peut afficher la page qu'on veut
|
// !
|
|
// Logique: si $erreur n'existe pas, c'est que l'utilisateur n'a ni essayé de
|
// se
|
// connecter, ni de cookie, il se balade juste !
|
if(!isset($erreur)) {
|
// On fait ce qu'on veut pour notre invité !
|
// On va faire un formulaire de connexion
|
?>
|
<form action="<?php print $_SERVER['SCRIPT_NAME']; ?>" method="post">
|
<label for="login">Login: </label>
|
<input type="text" name="login" /><br/>
|
<label for="pass">Password: </label>
|
<input type="password" name="pass" /><br/>
|
<input type="submit" value="Connexion" />
|
</form>
|
<?php
|
} else {
|
if(empty($erreur)) {
|
// Notre utilisateur est réglo, on fait ce qu'on veut !
|
} else {
|
// Pas cool, il y a une erreur !
|
print $erreur;
|
}
|
}
|
?>
|
|
|