Authentification salée et poivrée
 >  2018 , Programmation , Défendre et protéger






Une implémentation d'authentification avec protection des identifiants par un mécanisme de protection des empreinte avec le système de grain de sel et de poivre. Cet article, proposera également de stocker des données à l'aveugle, c'est à dire, qu'aucune donnée n'est exploitable sans accès au serveur.





0x01. CONCEPTS



Grain de sel (salt) : le code PHP contient une clé qui sera préfixée ou suffixée au mot de passe afin de protéger le hash du bruteforce de mot de passe

Poivre (pepper) : est stocké dans une base tiers ou dans un fichier inaccessible, un préfixe ou suffixe unique par utilisateur.





0x02. VERIFICATION DES DONNEES



Malgré que le nom utilisateur et le mot de passe soient "hashés", il reste important de vérifier que le username soit cohérant, afin toute autre forme d'injection (XSS & co.)


 
function check_user($user) {
  if( preg_match_all('/^[a-zA-Z][a-zA-Z0-9]{3,}$/',$user) )
    return($user);
  return(NULL);
}
 
function check_pass($pass) {
  if( preg_match_all('/^[a-zA-Z0-9:=;,\\.\\_\\(\\)\\-\\[\\]\\{\\}]{4,}$/',$pass) )
    return($pass);
  return(NULL);
}
 




0x02. EMPREINTE DES DONNEES



Aucune donnée ne sera stockées en claire dans la base de donnée :


 
function sha512($txt) {
  return( openssl_digest( $txt , "sha512") );
}
 
function get_user($user,$salt) {
  return( openssl_digest( $user . DB_SALT , "whirlpool") );
}
 
function get_pass($pass,$salt,$pepper) {
  return( openssl_digest( gzdeflate($pass . $pepper . DB_SALT), "whirlpool") );
}
 




0x02. GRAIN DE SEL



Dans cet exemple, le grain de sel, sera dans les définitions (define). Il pourrait s'agir d'un fichier à lire sur le système de fichiers (hors DocumentRoot).


 
 
define('DB_HOST','localhost');
define('DB_USER','root');
define('DB_PASS','');
define('DB_NAME','test');
 
define('DB_SALT',base64_decode('PkEwcSwoeSNqcW1lbWNoTzpkeD01RDNQWm05ZyZBZm9zL3ksLzUwMiRzPWFfPlFLZTMpcFE9VmUrfXddRHI0UA=='));
 
define('PEPPER_FOLDER','/opt/webapps/pepper_db/');
 
 




0x02. POIVRE



Dans cet exemple, le poivre sera stocké dans un fichier, accessible uniquement depuis le code source PHP. Il pourrait être dans une base de donnée tierce, un fichier SQLite3 par exemple, ou sur un autre serveur.


 
function get_pepper($user) {
  $file_pepper = PEPPER_FOLDER."/".md5($user);
  if( ! file_exists($file_pepper) ) { return; }
  $fh = fopen($file_pepper,"rt");
  $pepper = fgets($fh);
  fclose($fh);
  return($pepper);
}
 
function set_pepper($user,$pepper) {
  $fh = fopen(PEPPER_FOLDER."/".md5($user),"wt");
  fputs($fh,$pepper);
  fclose($fh);
}
 




0x03. AJOUT D'UN UTILISATEUR



La subtilité réside dans le fait que dans cette base, les données concernant le nom d'utilisateur et le mot de passe stockés seront des empreintes, y compris pour les noms d'utilisateurs. Du coup, une injection, n'est pas possible, et même si elle était réussie, les données ne pourrait être retrouvées sans le grain de sel, qui implique un accès au code source.


 
function add_user() {
 
  $pepper = $_SERVER["UNIQUE_ID"]."-".$_SERVER["REQUEST_TIME_FLOAT"]."-".str_rand(96);
 
  $clear_user = check_user($_POST["user"]);
  $clear_pass = check_pass($_POST["pass"]);
 
  if( $clear_user == NULL )
  {
    die("Invalid username");
  }
 
  if( $clear_pass == NULL )
  {
    die("Password doest not match prerequities");
  }
 
  $user = get_user( $clear_user , DB_SALT );
  $pass = get_pass( sha512($clear_pass) , DB_SALT , $pepper );
 
  set_pepper("$user",$pepper);
  $ret = db_query("SELECT user FROM `".DB_NAME."`.`auth_hardened` WHERE user LIKE '$user'; ");
 
  if( isset( $ret->fetch_object()->user ) )
  {
    die("User '$clear_user' already exists.");
  }
 
  db_query("INSERT INTO `".DB_NAME."`.`auth_hardened` (`id`, `user`, `pass`) VALUES (NULL, '$user', '$pass'); ");
  die("<br />'$clear_user' successfully registered.");
 
}
 




0x04. AUTHENTIFICATION



L'authentification repose alors non pas sur la vérification d'un login et d'un mot de passe mais du hash de chacun plus du poivre.


1-a récupérer le nom d'utilisateur
1-b saler et hasher le nom d'utilisateur
1-c vérifier que son poivre existe et le récupérer
2-a récupérer le mot de passe
2-b poivrer, saler et hasher le mot de passe
3- vérifier la présence des 2 hash en base
 
function check_user() {
 
  $score_ok = 0;
  $clear_user = check_user($_POST["user"],ENT_QUOTES);
  $user = get_user( $_POST["user"] , DB_SALT );
 
  $pepper = get_pepper("$user");
  $pass = get_pass( sha512($_POST["pass"]) , DB_SALT , $pepper );
 
  if( $pepper )
  {
    $score_ok++;
  }
 
  $ret = db_query("SELECT user FROM `".DB_NAME."`.`auth_hardened` WHERE user LIKE '$user' AND pass LIKE '$pass';");
 
  if( isset( $ret->fetch_object()->user ) )
  {
    $score_ok++;
  }
 
  if( $score_ok == 2 )
  {
    die("<br />User '$clear_user' successfully logged in.");
  }
  else
  {
    die("<br />Invalid user or pass.");
  }
}
 


Les formulaires d'authentification et d'enregistrement :


 
function form_header() {
  echo 'No login ? <a href="?register">Register</a> | <a href="?login">Login</a><hr />';
}
 
function form_register() {
  echo '<html>';
  echo '<body>';
  echo '  <center><h1>Register :</h1></center>';
  echo '  <hr />';
  echo '  <pre>';
  echo '  <form action="?register" method="post" accept-charset="utf-8">';
  echo '      <label>Username :<input type="text" name="user" value=""></label><br />';
  echo '      <label>Password :<input type="text" name="pass" value=""></label><br />';
  echo '      <label>Password :<input type="submit"></label><br />';
  echo '  </form>';
  echo '  </pre>';
  echo '</body>';
  echo '</html>';
}
 
function form_login() {
  echo '<html>';
  echo '<body>';
  echo '  <center><h1>Login :</h1></center>';
  echo '  <hr />';
  echo '  <pre>';
  echo '  <form action="?login" method="post" accept-charset="utf-8">';
  echo '      <label>Username :<input type="text" name="user" value=""></label><br />';
  echo '      <label>Password :<input type="text" name="pass" value=""></label><br />';
  echo '      <label>Password :<input type="submit"></label><br />';
  echo '  </form>';
  echo '  </pre>';
  echo '</body>';
  echo '</html>';
}
 


Le process de traitement de la page :


 
form_header();
 
if( isset($_POST["user"]) && isset($_GET["register"]) )
{
  add_user();
  die();
}
 
if( isset($_POST["user"]) && isset($_GET["login"]) )
{
  check_user();
  die();
}
 
if( isset($_GET["register"]) )
{
  form_register();
}
else
{
  form_login();
}
 
 




0x05. EN BASE



En base, il y aura alors des données inexploitables :


| id          | user                    | pass
| ----------- | ----------------------- | -------------------------
| 1           | 351c546ca8e7e26dd [...] | 54044dfd6272afe187d [...]
| 2           | 8268cfc50e44aeeb6 [...] | d67017e0bec13de1de6 [...]



0x06. TEST DE L'UTILISATEUR


./files/hard_auth/00_testuser.png

./files/hard_auth/01_userexists.png




0x07. ENREGISTREMENT


./files/hard_auth/02_register.png

./files/hard_auth/03_registered.png




0x08. AUTHENTIFICATION


./files/hard_auth/04_userlogin.png

./files/hard_auth/05_userlogin_ok.png



   =>   Écrit par : Nicolas, le 13 décembre 2018


 
Mots clés :  
  security 
  
  php 
  
  web 
    >   Articles connexes :

/tmp et /var/log en noexec sur macOS



Durcissement de Windows



Troll The Lamer



Se protéger des injections SQL



HTTP Server, tell me who you are ?


Discuter avec un serveur web *apparement* muet ? Voici comment faire...

TLD et Indexes téléphoniques



8864397