• Hallo liebe Userinnen und User,

    nach bereits längeren Planungen und Vorbereitungen sind wir nun von vBulletin auf Xenforo umgestiegen. Die Umstellung musste leider aufgrund der Serverprobleme der letzten Tage notgedrungen vorverlegt werden. Das neue Forum ist soweit voll funktionsfähig, allerdings sind noch nicht alle der gewohnten Funktionen vorhanden. Nach Möglichkeit werden wir sie in den nächsten Wochen nachrüsten. Dafür sollte es nun einige der Probleme lösen, die wir in den letzten Tagen, Wochen und Monaten hatten. Auch der Server ist nun potenter als bei unserem alten Hoster, wodurch wir nun langfristig den Tank mit Bytes vollgetankt haben.

    Anfangs mag die neue Boardsoftware etwas ungewohnt sein, aber man findet sich recht schnell ein. Wir wissen, dass ihr alle Gewohnheitstiere seid, aber gebt dem neuen Board eine Chance.
    Sollte etwas der neuen oder auch gewohnten Funktionen unklar sein, könnt ihr den "Wo issn da der Button zu"-Thread im Feedback nutzen. Bugs meldet ihr bitte im Bugtracker, es wird sicher welche geben die uns noch nicht aufgefallen sind. Ich werde das dann versuchen, halbwegs im Startbeitrag übersichtlich zu halten, was an Arbeit noch aussteht.

    Neu ist, dass die Boardsoftware deutlich besser für Mobiltelefone und diverse Endgeräte geeignet ist und nun auch im mobilen Style alle Funktionen verfügbar sind. Am Desktop findet ihr oben rechts sowohl den Umschalter zwischen hellem und dunklem Style. Am Handy ist der Hell-/Dunkelschalter am Ende der Seite. Damit sollte zukünftig jeder sein Board so konfigurieren können, wie es ihm am liebsten ist.


    Die restlichen Funktionen sollten eigentlich soweit wie gewohnt funktionieren. Einfach mal ein wenig damit spielen oder bei Unklarheiten im Thread nachfragen. Viel Spaß im ngb 2.0.

[PHP] Ist dieses Loginscript gut und sicher?

Cyperfriend

Der ohne Avatar

Registriert
14 Juli 2013
Beiträge
1.123
  • Thread Starter Thread Starter
  • #41
Naja, ich habe mir schon was dabei gedacht als ich das so eingebaut habe.
Wird die Session geklaut gibt es immer noch den IP-Check und der Timeout ist halt, wenn sich ein User vergisst abzumelden. Was ist an diesem Gedankengang falsch, bzw. wie geht es richtig. Momentan macht sich bei mir leichter Frust breit, weil ich das Gefühl habe alles falsch zu machen.
 

Kugelfisch

Nerd

Registriert
12 Juli 2013
Beiträge
2.342
Ort
Im Ozean
Du machst durchaus nicht alles falsch - bedenke, dass einige Benutzer unterschiedliche Ansichten haben und auch widersprüchliche Tipps geben, z.B. bzgl. des zusätzlichen Speicherns von Session-Daten in der DB (worin ich ebenfalls wenig Sinn sehe). Dein Skript stellt meines Erachtens durchaus eine geeignete Basis dar, den Tipps von Shodan kann ich mich jedoch anschliessen - abgesehen von den Klartext-Passwörtern, welche ich in deinem Skript nicht erkennen kann, wobei man die Eignung von MD5-Hashes ohne Salt zum Speichern von Passwörtern durchaus bezweifeln kann (besser wäre z.B. bcrypt, zumindest aber ein statisches Salt ausreichender Länge). Ausserdem gebe ich (aufgrund der .html-Dateiendung) zu bedenken, dass natürlich die Zielseite der Weiterleitung, http://example.com/neu/index.html, die Gültigkeit der Session berprüfen muss (wenn du mehrere Interne Seiten hast, lagere die Überprüfung in ein Skript aus, welches du dann auf jeder Seite am Anfang(!) inkludierst).

Dass die Session ein Timeout hat und an die IP-Adresse gebunden ist, ist an sich durchaus sinnvoll. Allerdings reicht es dazu, die notwendigen Daten in $_SESSION zu speichern, eine zusätzliche Speicherung in der Datenbank ist nicht erforderlich. Weshalb du die Werte vor der Speicherung in $_SESSION mit htmlspecialchars() maskierst, erschliesst sich mir ebenfalls nicht - schliesslich willst du sie ja (noch) nicht im (X)HTML-Kontext ausgeben.
 

Shodan

runs on biochips

Registriert
14 Juli 2013
Beiträge
661
Ort
Citadel Station
@Cyberfriend:
"Momentan macht sich bei mir leichter Frust breit, weil ich das Gefühl habe alles falsch zu machen. "
Das war nicht meine Absicht. Verdammt was tu ich nun.. mmh.. Shodan.niceness++;

"Was ist an diesem Gedankengang falsch?"
Nichts! Es ging mir nur um das Escapen und Verdoppeln der Session Daten in einer Datenbank.

Den timeout finde ich sogar eine richtig gute Idee. Siehe Stackoverflow. Bei Anwendungen mit sehr hoher Aktivität ist das nicht unbedingt notwendig, da feuert der Garbage Collector des PHP Session Managements häufig genug. Bei Anwendungen mit geringer Aktivität ist ein eigener timeout aber äußerst sinnvoll, der GC könnte schließlich über Tage hinweg nicht aufgerufen werden.
Ob man jetzt für kleine Anwendungen besser die GC Wahrscheinlichkeitswerte ändert oder ob man eigene timeouts generell als best practice sehen sollte :rolleyes: puh keine Ahnung.
Aber deine Lösung passt und macht in dem Kontext Sinn, also "falsch" ist sie auf keinen Fall ;)


@Kugelfisch: Klartext bei Übertragung. Thema hashen mit JS.




@Krutius:
PHP:
virtualPW = HASH(PW + salt1)
publicHash2 = HASH(virtualPW + salt3) //idee: public hash 2 geht niemals in die DB
// Jetzt an server senden: publicHash2, ...
1. Wie oben erwähnt sollte auch publicHash1 und virtualPW nie in der DB landen.
2. Warum willst du zweimal salzen bevor du den Hash an den Server sendest?

PHP:
challengeVerification == HASH(publicHash1 + challenge)
Ich glaube das funktioniert so nicht. Der Server kennt publicHash1 nicht, soll ja nicht gesendet werden.
Wenn er publicHash1 oder virtualPW bei der Registrierung in der Datenbank gespeichert hat, dann ist der Nutzen des nutzerspezifischen Salts salt4 doch nicht mehr vorhanden, denn in der DB steht dann ja das mit öffentlichen, konstanten und für alle User identische Salts gehashte Passwort.

Generell gibt es hier ein Problem. Entweder verwenden wir nutzerspezifischen Salts um die Passwörter in der Datenbank zu sichern, dann haben Client und Server kein shared secret mehr, oder wir benutzen ein Challenge-Response Verfahren, das ein solches verlangt.
Beides geht nicht. Naja wir könnten dem Client das Salt liefern, aber das wollen wir nicht.
Wir könnten als Challenge vom User verlangen das Passwort mit dem öffentlichen Key des Servers zu verschlüsseln. Und wenn wir dann noch versuchen das gegen man in the middle Attacken zu schützen sind wir bei SSL/TLS ;)

argh.. die Zeit.. muss weg.. mehr zum Thema Salz vielleicht später
 
Zuletzt bearbeitet:

Cyperfriend

Der ohne Avatar

Registriert
14 Juli 2013
Beiträge
1.123
  • Thread Starter Thread Starter
  • #44
Ja, ich habe das Script , bzw. das Projekt leicht umgebaut, bzw. will es anders organisieren. Daher index.html
War aber vielleicht zu kurz gedacht. Ich bekomme zwar PHP-Code in der HTML-Datei ausgeführt, aber ob das am Ende schön ist ...

Andere Sache:
Ausserdem gebe ich (aufgrund der .html-Dateiendung) zu bedenken, dass natürlich die Zielseite der Weiterleitung, http://example.com/neu/index.html, die Gültigkeit der Session berprüfen muss (wenn du mehrere Interne Seiten hast, lagere die Überprüfung in ein Skript aus, welches du dann auf jeder Seite am Anfang(!) inkludierst).
Wie soll das funktionieren? Die Überprüfung sieht ja in etwa so aus (Pseudocode)
HTML:
<php if(Session['gültig'] == true) { ?>
HTML-Code der internen Seite und so
<?php }
else
{
echo "Nicht eingeloggt
}?>
Wie soll ich das in eine externe Datei auslagern? Schon aufgrund der Klammerbildung geht das ja nicht?

Allerdings reicht es dazu, die notwendigen Daten in $_SESSION zu speichern, eine zusätzliche Speicherung in der Datenbank ist nicht erforderlich. Weshalb du die Werte vor der Speicherung in $_SESSION mit htmlspecialchars() maskierst, erschliesst sich mir ebenfalls nicht - schliesslich willst du sie ja (noch) nicht im (X)HTML-Kontext ausgeben.
Aber wenn ich das nicht in die Datenbank speichere, dann könnte das ja manipuliert werden, weil auf Userbasis. So zumindest verstehe ich die Aussage von Lex. Die Session wird zwar nicht ausgegeben, aber mit ihr wird intern gearbeitet. Daher das htmlspecialchars, damit da niemand Code einschmuggelt. Dazu ist das doch, neben "mysql_real_escape_string" gedacht?

Aber danke, dass ich auch mal gesagt bekomme, dass nicht alles Mist ist was ich mache. Ob mein Script aber SQL-Injektion und XMMS sicher ist weis ich immer noch nicht.
 

Kugelfisch

Nerd

Registriert
12 Juli 2013
Beiträge
2.342
Ort
Im Ozean
Wie soll das funktionieren? Die Überprüfung sieht ja in etwa so aus (Pseudocode)
[...]
Wie soll ich das in eine externe Datei auslagern? Schon aufgrund der Klammerbildung geht das ja nicht?
Du kannst exit() oder die() verwenden, um die Ausführung des Skripts abzubrechen, sofern die Session ungültig ist. Das ist auch aus einer inkludierten Datei heraus möglich. Zum Beispiel (Pseudocode):
PHP:
<?php
if(!session_is_valid()) {
    header('Location: http://example.com/loginform.php');
    print('Session ungültig, bitte unter http://example.com/loginform.php einloggen...');
    die();
}
?>

Aber wenn ich das nicht in die Datenbank speichere, dann könnte das ja manipuliert werden, weil auf Userbasis. So zumindest verstehe ich die Aussage von Lex.
Nein, die Session (d.h. der Inhalt von $_SESSION) wird serverseitig gespeichert, standardmässig im Dateisystem. Der Benutzer erfährt lediglich die Session-ID und die Daten aus $_SESSION, welche du explizit ausgibst. Manipulieren kann er die Inhalte von $_SESSION clientseitig nicht. Insofern kann ich das Argument nicht nachvollziehen und schliesse mich Shodan an.

Die Session wird zwar nicht ausgegeben, aber mit ihr wird intern gearbeitet. Daher das htmlspecialchars, damit da niemand Code einschmuggelt. Dazu ist das doch, neben "mysql_real_escape_string" gedacht?
Die Frage ist, in welchem Kontext. Du kannst Daten nicht `generell` maskieren und annehmen, die Maskierung würde in jedem Kontext wirken. htmlspecialchars() maskiert Daten zur Ausgabe im (X)HTML-Kontext. Zum Ablegen in $_SESSION musst du die Daten nicht maskieren - wohl aber, sobald du sie von dort ausliest nutzt (z.B. in einem SQL-Query - dann mittels mysql_real_escape_string() - oder zur Ausgabe in einem (X)HTML-Dokument - mittels htmlspecialchars()).
 

Exterminans

Neu angemeldet

Registriert
14 Juli 2013
Beiträge
147
Generell gibt es hier ein Problem. Entweder verwenden wir nutzerspezifischen Salts um die Passwörter in der Datenbank zu sichern, dann haben Client und Server kein shared secret mehr, oder wir benutzen ein Challenge-Response Verfahren, das ein solches verlangt.
Beides geht nicht. Naja wir könnten dem Client das Salt liefern, aber das wollen wir nicht.
Wir könnten als Challenge vom User verlangen das Passwort mit dem öffentlichen Key des Servers zu verschlüsseln. Und wenn wir dann noch versuchen das gegen man in the middle Attacken zu schützen sind wir bei SSL/TLS ;)

argh.. die Zeit.. muss weg.. mehr zum Thema Salz vielleicht später

Die Idee ist es, mittels salt1 in Form des VirtualPW ein gemeinsames Geheimnis zu erzeugen, welches allerdings NUR bei der Registrierung einmalig übertragen werden muss und anschließend in Form von publicHash1 gespeichert wird.

Warum das doppelte Hashen? Zunächst einmal, damit das persönliche Passwort des Benutzers NIEMALS, auch nicht bei der Registrierung übertragen werden muss, das echte Passwort existiert ausschließlich im Browser.
Der zweite Hash (von virtualPW nach PublicHash1) erfolgt damit das virtualPW nicht in der Datenbank steht, denn das virtualPW hat die gleiche Funktion wie das ursprüngliche PW, kennst du es, hast zu vollen Zugang.



Übrigens: Das Verfahren so wie Krutius es da abgeschrieben hat IST gegen Man-In-The-Middle-Attacken abgesichert, das ist durch die Challenge-Auth gewährleistet. Lediglich die Session kann gespooft werden, aber die Authentifizierung an sich ist komplett sicher.
Will man sich nochmals absichern, lässt man natürlich die komplette Sitzung in einem SSL-Tunnel ablaufen, allerdings auch da wieder Vorsicht.

Will man PRISM und Co. ne Chance geben, dann macht man es so wie Google und Facebook. Es sind zwar SSL-Verbindungen, allerdings gibt es da zwei mögliche Methoden die Verbindung auf zu bauen, eine ist sicher, die andere nicht.
  • Bei der von Google, Facebook und Microsoft verwendeten Methode schickt der Server dem Client seinen Public Key, der Client wählt alleine(!) einen Schlüssel für die eigentliche Verbindung und schickt diesen verschlüsselt an den Server. Theoretisch kann jetzt nur der Server den eigentlichen Schlüssel lesen da er der einzige ist der den Private Key haben sollte, in der Praxis kann aber JEDER der den Private Key kennt den Schlüssel aus einem Mitschnitt des Handshakes extrahieren und damit den eigentlichen SSL-Tunnel nachträglich komplett entschlüsseln.
  • Die andere Möglichkeit ist es, dass Server und Client gemeinsam mit einer Form des Diffie-Hellman-Verfahrens gemeinsam(!) einen Schlüssel aushandeln, wobei Diffie-Hellman aufgrund seiner Funktionsweise dabei garantiert, dass man, selbst wenn man die komplette Kommunikation mitschneidet UND den privaten Teil SSL-Zeritifkat des Servers kennt, es allerdings trotzdem immer noch UNMÖGLICH ist heraus zu finden auf welchen Schlüssel sich die Teilnehmer geeinigt haben, wers genau wissen will, der soll bei Wikipedia nach Diffie-Hellman suchen. Das ist übrigens auch die Standardkonfiguration für den SSL-Handshake und wer auch nur den leisesten Wert auf Vertraulichkeit legt, verbietet die erste Methode komplett. Der Client ist damit nur noch gegen Man-In-The-Middle-Attacken mit gestohlenen Zertifikaten anfällig, dagegen existiert aber defakto kein Schutz.
Google und Co. verwenden übrigens IMMER NOCH die erste Methode. Eine SSL-Verbindungen mit zu schneiden die nach dem zweiten Prinzip aufgebaut wurde ist sinnlos, es ist niemals möglich diese nachträglich noch zu entschlüsseln. Verbindungen nach der ersten Methode können wahlweise sofort (wenn man den private Key bereits kennt) oder auch erst später (wenn man den private Key noch(!) nicht kennt) entschlüsselt werden. Konkret bedeutet das sogar, dass jemand der 2 Jahre später deinen Server hackt und sich eine Kopie des alten Zertifikats besorgt, rückwirkend ALLE Verbindungen entschlüsseln kann die er mitgeschnitten hat.

(Anmerkung: Diffie-Hellman an sich, in der ursprünglichen Implementierung welche auf dem diskreten Logarithmus basiert, ist mit Quantencomputern prinzipiell unsicher. Daher kommt beim SSL-Handschake auch nur ein an Diffie-Hellman angelehntes Verfahren zum Einsatz welches die gleichen Eigenschaften hat, aber nicht auf Potenzen sondern auf eliptischen Kurven basiert.)
 
Zuletzt bearbeitet:

Krutius

Verrückter

Registriert
14 Juli 2013
Beiträge
115
@Exterminans

Danke, genau so ists.

Der secureDbHash kommt noch dazu, um sicherzustellen, das bei einem Lesezugriff auf die entsprechenden Teile der DB auch nicht alle Informationen anfallen um ein neues Login durchzuführen.

Und, natürlich sind alles Salts Userspezifisch, und der Client darf die abfragen (also, bis auf salt4). Will man verhindern, das der Client so verifzieren kann, ob ein User existiert, kann man einfach dummy-salts ohne echter Entropie errechnen, meistens gibt es aber ja sowieso andere Möglichkeiten die Existenz eines Users zu verifizieren.

@Shodan
Wieso willst du denn die userspezifischen Salts nicht auf Anfrage rausrücken? Da spricht ja eig. nichts dagegen!
 

Cyperfriend

Der ohne Avatar

Registriert
14 Juli 2013
Beiträge
1.123
  • Thread Starter Thread Starter
  • #48
Ich habe das Script erneut leicht umgeschrieben.
Vielleicht kurz zum Salt: Ob das so jetzt clever ist weis ich nicht, aber die Überlegung beruht ja darauf, dass jemand an die DB kommt, bzw. diese ausließt. Wenn dazu noch jemand an das PHP-Script kommt ist eh alles verloren, egal wie der Salt lautet. Das "md5("56.658gt" ist im Echtscript natürlich was total kryptisches mit Großbuchstaben und Sonderzeichen.
Ich hoffe das Script ist so jetzt OK und bietet keine Angriffsmöglichkeit für SQL-Injektion bzw. XSS.
PHP:
<?php
# Diese Datei enthält Funktionen, welche für das Script zwingend benötigt werden.
require_once("functions.inc.php");
# Diese Datei enthält die Fehlercodes.
include("codes.php");

# Benutzername und Passwort in einen String umwandeln.
$_POST['benutzername'] = (string)$_POST['benutzername'];
$_POST['passwort'] = (string)$_POST['passwort'];

# Wenn sowohl Benutzername als auch Passwort im Login-Formular eingetragen worden sind.
if(!empty($_POST['benutzername'])  && !empty($_POST['passwort'])) {
	#Session starten.
	session_start();
	# Datenbankverbindung herstellen
	$db_connect = db_connect();
	# Nach Benutzernamen und Passwort in der Datenbank suchen.
	# Die ID brauchen wir später für die Session, daher wird sie ebenfalls ausgelesen.
	$db_read = "select id, benutzername, passwort from db_benutzer 
				where (benutzername='".mysql_real_escape_string($_POST['benutzername'])."') && (passwort='".hash('sha512', ($_POST['passwort']).md5("56.658gt"))."')";
	$db_result = mysql_query($db_read, $db_connect) or die (mysql_error());

	# Wenn eine Übereinstimmung gefunden wurde SESSION setzen und in den internen Bereich weiterleiten
	if(mysql_num_rows($db_result) == 1){
		# ID zum Benutzer aus der Datenbank auslesen.
		$res = mysql_fetch_array($db_result);
		# Benutzer-ID des eingeloggten Benutzers um Aktionen zuzuordnen
		$_SESSION['id'] == $res['id'];
		# Session-ID
		$_SESSION['sid'] = session_id();
		# IP des Benutzers zum Abgleich
		$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
		# Zeit festlegen, wann die Session ablaufen soll, wenn Inaktivität (900 Sekunden == 15 Minuten)
		$_SESSION['term'] = time() + 900;
		# Fehlercode (0 == Leer)
		$_SESSION['code'] = 0;
		header ("Location:  http://".$_SERVER['HTTP_HOST']."/neu/index.html");
	}
	else{
		# Wenn der Login falsch war
		# -> Umleitung zum Login
		# Fehlercode 1 == Fehler beim Login)
		$_SESSION['code'] = 1;
		header ("Location: http://".$_SERVER['HTTP_HOST']."/neu/index.html");
	}
}
else{
	# Wenn das Loginscript direkt aufgerufen wird oder wenn die Felder Benutzername und Passwort leer abgeschickt werden
	# -> Umleitung zum Login
	header ("Location: http://".$_SERVER['HTTP_HOST']."/neu/index.html");
}
?>
 
Zuletzt bearbeitet:

Krutius

Verrückter

Registriert
14 Juli 2013
Beiträge
115
@Cyperfriend
Nein, das ist so nicht richtig! Ein salt schützt durchaus auch wenn der Angreifer DB + Scripte in die Finger kriegt. Hier auch noch mal der Link zu einem guten Text dazu:
http://crackstation.net/hashing-security.htm

Um das kurz zu erklären:

Der salt schützt vor einem Angriff mit Rainbow Tables. Ein Hash ist ein Einweg-Operation. Die Idee dabei ist, das man aus einem Text einen Hash errechnen kann, aber niemals umgekehrt. Das soll verhindern, dass jemand der die DB hat an das Klartext Passwort kommt, welches u.U. auch wo anders benutzt wird.

Nehmen wir an, der Benutzer nutzt das Passwort 'password'. Den daraus errechneten SHA256 hash '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8' speicherst du dann in der DB.

Der Angreifer hat nun das '5e884...', kann jedoch daraus nicht das 'password' errechnen. Er kann jedoch einen Brute Force Angriff machen, und z.B. anhand eines Passwort-Wörterbuchs (Also ein Liste mit sehr vielen möglichen passwörten) einfach für alles in der Liste den Hash errechnen. Steht in dieser Liste nun 'password' und er errechnet daraus '5e884...', und vergleich das mit dem '5e884...' in der DB, stellt er fest, dass das identisch ist, und er kennt das Klartextpasswort.

Nun kostet er Rechenzeit so einen Hash zu errechnen. Will ich also nun z.B. alle möglichen 8-stelligen Passwörter errechnen, wäre das ein unrealistischer Aufwand! Der trick hierzu ist nun die Rainbow table. Stelle dir eine Tabelle vor, mit sämtlichen 8-stelligen Zeichenketten, und den dazugehörigen hashes. Diese Tabellen sind teilweise mehrere TB gross, aber wenn man sie mal hat, muss man nurnoch den Hash aus der liste nehmen, und schon hat man das Passwort dazu.

Hier kommt nun der Salt ins Spiel. Hier kommen z.B. so den 8 Stellen vom Passwort noch die 100 vom Salt dazu.
Also, quasi statt das du den hash von 'password' errechnest, errechnest du den von 'password012345678901234567890123456789012345678901234567890'.
Eine so grosse Rainbow-Table ist weder machbar, noch könnte man sie irgendwo speichern, da sie schlicht zu gross wäre!

Hast du jedoch nur einen Salt für alle User, wäre es möglich eine Rainbow-table genau für diesen Salt zu errechnen. Dass ich also statt den hash für 'a' den für 'aMEINSALT' errechne. den hash nur für 'a' müsste ich ja nicht errechnen. Das reduziert wieder die Grösse der Rainbow-Table und macht ein errechnen der Rainbow-Table möglich. Mit dieser Rainbow-Table wäre es nun möglich, an die Klartest-Passwörter sämtlicher Benutzer von dir zu kommen!

Deswegen: Generiere für jeden User einen eigenen Salt (sollte lang sein) und speichere den in der DB.

Von der Idee her, musst du dann wenn ein User sich einloggen will, erstmal den salt aus der DB holen, und machst dann ein hash('sha512', salt + password) und vergleichst das Ergebnis mit dem Passwort in der DB. Das MD5 würde ich übrigens weglassen, wenn du auf SHA512 setzt. Bringt dann auch keine zusätzliche Sicherheit.


Um der Wahrheit genüge zu tun:
Kryptografie und Sicherheit sind keine einfachen Themen. Dazu kommt, dass es oftmals falsch gemacht wird. Ich habe auch schon eine Software von einem IT Professor gesehen, die alle Passwörter im Klartext speichert, und sogar noch eine ungeschützte API anbietet, auf der man diese manipulieren könnte. Und nein, dies war kein Studienprojekt sondern eine Produktiv im Einsatz befindliche Webseite, bei der es um Kreditkartendaten ging!


Was du aber immer beachten musst, das schlimmste was passieren kann, ist nicht das jemand in deine Applikation kommt, sondern das ein User die gleiche Username-Passwort kombination für PayPal o.ä. nutzt, und der Angreifer über dich an diese Daten kommt! Von daher befreit dich auch die Tatsache das deine Applikation ja unkritisch ist, nicht vor einem vertrauensvollen Umgang mit Passwörtern.
 

Cyperfriend

Der ohne Avatar

Registriert
14 Juli 2013
Beiträge
1.123
  • Thread Starter Thread Starter
  • #50
OK, danke. Ich werde das anpassen. Zuvor aber trotzdem eine Frage dazu:

Wenn man für jeden Benutzer den Salt in der DB speichert, dann würde es aber trotzdem ausreichen diesen einen Salt für diesen einen Benutzer (vorwiegend ein Admin) zu nehmen und der Angreifer kann das Passwort errechnen, da er wieder einen statischen Salt hat.
Zugegeben: Die restlichen Benutzer wären sicher, aber meine Webanwendung trotzdem nicht.

Ist der Rest wenigstens OK oder habe ich weitere Baustellen in dem Script?
Irgendwie habe ich auch den Eindruck jeder drückt sich um die Frage nach der SQL Injektion und dem XSS.
 

Shodan

runs on biochips

Registriert
14 Juli 2013
Beiträge
661
Ort
Citadel Station
@Cyperfriend:

$_SESSION['sid'] = session_id(); brauchst du nicht, mehr noch, ich würde sogar davon abraten hier eine Redundanz zu schaffen. Wenn du die aktuelle ID für irgend etwas brauchst, dann hol sie dir mit session_id(). Auch brauchst du zu einem späteren Zeitpunkt nicht zu überprüfen, ob die noch identisch sind, denn die Werte in $_SESSION sind ja von der ID abhängig. Das heißt entweder sie sind identisch, oder du hast ein session_regenerate_id() ausgeführt und $_SESSION['sid'] nicht überschreiben.

Sinnvoll kann das sein, wenn man zum Beispiel folgendes haben will: User loggt sich in Sicherheitsbereich 1 ein, bekommt sid-1. User loggt sich in Sicherheitsbereich 2 ein, bekommt eine sid-2. Da macht er etwas, loggt sich aus Sec2 aus und soll nahtlos wieder in Sec1 sein. Dann musst du zwischenzeitlich sid-1 natürlich speichern, damit sie dem User, der eine Weile mit sid-2 unterwegs war, wieder zugewiesen werden kann. Über soetwas braucht man aber erst nachdenken, wenn man mindestens zwei Sicherheitsbereiche und ein Dutzend Sessionvariablen hat ;-)


Wie schon erwähnt, würde ich ein session_regenerate_id(); bei Login empfehlen.

Lex ging ein Stück weiter und empfahl das bei jedem Aufruf zu tun, was ich persönlich für weniger sinnvoll halte:
1: Wenn irgendjemand in der Lage ist effizient genug an die sid des Users zu kommen bevor der timeout greift, dann ist er sehr wahrscheinlich auch in der Lage ein "Rennen" um den nächsten Seitenaufruf zu gewinnen.
2: Benutzt man nur session_regenerate_id() statt session_regenerate_id(TRUE) erzeugt man große Mengen nie wieder verwendeter Sessions, die der Garbage Collector dann wegräumen muss. Dafür haut einem letzteres bei vielen Anfragen eine Delle in die Performance.
3: Sendet ein User eine weitere Anfragen bevor die erste beantwortet wurde sendet er bei beiden die gleiche sid.
- Bei session_regenerate_id(FALSE) generiert der Server zwei neue Sessions. Der User verwendet dann die, die er zuletzt empfangen hat. Alle Änderungen an den Sessionvariablen der anderen gehen verloren.
- Bei session_regenerate_id(TRUE) beschwert sich der Server bei allen Anfragen nach der ersten, dass die sid nicht gültig ist.
Wirklich lustig wird das erst dann, wenn es nicht abhängig von der Klickgeschwindigkeit des Users ist, sondern wenn man ein komplexeres Projekt hat, in dem mehrere Anfragen automatisch generiert werden. Da häufen sich dann die Beschwerden der User, dass der Server Änderungen nicht übernimmt / einen dauernd ausloggt.



Wenn man für jeden Benutzer den Salt in der DB speichert, dann würde es aber trotzdem ausreichen diesen einen Salt für diesen einen Benutzer (vorwiegend ein Admin) zu nehmen und der Angreifer kann das Passwort errechnen, da er wieder einen statischen Salt hat.
Zugegeben: Die restlichen Benutzer wären sicher, aber meine Webanwendung trotzdem nicht.
Naja er kann keine allgemeine Table gegen den Algorithmus verwenden. Eine Table für einen User (Admin) und dessen Salt zu erstellen ist identisch damit das Password zu bruteforcen. Wenn gerade der Admin jetzt nicht passw0rt als Passwort hat, ist das nicht effizient möglich.
 
Zuletzt bearbeitet:

Cyperfriend

Der ohne Avatar

Registriert
14 Juli 2013
Beiträge
1.123
  • Thread Starter Thread Starter
  • #52
Ja aber wie soll ich noch den Benutzer zuordnen können, wenn ich
PHP:
$_SESSION['id'] == $res['id'];
rauswerfe? Das $_SESSION['id'] ist ja die Benutzer-ID, nicht die Session-ID (sid).
Ich mache ja später Datenbankeinträge und so. Da muss ich doch nachvollziehen können wer was macht. Außerdem will ich den Benutzer ja überall auf der Seite ausgeben, beispielsweise für "Eingeloggt als: Cyperfriend".

Ich will grad nochmal nachschlagen, warum man nicht auch direkt $_SESSION['benutzername'] nehmen kann.
 

Shodan

runs on biochips

Registriert
14 Juli 2013
Beiträge
661
Ort
Citadel Station
PHP:
...
        # Benutzer-ID des eingeloggten Benutzers um Aktionen zuzuordnen
        $_SESSION['id'] == $res['id'];
        # Session-ID
        $_SESSION['sid'] = session_id();
...

ich meinte das zweite, das erste ist schon richtig so ;-)

Was du aus der DB holst und in der Session lagerst ist eigentlich egal solange es unique ist. Id bietet sich da halt an, ich vermute mal, das ist der Primärschlüssel der Usertabelle.
Den Benutzernamen kannst du noch dazu aus der DB in die Session übernehmen, wenn du zB oben auf jeder Seite "Hallo Username" stehen haben willst, dann musst du nicht dauernd die Datenbank fragen, wie der User mit der ID xy noch mal hieß. .



Die Idee ist es, mittels salt1 in Form des VirtualPW ein gemeinsames Geheimnis zu erzeugen, welches allerdings NUR bei der Registrierung einmalig übertragen werden muss und anschließend in Form von publicHash1 gespeichert wird.
Achso, einmalig übertragen. Ich habe "niemals übertragen" wörtlich genommen ;-)
Ob ich bei der Registrierung einmalig publicHash1 oder virtualPW sende spielt doch keine Rolle, wird die Registrierung abgehört kann man aus virtualPW ja publicHash1 berechnen. Letzteres wäre zwar nie "direkt" übertragen worden, aber eben doch einmal "indirekt". Daher stellt sich mir auch die Frage, warum doppelt gehasht wird. Ich könnte bei der Registrierung doch auch publicHash1 und publicHash2 senden, wird die abgehört ist die Sicherheit doch genauso im Eimer wie bei Versendung von VirtualPW, da ich daraus beide berechnen kann. Wie du schon sagtest "das virtualPW hat die gleiche Funktion wie das ursprüngliche PW", warum also doppelt hashen?

Das Verfahren so wie Krutius es da abgeschrieben hat IST gegen Man-In-The-Middle-Attacken abgesichert, das ist durch die Challenge-Auth gewährleistet. Lediglich die Session kann gespooft werden...
bitte? Ja, das Passwort geht bei einer MITM-Attacke nicht verloren und die challenge sichert gegen Replay Attacken, aber ein Authentifizierungsverfahren als gegen MITM abgesichert zu bezeichnen, wenn am Ende der Angreifer als User authentifiziert wurde, ist doch sehr gewagt.
Alice sendet Mallory ihren Usernamen.
Mallory sendet den an Bob weiter und behauptet Alice zu sein.
Bob sendet Mallory die salts und challenge.
Mallory gibt die an Alice weiter.
Alice sendet Mallory publicHash2 und challengeVerification.
Mallory sendet die an Bob weiter und hat sich als Alice authentifiziert.

Selbst wenn da später noch weitere challenges kommen (für transactions zB), Mallory sitzt noch immer in der Mitte.

Alice sendet Mallory "überweise 5€ an Brot für die Welt"
Mallory sendet Bob "überweise 5€ an Brot für die Welt" und "überweise 10.000€ an Mallory" (erstere kann man ignorieren, wenn Mallory die von Alice erwartete Antwort fälschen kann)
Bob sendet Mallory zwei Antworten mit je einer challenge.
Mallory sendet Allice die erwartete Antwort mit der bösen challenge und bekommt den response für die böse Anfrage.

Zufallsfund beim rumsurfen: Nokia: Yes, we decrypt your HTTPS data, but don’t worry about it
 

Cyperfriend

Der ohne Avatar

Registriert
14 Juli 2013
Beiträge
1.123
  • Thread Starter Thread Starter
  • #54
Ähm als ich gerade das mit dem Salt praktisch umsetzen wollte ist mir was aufgefallen:
Wenn ich in der DB ein Feld "passwort" und ein Feld "salt" habe und jemand bekommt die DB in die Finger und will das Passwort was macht derjenige dann? Er nimmt nur die spalte "passwort" und ignoriert das Feld "salt".

Irgendwie ist es also ziemlich sinnfrei den Salt in DB zu speichern
Wenn es aber auch Unsinn ist den Salt statisch im Script festzulegen und mit dem Passwort zu vermischen, dann ist diese ganze Saltgeschichte recht sinnfrei.
Den Salt im Script zu speichern fände ich da fast sinnvoller, auch wenn er dann statisch ist, aber zumindest liegen Salt und Passwort dann an zwei getrennten Stellen.


Edit: Bin selber drauf gekommen: Einfach Passwort und Salt beim Eintrag vermischen und den Salt zum Entschlüsseln extra speichern. Hat wohl kurz gedauert ;)
 
Zuletzt bearbeitet:

Exterminans

Neu angemeldet

Registriert
14 Juli 2013
Beiträge
147
Nein, so funktioniert das nicht, du hast da was komplett falsch verstanden.

Du speicherst den HASH und den Salt zusammen. Und zwar der Salt der genau für den jeweiligen Hash verwendet wurde. Das Passwort in Reinform speicherst du niemals, und ohne den Salt ist der Hash nicht verwertbar, denn da steckt der Salt drinnen und der lässt sich auch nicht vom Passwort trennen wenn du ihn nicht kennst.
 

Cyperfriend

Der ohne Avatar

Registriert
14 Juli 2013
Beiträge
1.123
  • Thread Starter Thread Starter
  • #56
Japp, danke. Hat dann auch irgendwann bei mir geklingelt. Dauerte nur etwas.

---

Vielleicht liegt es daran, dass es so spät und in meiner Bude so heiß ist, aber ich stehe gerade echt auf dem Schlauch.

Ich habe jetzt das Passwort mit dem Salt vermisch und den Salt in die Datenbank geschrieben, aber wie komme ich da jetzt ran?
Mein Loginscript sieht an der entsprechenden Stelle so aus:
PHP:
$db_read = "select id, benutzername, passwort, salt from db_benutzer 
			where (benutzername='".mysql_real_escape_string($_POST['benutzername'])."') && (passwort='".hash('sha512', ($_POST['passwort']."salt"))."')";
$db_result = mysql_query($db_read, $db_connect) or die (mysql_error());
Das ergibt leider nur Müll. Der Wert der ausgegeben wird stimmt nicht mit dem in der DB überein.
 

jbs

Lernsüchtig

Registriert
20 Juli 2013
Beiträge
14
Ganz einfach :)
Du machst erster ein Select auf den "benutzername" und liest das "passwort" aus.
Anschließend teilst du das "passwort" in Passwort und Salt bzw. Salt und Passwort (je nachdem in welcher Kombination du es in der Datenbank abgespeichert hast).
Nun nimmst du dir den Salt und erstellst mit dem eingegebenen Passwort einen neuen Hashwert.
Diesen Hashwert vergleichst du dann mit dem vorhandenen Passwort aus der Datenbank.

Hab mir grad nochmal deine SQL Abfrage angeschaut und gesehen, dass du den Salt in einer extra Spalte abspeicherst. Das macht das ganze natürlich einfacher. Den Code dafür findest du in den nächsten Zeilen:

PHP:
$db_read = "select id, passwort, salt from db_benutzer  
            where (benutzername='".mysql_real_escape_string($_POST['benutzername'])."') LIMIT 1"; // SQL zusammenbasteln
$db_result = mysql_query($db_read, $db_connect) or die (mysql_error()); // Query ausführen
if (mysql_num_rows($db_result) > 0) { // check ob mindestens ein Eintrag in der Datenbank gefunden wurde
$row = mysql_fetch_assoc($db_result); // Eintrag aus der Datenbank holen
$db_password = $row['passwort']; // Passwort in Variable füllen (optional)
$db_salt = $row['salt']; // Salt in Variable füllen (optional)

if($db_password==hash('sha512', $_POST['passwort'].$db_salt)) { // Prüfen ob der ursprüngliche Passworthash mit dem eingegebenen Passworthash übereinstimmt
// Passwort stimmt und Benutzer wird eingeloggt.
$db_read_usrData = "select * from db_benutzer  
            where (id='".mysql_real_escape_string($row['id'])."') LIMIT 1";  // SQL Abfrage zusammenbasteln
$db_result_usrData = mysql_query($db_read, $db_connect) or die (mysql_error()); // Query ausführen

$usrData = mysql_fetch_assoc($db_result_usrData); // Array das den kompletten Usereintrag enthält
print_r($usrData); // Debug ausgabe
} else {
die('Kombination von Benutzername und Passwort stimmt nicht überein!'); // Passwort stimmt nicht mit dem Passwort in der Datenbank überein
}
} else {
die('Benutzer nicht gefunden!'); // Benutzername ist in der Datenbank nicht vorhanden
}
 
Zuletzt bearbeitet:

Cyperfriend

Der ohne Avatar

Registriert
14 Juli 2013
Beiträge
1.123
  • Thread Starter Thread Starter
  • #58
So, ich habe das Script jetzt erweitert. Jetzt sieht es so aus. Irgendwelche Einwände?
PHP:
<?php
# Diese Datei enthält Funktionen, welche für das Script zwingend benötigt werden.
require_once("functions.inc.php");
# Diese Datei enthält die Fehlercodes.
include("codes.php");

# Benutzername und Passwort in einen String umwandeln.
$_POST['benutzername'] = (string)$_POST['benutzername'];
$_POST['passwort'] = (string)$_POST['passwort'];

# Wenn sowohl Benutzername als auch Passwort im Login-Formular eingetragen worden sind.
if(!empty($_POST['benutzername'])  && !empty($_POST['passwort'])){
	#Session starten.
	session_start();
	# Datenbankverbindung herstellen
	$db_connect = db_connect();
	# Nach Benutzernamen und Passwort in der Datenbank suchen.
	# Die ID brauchen wir später für die Session, daher wird sie ebenfalls ausgelesen.
	$db_read = "select id, benutzername, passwort, salt from db_benutzer 
				where (benutzername='".mysql_real_escape_string($_POST['benutzername'])."')";
	$db_result = mysql_query($db_read, $db_connect) or die (mysql_error());

	# Wenn eine Übereinstimmung gefundenPasswort auslesen.
	if(mysql_num_rows($db_result) == 1){
		$row = mysql_fetch_array($db_result);
		$passwort = $row['passwort'];
		# Passwort abgleichen und bei Übereinstimmung einloggen
		if($passwort == hash('sha512', $_POST['passwort'].$row['salt'])) {
			# Benutzer-ID des eingeloggten Benutzers um Aktionen zuzuordnen
			$_SESSION['id'] = $row['id'];
			# Benutzer-Name zum anzeigen auf der Seite.
			$_SESSION['benutzername'] = $row['benutzername'];
			# Session-ID
			$_SESSION['sid'] = session_id();
			# IP des Benutzers zum Abgleich
			$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
			# Zeit festlegen, wann die Session ablaufen soll, wenn Inaktivität (900 Sekunden == 15 Minuten)
			$_SESSION['term'] = time() + 900;
			# Fehlercode (0 == Leer)
			$_SESSION['code'] = 0;
			header ("Location:  http://".$_SERVER['HTTP_HOST']."/neu/inventory/index.html");
		}
		else{
			# Bei falschem Passwort
			# Fehlercode 2 == Falsches Passwort)
			$_SESSION['code'] = 2;
			header ("Location: http://".$_SERVER['HTTP_HOST']."/neu/index.html");
		}
	}
	else{
		# Bei nicht gefundenen Benutzernamen
		# Fehlercode 1 == Falscher Benutzername)
		$_SESSION['code'] = 1;
		header ("Location: http://".$_SERVER['HTTP_HOST']."/neu/index.html");
	}
}
else{
	# Wenn das Loginscript direkt aufgerufen wird oder wenn die Felder Benutzername und Passwort leer abgeschickt werden
	# -> Umleitung zum Login
	header ("Location: http://".$_SERVER['HTTP_HOST']."/neu/index.html");
}
?>

Ich hatte mir überlegt, ob es sinnvoll ist noch eine Sperre von ~15 Minuten einzubauen, wenn man das Passwort x-mal falsch eingegeben hat. Sinnvoll?

Anbei poste ich noch die codes.php, weil die Fehlermeldung mit dem falschen Passwort einfach nicht ausgegeben wird. Wenn ich einen nicht vorhandenen Benutzernamen eingebe bekomme ich eine Fehlermeldung. In die entsprechende Schleife rutscht das Script auch, aber irgendwie wird die Session nicht mitgenommen. Ich komme nicht drauf warum.
PHP:
<?php session_start();
switch($_SESSION['code']) {
	case 0:
	$msg = "Keine Message";
	break;
	case 1:
	$msg = "Falscher Benutzername.";
	break;
	case 2:
	$msg = "Falsches Passwort.";
	break;
	default:
	$msg = "Sollte niemals vorkommen";
}
?>
 

Shodan

runs on biochips

Registriert
14 Juli 2013
Beiträge
661
Ort
Citadel Station
Einwände? Natürlich ;-)

PHP:
		if($passwort == hash('sha512', $_POST['passwort'].$row['salt'])) {
			....
		}
In diesem Block hätte ich gerne ein session_regenerate_id().


PHP:
			# Session-ID
			$_SESSION['sid'] = session_id();
Dazu habe ich mich ja schon geäußert. Erläutere mir doch bitte mal, wozu du $_SESSION['sid'] benutzen willst.


Ich hatte mir überlegt, ob es sinnvoll ist noch eine Sperre von ~15 Minuten einzubauen, wenn man das Passwort x-mal falsch eingegeben hat. Sinnvoll?
Ja. Den Zähler musst du aber in der DB speichern.


Edit: Cross-topic-quote - FYI
Beachte auch, dass solch eine Sperre leicht für DoS-Angriffe auf ein bestimmtes Benutzerkonto missbraucht werden kann, indem ein Angreifer gezielt Fehlversuche produziert. Meines Erachtens wäre ein CAPTCHA nach einer bestimmten Anzahl von Fehlversuchen oder auch ein komplette Verzicht auf die Sperre (da sie bei zumindest halbwegs starken Passwörtern wenig hilft) die bessere Wahl.
 
Zuletzt bearbeitet:

Cyperfriend

Der ohne Avatar

Registriert
14 Juli 2013
Beiträge
1.123
  • Thread Starter Thread Starter
  • #60
Script nochmal leicht verfeinert. Weiß noch jemand warum der Code 2 nicht ausgegeben wird?
PHP:
<?php
# Diese Datei enthält Funktionen, welche für das Script zwingend benötigt werden.
require_once("functions.inc.php");
# Diese Datei enthält die Fehlercodes.
include("codes.php");

# Benutzername und Passwort in einen String umwandeln.
$_POST['benutzername'] = (string)$_POST['benutzername'];
$_POST['passwort'] = (string)$_POST['passwort'];

# Wenn sowohl Benutzername als auch Passwort im Login-Formular eingetragen worden sind.
if(!empty($_POST['benutzername'])  && !empty($_POST['passwort'])){
	#Session starten.
	session_start();
	# Datenbankverbindung herstellen
	$db_connect = db_connect();
	# Nach Benutzernamen und Passwort in der Datenbank suchen.
	# Die ID brauchen wir später für die Session, daher wird sie ebenfalls ausgelesen.
	$db_read = "select * from db_benutzer 
				where (benutzername='".mysql_real_escape_string($_POST['benutzername'])."')";
	$db_result = mysql_query($db_read, $db_connect) or die (mysql_error());

	# Wenn eine Übereinstimmung gefunden Passwort auslesen.
	if(mysql_num_rows($db_result) == 1){
		$row = mysql_fetch_array($db_result);
		$passwort = $row['passwort'];
		# Passwort abgleichen und bei Übereinstimmung einloggen
		if($passwort == hash('sha512', $_POST['passwort'].$row['salt'])) {
			# Neue Session-ID generieren.
			session_regenerate_id();
			# Benutzer-ID des eingeloggten Benutzers um Aktionen zuzuordnen
			$_SESSION['id'] = $row['id'];
			# Benutzer-Name zum anzeigen auf der Seite.
			$_SESSION['benutzername'] = $row['benutzername'];
			# IP des Benutzers zum Abgleich
			$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
			# Zeit festlegen, wann die Session ablaufen soll, wenn Inaktivität (900 Sekunden == 15 Minuten)
			$_SESSION['term'] = time() + 900;
			# Fehlercode (0 == Leer)
			$_SESSION['code'] = 0;
			header ("Location:  http://".$_SERVER['HTTP_HOST']."/neu/inventory/index.html");
		}
		else{
			# Bei falschem Passwort
			# Fehlercode 2 == Falsches Passwort)
			$_SESSION['code'] = 2;
			header ("Location: http://".$_SERVER['HTTP_HOST']."/neu/index.html");
		}
	}
	else{
		# Bei nicht gefundenen Benutzernamen
		# Fehlercode 1 == Falscher Benutzername)
		$_SESSION['code'] = 1;
		header ("Location: http://".$_SERVER['HTTP_HOST']."/neu/index.html");
	}
}
else{
	# Wenn das Loginscript direkt aufgerufen wird oder wenn die Felder Benutzername und Passwort leer abgeschickt werden
	# -> Umleitung zum Login
	header ("Location: http://".$_SERVER['HTTP_HOST']."/neu/index.html");
}
?>

Ich würde dann langsam dazu über gehen die Gültigkeit der Sitzung zu überprüfen.
Einfach Session['ip'] und Session['term'] überprüfen?
Wie gehe ich mit der session_id(); um, die ich "rausgeworfen" habe?
 
Zuletzt bearbeitet:
Oben