[Einsteiger-OOP] Userverwaltung mit Anti-Brutoforcing basierend auf Cookies/Sessions!
Ankündigung
Einklappen
Keine Ankündigung bisher.
[Einsteiger-OOP] Userverwaltung mit Anti-Brutoforcing [...]
Einklappen
Neue Werbung 2019
Einklappen
Dieses Thema ist geschlossen.
X
X
-
Hallo,
willkommen bei meinem ersten Tutorial. Ich habe mir vor einigen Tagen vorgenommen, ein bischen OOP zu erlernen! So habe ich mich kurzerhand dazu entschlossen, eine kleine Userverwaltung zu schreiben!
Diese basiert wunschweise auf Cookies und/oder auf Sessions! Die Klasse setzt 1e MySQL Datenbank mit 2 Tabellen vorraus, PHP4 oder höher und ein paar Minuten für die Installation!
Das Tutorial:
1. Feature der Klasse
2. Erklärung der Klasse
3. Admin Bereich
4. Installation
5. Anwendungsbeispiele für Logout und für geschütze Bereiche!
Als erstes möchte ich euch zeigen, was die userverwaltung kann:
PHP-Code:<?php
/******************************************
* Features:
* - einfache Installation
* - Supervisor, welcher direkt in dieser Datei
* angepasst werden kann! Hat immer Zugriff
* als Admin!
* - Benutzung von Sesseions und/oder Cookies
* zur Authentifizierung!
* - Debugging ein- bzw ausstellbar!
* - Cookie-lebensdauer festlegbar!
* - Cookiename einstellbar!
* - Maximale Anzahl an fehlgeschlagenen
* Logins in einer bestimmten Zeitspanne
* einstellbar!
* - Einfach Verwendung (Siehe Anwendungsbeispiele!)
* - Bei jedem Aufruf einer Secret Area wird
* überprüft, ob der User das Recht hat, die Page
* zu sehen!
* - Bestimmte Bereiche festlegbar, welche nur
* Admins sehen dürfen!
* - Falls ein User ohne Authentifizierung eine
* geschützte Seite aufruft, wird das Login
* Formular auf dieser Seite angezeigt und
* er ist danach auch direkt auf dieser Seite!
* - Anti-Hacker Schutz mit Simulierung einer
* variablen Antwortzeit des Servers, um Massen-
* requests zu blocken!
*/
Am Anfang wird die Session gestartet und der Output Buffer gestartet, sodass alles erstmal gebuffert wird, und am Ende sortiert an den Klient geschickt wird.
PHP-Code:<?php
session_start();
ob_start();
Wenn Ihr jedoch ob_start() verwendet, werden alle Ausgaben und Header zuerst gebuffert, und am Ende sortiert an den Klienten geschickt!
So, als nächstes wird ein array angelegt, welches alle anzupassenden Variablen enthält:
PHP-Code:<?php
/* Some vars: */
$cfg = array();
/* LogIn: */
$cfg['user'] = 'ad'; // Login Username für Supervisor
$cfg['pass'] = 'ad'; // Login Passwort für Supervisor
/* MySQL Table Prefix: */
$cfg['tp'] = 'userlogin_t_'; // Tabellen Prefix!
/* Debug? */
$cfg['debug'] = 1; // 1 = true, 0 = false;
$cfg['notices'] = 0; // show notices. 1 = true, 0 = false;
/* MySQL Connection Settings: */
$cfg['db_host'] = 'localhost'; // DB-Host
$cfg['db_user'] = 'root'; // DB-Username
$cfg['db_pass'] = ''; // DB-Passwort
$cfg['db_db'] = 'test_login'; // Datenbank
/* Templates: */
$cfg['tpl_dir'] = 'tpl/'; // Template Verzeichnis
$cfg['tpl_form'] = 'form.html'; // Template für Login Formlar
/* How to login: */
$cfg['use_sessions']= 1; // Sessions nutzten?
$cfg['use_cookies'] = 1; // Cookies nutzten?
/* Cookies: */
$cfg['cookie_name'] = 'login_'; // Cookie Name
$cfg['cook_timeout']= 60*60*24*10; // Wie lange gelten Cookies in Sekunden!
$cfg['seperator'] = '|.|!|.|'; // Seperator when build string of pw and
// username to save in only 1 cookie!
$cfg['c_path'] = '/'; // Path, in which the cookie is available
// no neet to change this!
/* Anti-Brutoforcing: */
$cfg['retrys'] = 3; // Max. Login Versuche in Zeitspanne: $cfg['timeout'];
$cfg['timeout'] = 3600; // In dieser Zeitspanne darf der User max $cfg['retrys']
// login versuche machen!
/* Admin */
$cfg['adminpage'] = 'admin.php'; // Admin Menü!
Danach wird festgelegt, ob Notices von dem Parser ausgegeben werden sollen oder nicht! Falls $cfg['notices'] auf 1 steht, werden diese per ini_set() und error_reporting(E_ALL) eingeschaltet, ansonsten aus:
PHP-Code:<?php// Notices zeigen, oder nicht:
if ($cfg['notices'] == 1) {
error_reporting(E_ALL);
@ini_set('display_errors', '1');
}
else {
error_reporting(0);
@ini_set('display_errors', '0');
}
Nun wird die Klasse eingeleitet
PHP-Code:<?php
class login {
PHP-Code:<?php
/*
* admin username
*/
var $_adminUser;
/*
* admin password
*/
var $_adminPass;
/*
* Admin page:
*/
var $_adminPage;
/*
* is admin?
*/
var $_isAdmin;
/*
* use sessions?
*/
var $_useSessions;
/*
* use cookies?
*/
var $_useCookies;
/*
* name of the cookie
*/
var $_nameCookie;
/*
* path to aply cookie to
*/
var $_cookiePath;
/*
* seperates cookiestring of
* password and username
*/
var $_cookieSep;
/*
* How long logged in?
*/
var $_cookieTimeout;
/*
* do debug?
*/
var $_debug;
/*
* table prefix
* for mysql tables
*/
var $_tp;
/*
* max failed logins
* per ip (anti-brutoforce)
*/
var $_maxLogins;
/*
* user can retry login after
* this time if he had more than
* $_maxLogins failured logins
*/
var $_timeOut;
/*
* template direction:
*/
var $_tplDir;
/*
* formular template
*/
var $_tplForm;
/*
* DataBase
* db host
*/
var $_dbHost;
/*
* DataBase
* db user
*/
var $_dbUser;
/*
* DataBase
* db pass
*/
var $_dbPass;
/*
* DataBase
* db database
*/
var $_dbDb;
/*
* DataBase
* contains connection
*/
var $_dbConnected;
/*
* DataBase
* number of querys:
*/
var $_dbQueries;
/*
* DataBase
* last error:
*/
var $_dbError;
/*
* DataBase
* selected db
*/
var $_dbSelectedDb;
Nun wird der Konstruktor festgelegt, der den eben festgelegten Variablen Werte zuweißt. Außerdem bricht er das Script ab, falls die install.php noch existiert (aus Sicherheitsgründen muss diese nach der Installation gelöscht werden!):
PHP-Code:<?php
/*
* Konstruktor:
* inits vars, connects to mysql
* server and selects db!
*/
function login($par) {
// does install.php still exist
// and is the actual file != install.php
// so exit and print error!
if(file_exists("install.php") && basename($_SERVER["PHP_SELF"]) != "install.php")
die("<font color='red'>Löschen Sie die install.php!</font>");
// init misc var's:
$this->_adminUser = $par['user'];
$this->_adminPass = $par['pass'];
$this->_tp = $par['tp'];
$this->_maxLogins = $par['retrys'];
$this->_useSessions = $par['use_sessions'];
$this->_useCookies = $par['use_cookies'];
$this->_nameCookie = $par['cookie_name'];
$this->_timeOut = $par['timeout'];
$this->_cookieTimeout=$par['cook_timeout'];
$this->_debug = $par['debug'];
$this->_adminPage = $par['adminpage'];
$this->_cookieSep = $par['seperator'];
$this->_cookiePath = $par['c_path'];
// init some tpl var's:
$this->_tplDir = $par['tpl_dir'];
$this->_tplForm = $par['tpl_form'];
// init some connection var's:
$this->_dbHost = $par['db_host'];
$this->_dbUser = $par['db_user'];
$this->_dbPass = $par['db_pass'];
$this->_dbDb = $par['db_db'];
// MySQL: is there a connection?
if(!($this->_dbConnected))
$this->db_connect();
// MySQL: is db selected?
if(!($this->_dbSelectedDb))
$this->db_select_db();
}
Desweiteren wird gecheckt, ob der Variable, welche für das Connection Handle da ist (var _dbConnected) bereits festgelegt ist. Falls nicht, wird die Methode db_connect() aufgerufen, um eine Verbindung herzustellen! Selbes gilt auch für die Variable _dbSelectedDb, welche die selektierte Datenbank enthält!
Nun folgen die Conenction und die SelectDb methode:
PHP-Code:<?php
/*
* connects to mysql server
*/
function db_connect() {
$link = @mysql_connect($this->_dbHost, $this->_dbUser, $this->_dbPass, $this->_dbDb);
if(!$link) {
$this->_dbError = mysql_errno().' => '.mysql_error();
// debug:
$this->db_debug();
}
else
$this->_dbConnected = $link;
}
/*
* checks, if debug is on.
* if yes, then show error!
*/
function db_debug() {
if( $this->_debug == 1)
echo $this->_dbError;
}
/*
* selects the db
*/
function db_select_db() {
$_selDb = @mysql_select_db($this->_dbDb);
if(!$_selDb) {
$this->_dbError = mysql_errno().' => '.mysql_error();
$this->db_debug();
}
else
$this->_dbSelectedDb = $_selDb;
}
Nun geht es mit 2 Methoden weiter:
PHP-Code:<?php
/*
* checks, if there is a database connection!
* if not, connecte and select db
*/
function db_check_connection() {
if(!isset($this->_dbConnected))
$this->db_connect();
if(!isset($this->_dbSelectedDb))
$this->db_select_db();
}
/*
* querys a sql statement.
* can return an error!
* counts up the number of total querys
*/
function db_query($sql) {
$this->db_check_connection();
$res = @mysql_query($sql);
if($res) {
if($this->_dbQueries == "" OR $this->_dbQueries == 0 OR !($this->_dbQueries))
$this->_dbQueries = 1;
else
$this->_dbQueries++;
return $res;
}
else {
$this->_dbError = mysql_error();
$this->db_debug();
return false;
}
}
db_query wird später unsere SQL Statements an den Server senden. Per @ werden wiederum mögliche Fehler unterdrückt, welche aber trotzdem in _dbError geschrieben werden, und bei aktiviertem debugging angezeigt werden!
Die nächste Methode speichert die aktuelle IP und den aktuellen Timestamp, falls der Login aufgrund von falschen Daten fehlschlägt
PHP-Code:<?php
/*
* connection failed, insert
* ip to handle brutoforce
*/
function save_failed_login() {
// checks connection! if not: connect / select db!
$this->db_check_connection();
$sql = "INSERT INTO ".
$this->_tp."failedlogins
(ip, time)
VALUES
('".$_SERVER["REMOTE_ADDR"]."', '".time()."')";
$res = $this->db_query($sql);
}
PHP-Code:<?php
/*
* check failed logins
* to avoid brutoforce
*/
function check_failed_logins() {
// checks connection! if not: connect / select db!
$this->db_check_connection();
$sql = "SELECT
id, ip, time
FROM
".$this->_tp."failedlogins
WHERE
ip = '".$_SERVER["REMOTE_ADDR"]."'
AND
time > '".(time() - $this->_timeOut)."'";
$res = $this->db_query($sql);
if($res) {
$anzahl = mysql_num_rows($res);
return $anzahl;
}
else {
return false;
}
}
Die folgende Methode vergleicht nun die durch check_failed_login() zurückgegebenen Fehllogins mit der in $cfg['retrys'] festgelegten höchstanzahl an Loginversuchen, und gibt true zurück, wenn man noch Loginversuche frei hat, und false, wenn man die höchstzahl bereits erreicht hat:
PHP-Code:<?php/*
* gets number of failured logins
* if to high, then return false
*/
function may_try_login() {
$anzahl = $this->check_failed_logins();
if( isset($anzahl) ) {
if( $this->check_failed_logins() < $this->_maxLogins )
return true;
else
return false;
}
else {
return false;
}
}
PHP-Code:<?php/*
* checks, if user exists
* and if this is the right pw
*/
function check_user($user, $pass) {
if($this->_adminUser == $user && md5($this->_adminPass) == $pass) {
// let us now, he is admin:
$this->authenticate_as_admin();
return true;
}
else {
// checks connection! if not: connect / select db!
$this->db_check_connection();
$sql = "SELECT
id, user, pass, is_admin
FROM
".$this->_tp."users
WHERE
user = '".$user."'
AND
pass = '".$pass."' ";
$res = $this->db_query($sql);
if($res) {
$anzahl = mysql_num_rows($res);
if($anzahl != "0") {
$row = mysql_fetch_assoc($res);
if(trim($row["is_admin"]) == "yes") {
// let us now, he is admin:
$this->authenticate_as_admin();
}
return true;
}
return false;
}
else
return false;
}
}
Folgende Methode checkt die Daten, die per POST von einem Formular kommen. Zuerst werden mögliche Leerstellen entfernt, das PW wird als md5() Hash gespeichert und dann wird das Verglichen! Falls er als Supervisor eingeloggt ist, wird er wiederum als Admin authentifiziert, falls nicht, wird die DB nach einem User durchsucht, dessen PW dem gesendeten PW entspricht, und dessen Username ebenfalls dem gesendeten username entspricht. Ist er als Admin festgelegt, wir er in der Klasse auch als Admin definiert:
PHP-Code:<?php
/*
* checks data coming from the form
* and saves login data to cookie/session
*/
function check_post_data($user, $pass) {
$user = addslashes(trim($user));
$pass = trim($pass);
if( trim($this->_adminUser) == $user
&& md5(trim($this->_adminPass)) == md5($pass) ) {
// he is admin:
$this->authenticate_as_admin();
// Save 2 sessions & cookie
if($this->_useCookies == 1) {
$this->setup_cookies($user, $pass);
}
if($this->_useSessions == 1) {
$this->setup_sessions($user, $pass);
}
// Against Hackers, who sends too many queries! Lets the Server
// simulate a time between 0,5 and 1,5 seconds to answer request
usleep(rand(500000,1500000));
return true;
}
else {
$ok = $this->check_user( $user, md5($pass) );
if($ok) {
// Save 2 sessions & cookie
if($this->_useCookies == 1) {
$this->setup_cookies($user, $pass);
}
if($this->_useSessions == 1) {
$this->setup_sessions($user, $pass);
}
// Against Hackers, who sends too many queries! Lets the Server
// simulate a time between 0,5 and 1,5 seconds to answer request
usleep(rand(500000,1500000));
return true;
}
else {
// Against Hackers, who sends too many queries! Lets the Server
// simulate a time between 2 and 4 seconds to answer request
sleep(rand(2,4));
return false;
}
// Against Hackers, who sends too many queries! Lets the Server
// simulate a time between 2 and 4 seconds to answer request
sleep(rand(2,4));
return false;
}
}
DIe nächste Methode muss in die Secret Area gepackt werden. Sie checkt, ob der Login auf Sessions und/oder auf Cookies basiert. Danach checkt sie ab, ob die Daten in Session und/oder Cookies korrekt sind, dazuu werden diese an die Methode check_user(), übergeben!
PHP-Code:<?php
/*
* include this into secret area
*/
function may_person_view() {
$ok = false;
if($this->_useCookies == 1) {
// get str:
$str = @$_COOKIE[$this->_nameCookie.'user_pw'];
// explode string on seperator:
$e = explode($this->_cookieSep, $str);
$user = @$e[0];
$pass = @$e[1];
if( $this->check_user( $user, $pass) ) {
$ok = true;
}
}
if($this->_useSessions == 1) {
if( $this->check_user(@$_SESSION['user'], @$_SESSION['pass']) ) {
if( $this->_useCookies == 1 && $ok == true )
$ok = true;
elseif( $this->_useCookies == 1 && $ok == false )
$ok = false;
else
$ok = true;
}
else {
$ok = false;
}
}
return $ok;
}
Die folgenden 2 Methoden werden aufgerufen, wenn sich ein User korrekt eingeloggt hat und wenn Sessions bzw Cookies aktiviert sind, sie speichern die Login Daten in einem Cookie (beide sptring anhand eines Trennzeichens zusammegesetzt) bzw in der Session:
PHP-Code:<?php/*
* login: cookie
*/
function setup_cookies($user, $pass) {
// set one cookie with string (made of pass+user):
$str = trim($user).$this->_cookieSep.md5(trim($pass));
setcookie($this->_nameCookie.'user_pw', $str, time() + $this->_cookieTimeout, $this->_cookiePath);
}
/*
* login: session
*/
function setup_sessions($user, $pass) {
$_SESSION['user'] = $user;
$_SESSION['pass'] = md5($pass);
}
PHP-Code:<?php/*
* does the logout:
*/
function doLogout($path = "no") {
if($path == "no")
$path = basename($_SERVER["PHP_SELF"]);
if($this->_useCookies == 1)
$this->destroy_cookies();
if($this->_useSessions == 1)
$this->destroy_sessions();
if(!@$_GET["loggedout"])
echo 'Erfolgreich ausgeloggt! Sie werden in 5 Sekunden weitergeleitet!
<meta http-equiv="refresh" content="5;url='.$path.'?loggedout=yes">';
exit();
}
PHP-Code:<?php
/*
* logout: cookie
*/
function destroy_cookies() {
setcookie($this->_nameCookie.'user_pw', '', 0, $this->_cookiePath);
// only if using sessions:
if($this->_useSessions == 1)
setcookie('PHPSESSID', '', 0, '/');
}
/*
* logout: session
*/
function destroy_sessions() {
foreach($_SESSION as $var) {
$var = "";
}
$_SESSION = array();
unset($_SESSION);
session_destroy();
}
PHP-Code:<?php
/*
* always check admin before
* do some admin action:
*/
function secureAdmin() {
if( $this->_isAdmin == 1 )
return true;
else
die("
Sie sind kein Admin! Dieser Bereich ist nur für Admins![url='?kill=yes']Logout![/url]");
}
PHP-Code:<?php
/*
* if u are admin, you may insert new user
*/
function _adminInsertUser($user, $pass, $is_admin) {
// some secure:
$this->secureAdmin();
// does user with this name already exist?
$sql = "SELECT user, pass FROM ".$this->_tp."users WHERE user = '".addslashes(trim($user))."'";
$res = $this->db_query($sql);
if( mysql_num_rows($res) > 0 ) {
echo "User konnte nicht erstellt werden! Grund: Username existiert bereits!
";
return false;
}
else {
// does not exist, so insert new one:
$sql = "INSERT INTO ".$this->_tp."users (user, pass, is_admin) VALUES ('".addslashes(trim($user))."', '".md5(trim($pass))."', '".$is_admin."')";
$res = $this->db_query($sql);
return true;
}
}
/*
* if u are admin, you may insert new user
*/
function _adminDeleteUser($userid) {
// some secure:
$this->secureAdmin();
$sql = "DELETE FROM ".$this->_tp."users WHERE id = '".$userid."'";
$res = $this->db_query($sql);
}
/*
* if u are admin, you may list users!:
*/
function _adminListUser() {
// some secure:
$this->secureAdmin();
$sql = "SELECT id,
user as username,
pass as pw,
is_admin as admin
FROM ".$this->_tp."users
ORDER BY username ASC";
$res = $this->db_query($sql);
$foo = array();
while($row = mysql_fetch_assoc($res) ) {
$foo[] = $row;
}
return $foo;
}
-
-
Mir ist bei diesem Tuturial folgendes aufgefallen:
PHP-Code:$_SESSION = array();
unset($_SESSION);
http://de2.php.net/manual/de/function.session-unset.php (siehe "Achtung")
www.webdeveloperfactory.de - Der Blog und Ratgeber für Webentwickler mit zahlreichen Informationen
Kommentar
-
Zitat von phpdummiMail: firstlor at yahoo punkt de
und ich muss sagen es wäre total simpel ne normale email adresse mit dem ding auszulesen.
wobei das schon mal einen geringen schutz bietet.
Natürlich kann man da auch noch was machen wie at in @ umwandeln und punkt in . aber mit leerzeichen drinne so wie das da steht hilfts wirklich
:wink:
Kommentar
-
Eine Frage hab ich noch zum Konstruktor:
PHP-Code:// MySQL: is there a connection?
if(!($this->_dbConnected))
$this->db_connect();
// MySQL: is db selected?
if(!($this->_dbSelectedDb))
$this->db_select_db();
Da ja beim erzeugen des Objekts die vars _dbConnectet und selected ja immer false sind!
Kommentar
Kommentar