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.








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 permettant unique par utilisateur.






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);
}
 





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") );
}
 





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/');
 
 





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);
}
 





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.");
 
}
 





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();
}
 
 





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



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




./files/hard_auth/00_testuser.png

./files/hard_auth/01_userexists.png




./files/hard_auth/02_register.png

./files/hard_auth/03_registered.png




./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

/tmp et /var/log en noexec sur macOS



Durcissement de Windows

Durcissement de Windows



Troll The Lamer

Troll The Lamer



Se protéger des injections SQL

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

TLD et Indexes téléphoniques


2366016