Sicherer Login

Orex

Grünschnabel
Hi, ich bin was Sicherheit angeht ein absoluter Neuling.
Habe die letzten Tage das erste mal versucht einen Login "absolut" Sicher zu Programmieren, da ich einen Login benötige der das maximum an Sicherheit bietet.



So jetzt ein Post der eher für die härteren ist... Aber wenn dann richtig******



Vorerst zur Struktur. Ich hoffe meine Projectstruktur ist leicht zuverstehen:

../
aliases enthält die defines
classes enthält die Klassen
processes enthält die processe und ruft die KlassenFunktionen auf
public_html Ordner der vom Webserver als "root" gesehen wird und im www erreichbar ist
projectname
css enthält die css datein
view enthält die formulare bzw. das was zu sehen ist
index.php ruft klassen auf und bindet die benötigten datein ein




Alle Ordner bis auf die CSS Ordner enthalten IMMER 3 Datein.
1. .htaccess (Order deny,allow Deny from all)
2. index.php und index.html

beide enthalten:

HTML:
<html>
    <head>
        <title>ACCESS DENIED</title>
        <meta HTTP-EQUIV="REFRESH" content="0; url=http://www.xy.de/" />
    </head>
    <body></body>
</html>

Das war mein Versuch alles vor einem "oberflächlichem" Zugriff zu schützen. Somit ist kein Ordner aufrufbar und keine Datei die in dem Ordner enthalten ist.
Darüber hinaus ist jede .php Datei nocheinmal am Anfang mit einem code geschützt, der es nicht erlaubt die .php alleine aufzurufen, sondern nur ein include über die index.php zulässt.

Das Project enthält bis jetzt:

  1. Die Möglichkeit sich zu registrieren
  2. Die Möglichkeit sich einzuloggen
  3. Die Möglichkeit sich auszuloggen

Das ist wie folgt realisiert:

der Ordner VIEW welcher sich an der Oben genannten Stelle in /public_html/projectname/view befindet, enthält die Formulare.

REGISTER

PHP:
<?php
defined( '_UEBERINDEX' ) or die( header('location: http://xy.de/') );

if(!isset($_SESSION['usr_id'])){ ?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" id="registerForm"  method="POST" class="registerForm">
    <span class="registerForm">email</span>     <input type="text" name="reg_email" size="10" maxlength="25" class="registerForm" />
    <span class="registerForm">passwort</span>  <input type="password" name="reg_pass" size="10" maxlength="25" class="registerForm" />
    <input type="submit" name="formsubmit_registerForm" value="register" class="registerForm" />
</form>
<?php } ?>


LOGIN

PHP:
<?php
defined( '_UEBERINDEX' ) or die( header('location: http://xy.de/') );

if(!isset($_SESSION['usr_id'])){ ?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" id="loginForm"  method="POST" class="loginForm">
    <span class="loginForm">email</span>       <input type="text" name="login_email" size="10" maxlength="25" class="loginForm" value="" />
    <span class="loginForm">passwort</span>    <input type="password" name="login_pass" size="10" maxlength="25" class="loginForm" value="" />
    <input type="submit" name="formsubmit_loginForm" value="login" class="loginForm" />
</form>
<?php } ?>


LOGOUT

PHP:
<?php
defined( '_UEBERINDEX' ) or die( header('location: http://xy.de/') );

if(isset($_SESSION['usr_id'])){ ?>
<form action="<?php $_SERVER['PHP_SELF']; ?>" id="registerForm" method="POST">
     <input type="hidden" name="logout" value="1">
     <input type="submit" name="formsubmit_logoutForm" value="logout" />
</form>
<?php } ?>


Diese Formulare rufen die Index.php auf über die sie included wurden.

PHP:
<?php
session_start();

    define( '_UEBERINDEX', 1 );
    //ALLE DATEN SHCON HIER AUFBEREITEN

    require_once    '../../classes/user.php';
    require_once    '../../classes/SQLconfig.php';
    require_once    '../../classes/SecurityHandler.php';
    require_once    '../../aliases/aliases.php';


    $user = new User;
    $security = new SecurityHandler();
    $dbconn = new OIConf();
    $dbconn->connect();

    require_once    '../../processes/package_user/package_user_processes.php';
    if(isset($_SESSION['usr_id'])){$security->save_session();}




?>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title></title>
        <?php include 'css/style.php'; ?>
    </head>
    <body>
        <span style="color:red;">
            <?php

    foreach($_SESSION as $k => $v){
        if($k == 'error'){
            foreach($v as $vk => $vv){
            echo $vv.'<br>';}
        }
    

    }
unset($_SESSION['error']);


?></span>
        <div id="top"></div>
        <div id="left">
            <?php
        echo $security->isUserLoggedin();
        include "view/package_user/module_logout/logoutForm.php";
        echo '<br />';
        include "view/package_user/module_register/registerForm.php";
        echo '<br />';
        include "view/package_user/module_login/loginForm.php";
  
        ?></div>
        <div id="middle"></div>
        <div id="right"></div>
    </body>
</html>

<?php $dbconn->disconnect();


?>


Die index.php enthält wie oben gezeigt den Code "require_once '../../processes/package_user/package_user_processes.php';"
Darin sind die .php Datein die die Anfrage der Formulare verarbeiten.

die .php Datein liegen im Ordner /processes und sind nicht über das Webinterface aufrufbar.

jedes Formular hat eine Datei die den Prozess verarbeitet und auch auf ungültige eingaben überprüft, sowie einen Fehler an den User ausgibt wenn etwas nicht stimmt.



REGISTER


PHP:
<?php
defined( '_UEBERINDEX' ) or die( header('location: http://xy.de/') );

if(isset($_POST['formsubmit_registerForm'])){

    
if(strlen($_POST['reg_pass'])< 6){
    $_SESSION['error']['001'] = SESSION_ERROR01;
    $confirmprocess = false;
      
}
if(strlen($_POST['reg_pass'])> 20){
    $_SESSION['error']['002'] = SESSION_ERROR02;
    $confirmprocess = false;
        }


if(strlen($_POST['reg_email'])< 6){
    $_SESSION['error']['003'] = SESSION_ERROR03;
    $confirmprocess = false;
     
}

if(strlen($_POST['reg_email']) > 50){
    $_SESSION['error']['004'] = SESSION_ERROR04;
    $confirmprocess = false;
}

            
           

                    
if(!isset($confirmprocess)){

            $checkMail  =   $security->stringControl($_POST['reg_email']);
            $checkPass  =   $security->stringControl($_POST['reg_pass']);
            $checkMail  =   $security->checkemail($checkMail);

if($checkMail != false && $checkPass != false && !isset($_SESSION['user_id'])){


                
                    $ver_passwort = $security->verschluesseln($checkPass);
                    $user->set_email($checkMail);
                    $user->quick_save_user($ver_passwort, $security->getSalt());

                    echo 'regestriert! als '.$user->get_email().'';


}

elseif(!$checkMail){
    $_SESSION['error']['005'] = SESSION_ERROR05;
}
elseif(!$checkPass){
    $_SESSION['error']['006'] = SESSION_ERROR06;
}
elseif(isset($_SESSION['user_id'])){
    $_SESSION['error']['007'] = SESSION_ERROR07;
}
else{
    $_SESSION['error']['000'] = SESSION_ERROR00;
}

}
}
?>

LOGIN

PHP:
<?php
defined( '_UEBERINDEX' ) or die( header('location: http://xy.de/') );

if(isset($_POST['formsubmit_loginForm']) == 'login'){
    
            $checkPass  =   $security->stringControl($_POST['login_pass']);
            $checkMail  =   $security->stringControl($_POST['login_email']);
                        


if($checkMail != false && $checkPass != false){


                    $user->get_user($checkMail,$checkPass);
                    
                   

}

elseif(!$checkMail){
    $_SESSION['error']['001'] = SESSION_ERROR01; 

}
elseif(!$checkPass){
    $_SESSION['error']['002'] = SESSION_ERROR02; 

}
else{
    $_SESSION['error']['000'] = SESSION_ERROR00;

}
}


?>


LOGOUT

PHP:
<?php
defined( '_UEBERINDEX' ) or die( header('location: http://xy.de/') );


if($_POST['logout'] == 1){


        $_SESSION = array();
        if($_COOKIE['session_name()']){
        setcookie(session_name(), '', time()-42000, '/');
        }
        session_unset();
        session_destroy();
        header('location: /connectionplattform/index.php');
}

?>


Ist die Eingabe O.K. wird die jeweilige Funktion der Klasse aufgerufen, die dann den Eintrag in die Datenbank vornimmt oder Überprüft ob der User in der der Datenbank ist sowie das Passwort verschlüsselt usw.

Dafür gibt es 3 Klassen:

SQLconfig.php enthält die SQL config Datein.
SecurityHandler.php verschlüsselt Passwörter Kontolliert eingaben usw.
user.php alles was mit dem User zu tun hat (user speichern, DB abrufen, einloggen usw.)


Aus Übersichtsgründen kommen die Klassen im nächsten Post....
 
Zuletzt bearbeitet:
Zu den Klassen. Es 3 Klassen:

SQLconfig.php enthält die SQL config Datein.
SecurityHandler.php verschlüsselt Passwörter Kontolliert eingaben usw.
user.php alles was mit dem User zu tun hat (user speichern, DB abrufen, einloggen usw.)



SQLconfig.php

PHP:
<?php
defined( '_UEBERINDEX' ) or die( header('location: http://xy.de/') );

/**
 * Description of SecurityHandler
 *
 * @author 1337
 */

class SecurityHandler {
    private $input;
    private $output;
    private $tryCount;
    private $sessionHash;
    private $salt;

    function isUserLoggedin(){

        if(isset($_SESSION['user_id'])){return true;}
        else{return false;}
    }

    function stringControl($input){
        
        $output = mysql_real_escape_string($input);

        if($input == $output){return $output;}
        else{return false;}

        }

    function erzeugeSalt() {


        $auswahl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789?}]{[%§&!?ß";
        $salt = substr( str_shuffle( $auswahl ), 0, 6 );
        $salt = htmlentities($salt);
        $this->salt = $salt;
        return $salt;
    }


    function verschluesseln($pass, $salt=NULL) {
        if($salt == NULL)
          $salt = $this->erzeugeSalt();
        $cryptedPasswort = hash('xxx'.$pass.$salt.'xxx').'xxx'; //xxx sind fixed salts

        return $cryptedPasswort;
    }  

    function getSalt(){
        return $this->salt;
    }

    function stringControl_id($id){

        $id = (int)$id;
        return $id;
    }

function checkemail($email) {

// externes script! Danke an den Autor "KD3" , hat mir etwas schreibarbeit erspart!!
// http://www.tutorials.de/php-codeschnipsel/303451-email-adresse-ueberpruefen.html

  $email = htmlspecialchars($email);    // Email-Adresse ( z.B someone@gmx.de

  $r = false;                           // Standard auf false gesetzt, weil wenn die
                                        // if-Abfragen abweichend sind und kein true zurückgegeben wird, dann
                                     

      if(preg_match('/(.*?)\@(.*?)\.(\w){2,6}/i', $email)) {

          $split = explode('@', $email);     // [0] => someone , [1] => gmx.de
          $split2 = explode('.', $split[1]); // [0] => gmx , [1] => de

            if(preg_match('/([a-z]){3,64}/i', $split2[0])) {

                if(fsockopen($split[1], 80)) {

                    if(preg_match('/([a-z0-9\!\"\$\&\/\(\)\?\~\#\+\.\:\_\-]+){1,64}[^\@]/i', $split[0])) {

                        $r = true;

                        }
                    }
                }
          }
          if($r == true){return $email;}
          else{return false;}

          
}

function refreshSession(){
        $_SESSION = array();
        if($_COOKIE['session_name()']){
        setcookie(session_name(), '', time()-42000, '/');
        }
        session_unset();
        session_destroy();
        header("location: /index.php");
}

function save_session(){
    $sessionid = session_id();
    $client_ip = $_SERVER['REMOTE_ADDR'];
    $usr_id = (int)$_SESSION['usr_id'];
    
    $query  = "INSERT INTO sec (usr_id,sec_usr_session,sec_usr_ip) Values('".$usr_id."','".$sessionid."','".$client_ip."')";
    $result = mysql_query($query)
    or die(mysql_error());

    return $result;
}

}
?>



SQLconfig.php

PHP:
<?php
defined( '_UEBERINDEX' ) or die( header('location: http://xy.de/') );


class OIConf{
    private $databaseURL = "xxxx";
    private $databaseUName = "xxxx";
    private $databasePWord = "xxxx";
    private $databaseName = "xxxx";
    private $connection;

        
    function connect(){
        $this->connection = mysql_connect($this->databaseURL,$this->databaseUName,$this->databasePWord);     
        $db = mysql_select_db($this->databaseName, $this->connection);
    }

    function disconnect(){
        mysql_close($this->connection);
    }
}
?>



user.php

PHP:
<?php
defined( '_UEBERINDEX' ) or die( header('location: http://xy.de/') );
/*
This is a Guest helper class. Instance of
this class can store Guest information.
*/

class User{
    
    private $UID;
    private $name;
    private $email;
    //private $firstName;
    //private $lastName;
    //private $country;
    //private $streetNr;
    //private $plz;
    //private $city;
    //private $iban;
    //private $swift;
    //private $birthday;
    /* ... */
    private $adress;
    private $balance;
    private $puk;

    
   

    function set_email($email){
            $this->email = $email;
        }
    function set_name($name){
            $this->name = $name;
        }
    function set_balance($balance){
            $this->balance = $balance;
        }
    function set_adress($adress){
            $this->adress = $adress;
        }
        
    function get_UID(){
            return $this->UID;
        }
    function get_name(){
            return $this->name;
        }
    function get_email(){
            return $this->email;
        }
    function get_balance(){
            return $this->balance;
        }
    function get_adress(){
            return $this->adress;
        }

        

    function quick_save_user($password,$salt){

        $query  = "INSERT INTO users (usr_pass,usr_email,usr_puk) Values('".$password."','".$this->get_email()."','".$salt."')";
        $result = mysql_query($query)
        or die(mysql_error());

        return $result;

    }

    function change_user(){

        $query  = "UPDATE users SET usr_email = '" . $this->get_email() ."' WHERE user_id = '". $this->get_UID() ."'";
        $result = mysql_query($query)
        or die(mysql_error());

        return $result;

    }

    function get_user($email, $passwort){

       
        $query      = " SELECT usr_puk
                        FROM users
                        WHERE usr_email = '$email' ";
        $result     = mysql_query($query)
        or die(mysql_error());
        $row        = mysql_fetch_array($result);


        $passwort   = SecurityHandler::verschluesseln_2($passwort,$row["usr_puk"]);

        $query      = " SELECT *
                        FROM users
                        WHERE usr_pass = '".$passwort."'";
        $result     = mysql_query($query)
        or die(mysql_error());
        
        $row = mysql_fetch_assoc($result);


        if(!$row){
            $_SESSION['error']['008'] = SESSION_ERROR08;
 
        }
        else{

        $_SESSION['usr_email']  = $row["usr_email"];
        $_SESSION['usr_id']     = $row["usr_id"];
        
        }

        return true;

    }


}
?>




Eine mir sehr wichtige Frage wäre noch. Ich würde die Userdaten, also EMAIL, USER_ID, NAME, ADRESSE, usw (passwort natürlich nicht) in der $_SESSION variable speichern, da ich es ständig brauche und nicht jedes mal die Datenbank belästigen will.... Ist das sicher?
Falls es zu einem SessionHijacking kommen würde, wäre es eh wurscht weil der Angreifer dann die Daten auch hätte, wenn er die Seiten aufrufen würde. Ändern könnte er nichts, da ich davor nochmal ein Passwort check machen würden..









So abschließend:

JEDEM der das ganze überhaupt liest schonmal ein RIESENGROßES DANKE!

Sollte jemandem dann etwas auffallen was er für eine Sicherheitslücke hält, wäre ich ihm sehr Dankbar wenn er das einfach schnell drunter Posten würde!
Sollte das Script ausgezeichnet sein und keine mängel enthalten, freu ich mich natürlich auch über ein positives feedback.


Vielen Dank
und eine schöne restliche Nacht :D
 
Zuletzt bearbeitet:
Hi,

habe mir jetzt noch nicht alles durchgelesen (dauert ja auch ein bisschen ;)), mir ist aber schon was aufgefallen:

In der SecurityHandler die Funktion erzeugeSalt()
Diese erzeugt bei jedem Aufruf einen anderen Salt, den du dann an das Passwort anhängst. Dieser Salt geht aber verloren oder? Wie soll sich dann der Benutzer nochmals anmelden, bzw. wie soll dann das Passwort überprüft werden? Dynamische Salts müssen von irgendwas, das mit dem Benutzer assoziiert ist zusammenhängen (z.B.: email-adresse, benutzername, vorname etc.)

Ausserdem könntest du das auch so verkürzen:
PHP:
function verschluesseln($pass, $salt=NULL) {
        if($salt == NULL)
          $salt = $this->erzeugeSalt();
        $cryptedPasswort = hash('xxx'.$pass.$salt.'xxx').'xxx'; //xxx sind fixed salts

        return $cryptedPasswort;
    }
Somit sparst du dir die verschluesseln_2().

In der erzeuge_salt() kannst du das htmlentities() rausnehmen, du hast ja nur ASCII-Zeichen:
PHP:
function erzeugeSalt() {
        $auswahl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789?}]{[%§&!?ß";
        $salt = substr( str_shuffle( $auswahl ), 0, 6 );
        $this->salt = $salt;
        return $salt;
    }

In der checkemail() prüfst du auch, ob der Mailserver einen Webserver hat. Diese überprüfung ("if(fsockopen($split[1], 80)) { ") würde ich rausnehmen, ich kenne ein paar Mailserver, die keinen Webserver auf Port 80 laufen haben und somit würden die Adresse abgelehnt werden. Würde hier eher auf Port 25 die Verbindung öffnen (SMTP), der müsste bei jedem Mailserver offen sein.

$_SESSION sollte sicher sein, die Daten können vom Benutzer nicht direkt manipuliert werden da diese nur auf dem Server bleiben.

Dass du jede Änderung an dem User sofort in die Datenbank schreibst finde ich etwas unperformant. Ich habe da normalerweise so einen Ansatz:
PHP:
class Test {
  private $id;
  private $name;
  // weitere felder

  private $is_dirty = false;

  public function __construct($pk_in_db) {
    // von db laden
  }

  public function __destruct() {
    if($this->is_dirty) $this->saveToDb();
  }

  private function saveToDb() {
    // in die db speichern
     $this->dirty = false;
  }

  // Hier die ganzen set-Methoden, sobald ein Wert verändert wird,
  // dann wird die $this->dirty auf true gesetzt.
}

Du kannst im obigen Beispiel also die set-Methoden beliebig oft aufrufen, die Daten werden dann nur einmal in die Datenbank gespeichert (wenn die Seite fertig ist, also der Destruktor aufgerufen wird). Bei dir könnte es halt sein dass du die save-Methode vergisst aufzurufen, bei obigen funktioniert das automatisch :)

Gruß
BK
 
Zuletzt bearbeitet:
Hey, vielen dank schonmal! Werde deine Vorschläge sofort umsetzten und nacher auch die Posts mal ändern!

Der Salt wird in der DB gespeichert. Jeder user hat nen anderen Salt und der wird in der spalte "tan" abgelegt.
 
Zuletzt bearbeitet:

Neue Beiträge

Zurück