Passwort vergessen Funktion – „Challenge-Response“-Verfahren (Teil 2)

Nun können wir mit der „Passwort vergessen“-Funktion fortfahren. Uns fehlt lediglich noch die challenge.php. Die ist aber schnell geschrieben.

Das Script challenge.php erledigt folgendes:

  • Annehmen der ChallengeID
  • Vergleichen, ob diese vorhanden ist, ob sie noch unbenutzt ist und ob sie noch gültig ist
  • Gibt ein Formular aus indem der Benutzer ein neues Passwort wählen kann
  • Speichert das neue Passwort in die Datenbank und macht die ChallengeID ungültig


Solch ein Script könnte folgendermaßen aussehen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
< ?php
 
if(isset($_GET['UserID']) && isset($_GET['challenge'])) {
 $UserID		= (int)$_GET['UserID'];
 $Challenge 	= $_GET['challenge'];
 $timestamp	= time() - 24 * 3600;
}
 
if(@is_numeric((int)$UserID) && @(int)strlen($Challenge) === 32) {
 
 $challenge_auslesen = mysql_query("
  SELECT
   "._PREFIX_."changepass.`change_userid`,
   "._PREFIX_."changepass.`change_challenge`,
   "._PREFIX_."changepass.`change_aktiv`,
   "._PREFIX_."changepass.`change_timestamp`,
   "._PREFIX_."user.`user_email`
  FROM
   `"._PREFIX_."changepass`
  LEFT JOIN
   `"._PREFIX_."user`
  ON
   ("._PREFIX_."changepass.`change_userid` = "._PREFIX_."user.`user_id`)
  WHERE
   ((`change_userid` = '$UserID')
  AND
   (`change_challenge` = '$Challenge')
  AND
   (`change_timestamp` > '$timestamp'))
  LIMIT
   1") or logg_errors(mysql_errno(), mysql_error(), $_SERVER['PHP_SELF']);
 $change = mysql_fetch_assoc($challenge_auslesen);
 
 if(mysql_num_rows($challenge_auslesen) === 1) {
 
  if(isset($_POST['do_change'])) {
 
   $user_email= escape_string(strip_tags(trim($_POST['user_email'])));
   $user_passwort1= escape_string(strip_tags(trim($_POST['user_passwort1'])));
   $user_passwort2= escape_string(strip_tags(trim($_POST['user_passwort2'])));
 
   if(validate_email($user_email) === FALSE) {
    $error['email'] = "* Die E-Mail Adresse die Sie angegeben haber ist nicht korrekt";
   }
   if(strlen($user_passwort1) &lt; 8) {
    $error['passwort1'] = "* Zu Ihrer eigenen Sicherheit muss das Passwort mindestens 8 Zeichen lang sein";
   } elseif(preg_match("/^[a-z]+$/", $user_passwort1)) {
    $error['passwort1'] = "* Zu Ihrer eigenen Sicherheit sollte das Passwort nicht nur aus Kleinbuchstaben bestehen";
   } elseif(preg_match("/^[A-Z]+$/", $user_passwort1)) {
    $error['passwort1'] = "* Zu Ihrer eigenen Sicherheit Sollte das Passwort nicht nur aus Großbuchstaben bestehen";
   } elseif(preg_match("/^[0-9]+$/", $user_passwort1)) {
    $error['passwort1'] = "* Zu Ihrer eigenen Sicherheit sollte das Passwort nicht nur aus Zahlen bestehen";
   }
   if($user_passwort1 != $user_passwort2) {
    $error['passwort2'] = "* Die beiden Passwörter sind nicht identisch";
   }
 
   if(!empty($error)) {
    echo '
<div class="wrong">';
    if(!empty($error['email'])) { echo $error['email'].'
'; }
    if(!empty($error['passwort1'])) { echo $error['passwort1'].'
';}
    if(!empty($error['passwort2'])) { echo $error['passwort2'].'
';}
    echo '';
 
    echo '
<form method="post">
<dl>
<dt><label for="user_email">E-Mail:</label></dt>
<dd><input id="user_email" name="user_email" type="text" value="{$UserEmail}" /></dd>
</dl>
<dl>
<dt><label for="user_passwort1">Passwort:</label></dt>
<dd><input id="user_passwort1" name="user_passwort1" type="password" /></dd>
</dl>
<dl>
<dt><label for="user_passwort2">Passwort wiederholen:</label></dt>
<dd><input id="user_passwort2" name="user_passwort2" type="password" /></dd>
</dl>
<dl>
<dt>Optionen:</dt>
<dd>
       <input id="do_change" class="button" name="do_change" type="submit" value="Jetzt ändern!" />
       <input id="reset" class="button" name="reset" type="reset" value="Doch nicht..." />
   	 </dd>
</dl>
</form>
 
';
 
  } else {
 
   $user_passwort = pass_protection($user_passwort1);
 
   $user_aktuallisieren = mysql_query("
    UPDATE
     `"._PREFIX_."user`
    SET
     `user_email` = '$user_email',
     `user_passwort` = '$user_passwort'
    WHERE
     `user_id` = '$UserID'") or die(mysql_error());
 
    $challenge_deaktivieren = mysql_query("
     UPDATE
      `changepass`
     SET
      `valid` = 0
     WHERE
      `user_id` = '$UserID'") or die(mysql_error());
 
   if($user_aktuallisieren === TRUE) {
    echo 'Ihr Passwort wurde erfolgreich geändert. Bitte loggen Sie sich erneut ein.';
   } else {
   	echo 'Beim Ändern ist ein Fehler aufgetreten. Bitte versuchen Sie es später nochmal.';
   }
 
  }
 
  } else {
   echo '
<form method="post">
<dl>
<dt><label for="user_email">E-Mail:</label></dt>
<dd><input id="user_email" name="user_email" type="text" value="{$UserEmail}" /></dd>
</dl>
<dl>
<dt><label for="user_passwort1">Passwort:</label></dt>
<dd><input id="user_passwort1" name="user_passwort1" type="password" /></dd>
</dl>
<dl>
<dt><label for="user_passwort2">Passwort wiederholen:</label></dt>
<dd><input id="user_passwort2" name="user_passwort2" type="password" /></dd>
</dl>
<dl>
<dt>Optionen:</dt>
<dd>
      <input id="do_change" class="button" name="do_change" type="submit" value="Jetzt ändern!" />
      <input id="reset" class="button" name="reset" type="reset" value="Doch nicht..." />
	 </dd>
</dl>
</form>
 
';
  }
 } else {
  // CHALLENGE NICHT VORHANDEN
 }
}
?>

Auch hier sollten Sie beachten, dass dieses Script nur als Beispiel dienen soll und somit nicht alles ausführlich behandelt!

Somit sind wir auch schon am Ende unserer Funktion angelangt. Wie Sie sehen ist eine sichere „Passwort vergessen“-Funktion gar nicht so kompliziert. Es erfordert lediglich ein bisschen Zeit.

Ergänzungen und Kritik sind bei mir immer willkommen, denn nur dadurch lassen sich die Artikel optimal optimieren. Also schreck nicht zurück und schreib mir was Du zum Artikel denkst und was Du besser finden würdest. Also ran an das Kommentarformular. 😉

ALL-INKL.COM

19 Kommentars zu dem Artikel “Passwort vergessen Funktion – „Challenge-Response“-Verfahren (Teil 2)”

  1. Avatar von Frida_66
    Frida_66

    Hey, wirklich ne schöne Anleitung, die du hier gegeben hast 😉
    Hab da mal eine Frage:
    Soll dieses Präfix für die Bezeichnung der DB stehen ?
    lg

    • Avatar von Alexander Bogomolov
      Alexander Bogomolov

      Hallo Frida_66,
      nicht ganz. Ich habe bei jedem Projekt einen anderen Tabellenpräfix. Damit vermeitet man, dass potenzielle Angreifer die Tabellennamen „erraten“. Vor allem bei Open-Source Projekten sehr wichtig!
      Beispiel anhand der Tabelle user:
      Anwendung 1: yxcvbn_user
      Anwendung 2 qwertz_user
      Gruß
      Alex

  2. Avatar von BStylz90
    BStylz90

    Hallo,
    ich habe die „Passwort vergessen“-Funktion (Teil 1) einmal für mich angepasst und auf meinen (Test-)Webserver gepackt. Nun meine Frage: wenn ich die php-Datei aufrufe, bleibt die Seite leer. Eine Datenbank „changepass“ mit „userid“, „challenge“, „valid“ und „timestamp“ habe ich angelegt.
    Ich möchte Teil 1 erst einmal zum Laufen gebracht haben, bevor ich mit dem Teil 2 beginne 🙂

    Vielen Dank für eure Unterstützung,
    BStylz90

    • Avatar von Alexander Bogomolov
      Alexander Bogomolov

      Also ganz leer sollte die Seite nicht sein. Das sieht nach einem Fehler in der PHP Datei aus. Hast du die Fehlermeldungen bei dir auf dem Server unterdrückt?

  3. Avatar von Honja
    Honja

    Hallo Alexander,

    vielen Dank für Deine Anleitung. Ich brauche die Passwort vergessen-Funktion für eine Webseite und habe mich mal durch Dein Skript durchgearbeitet.
    Alle Datenbanken sin erstellt, der Zugriff passt, die erste Seite funktioniert und versendet eine E-Mail mit angehängten Variablen im Link. Ich habe jetzt das Problem, das mit Anklicken der Mail zwar die Datei challenge.php aufgerufen wird, aber die Variablen $UserID und Challenge nicht abrufbar sind. Ich probiere schon ziemlich lange, aber finde den Fehler nicht.

    Vielen Dank für eine Unterstützung,
    Honja

    • Avatar von Alexander Bogomolov
      Alexander Bogomolov

      Hallo Honja, wie sieht den der verschickte Link aus?
      Dieser muss natürlich die beiden GET-Parameter beinhalten:
      http://www.starstormdesign.de/change.php?UserID=154&challenge=md5hash

      • Avatar von Honja
        Honja

        Hallo Alexander, danke für Deine Antwort.
        Die Variablen wurden korrekt übertragen; ich habs ausprobiert und kann sie mit echo ausgeben lassen. Soweit hab ichs hingekriegt. Allerdings kann ich ein paar Zeilen nicht nachvollziehen.

        if(@is_numeric((int)$UserID) && @(int)strlen($Challenge) === 32)

        ergibt eine Fehlermeldung wegen unerlaubtem ===;
        change_userid ergibt folgende Fehlermeldung:
        challenge_auslesen hat nicht funktioniert: Unknown column ‚changepass.change_userid‘ in ‚field list‘. Hast du noch eine weitere Tabelle erstellt, mit change_userid, change_challenge… ?
        Was definiertst du als $user_passwort = pass_protection($user_passwort1); das gibt auch eine Fehlermeldung.
        Entschuldige, wenn ich soviele Fragen habe, aber ich suche wirklich schon lange nach den Fehlern und habe auch die Fehlermeldungen angepaßt.
        Ich wäre Dir dankbar, wenn ich das Skript endlich zum Laufen bringe.
        Honja

        Vom Admin bearbeitet: Code wird jetzt hervorgehoben. 😉

        • Avatar von Alexander Bogomolov
          Alexander Bogomolov

          Hallo Honja,
          ich gucke mir das gerne an. Magst du den Code evtl. zeigen?
          http://pastebin.com/
          Dann kann ich etwas besser gucken, was da los ist.
          (Ich muss dazu sagen, dass das Script schon recht alt ist und ich das mal überarbeiten müsste.)
          Gruß Alex

  4. Avatar von Honja
    Honja

    Hallo Alexander,

    ich habe den Code in pastebin hochgeladen:

    Honja/Formular Challenge.php.

    Ich bekomme eine Fehlermeldung:challenge_auslesen hat nicht funktioniert: Unknown column ‚changepass.change_userid‘ in ‚where clause;

    außerdm wird nie das Formular zur Passwortänderung angezeigt.

    Ich hoffe, du kannst mir helfen

    Grüße Corinna

    • Avatar von Alexander Bogomolov
      Alexander Bogomolov

      Hallo Honja,
      da habe ich im Tutorial wohl nicht aufgepasst…
      Die Attribute „change_userid„, „change_challenge“ und „change_valid“ müssten doch eigl. ohne den Prefix „change_“ heißen. (Also einfach nur „userid„, „challenge“ und „valid„.)
      Kann das der Fehler sein? Wie hast du die Attribute im ersten Teil des Tutorials genannt?

      Wünsche dir einen guten Rutsch!
      Alex

  5. Avatar von Michael
    Michael

    Hallo Alexander,

    sehr schöne Anleitung. Genau das, was ich für ein neues Projekt suche. Vielen Dank.

    Ich habe nur eine Frage, bzw. Anmerkung:
    Du setzt in der Challenge-Tabelle den Flag-Wert nach erfolgreicher Passwortänderung auf „0“.

    Du überprüfst bei „$challenge_auslesen“ aber nicht diesen Wert auf 0 oder 1?!
    Somit könnte man während der Lebensdauer (24Stunden) trotz bereits erfolgter Änderung immer wieder mit dem Script challenge.php das Passwort ändern.

    Gruß,
    Michael

    • Avatar von Alexander Bogomolov
      Alexander Bogomolov

      Hallo Michael, freut mich, dass es dir hilft.
      Du hast recht! In den Zeilen 24 – 29 müsste noch das Attribut „valid“ geprüft werden.
      Ich werde bei Gelegenheit das Script mal etwas aktuellisieren. Das entspricht ja nicht wirklich dem „guten Code“ mehr. 😉
      Viele Grüße
      Alex

  6. Avatar von Robert
    Robert

    Hallo Alexander,

    du sagst, es sei unsicher, dem User ein Passwort per Email zukommen zu lassen. Was ich nicht verstehe ist, was macht es sicherer, dem User einen Link zu schicken, auf den er dann klicken muss. Diese Email kann doch auch mitgehorcht werden und der Angreifer kann dann auf den Link klicken und das Passwort ändern. Oder denke ich hier falsch?

    Danke!

    • Avatar von Alexander Bogomolov
      Alexander Bogomolov

      E-Mail können abgehört werden, ja.
      Bekommt ein Angreifer den Link und ändert das Passwort selbst, so wird es dem Benutzer sehr schnell auffallen und er kann reagieren.
      Wird aber ein Passwort automatisch gesetzt, und der Angreifer bekommt dieses in die Finger, so kann er sich Problemlos und unauffällig einloggen und „parallel“ den Account nutzen. Der rechtmäßiger Besitzer des Accounts bekommt es ja nicht mit. (Solange der Angreifer das Passwort nicht ändert.)

  7. Avatar von Ivan
    Ivan

    Hallo,

    um gleich den letzten Post zu beantworten:
    Der größte Unterschied ist, dass man dann ein wahrscheinlich vom Benutzer gesetztes Passwort erfrägt und sich damit gleich einloggen kann – auf dieser Website und vielleicht auch gleich auf anderen, auf denen der Benutzer dasselbe Passwort benutzt.
    Hat man nur den Token, kann man erstmal „nur“ das Passwort zurücksetzten. Wenigstens eine Aktion die der Server und wahrscheinlich auch der Kunde mitbekommt.

    Was mir hier fehlt (oder habe ich ihn übersehen?) ist der Hinweis darauf, dass der Token für immer einmalig sein muss oder falls nicht (weil er z.B. nach Verfall gelöscht wird) muss der Link zusätzlich zum Token auch die Email-Adresse beinhalten.
    Oder?
    Sonst könnte man mit einem ehemals gültigen Link eventuell später das Passwort eines anderen zurücksetzen…

    LG

    • Avatar von Alexander Bogomolov
      Alexander Bogomolov

      Hallo Ivan, danke für die Antwort!
      An dem Link ist auch die User-ID angehängt. Somit bleibt das Token eindeutig.
      Viele Grüße, Alex

      • Avatar von Ivan
        Ivan

        Ach so, ja das ist OK.
        Wenn man ganz heikel wäre könnte man noch sagen: Auch der Benutzername hat so wie ein Passwort nichts in der Email zu suchen.
        Also wäre im Link meiner Meinung nach am besten nichts außer dem Token (welcher natürlich am besten für immer und ewig eindeutig sein sollte) oder zusätzlich die Email-Adresse.

        Andererseits: Man könnte den Benutzernamen auch für den Email-Empfänger ins Mail schreiben. Schließlich vergisst man diesen inzwischen fast genauso gerne wie ein Passwort 🙂

        Kleine Anregung: Der Benutzer, der die Email-Adresse auf der Passwort-vergessen-Seite einträgt sollte nicht erfahren, ob die Adresse gefunden wurde und die Email versandt wurde.
        Das betrachte ich als sehr unsicher; damit kann ein Angreifer ausprobieren, mit welcher Email-Adresse Leute angemeldet sind (diese ist oft gleichzeitig Benutzername usw.).
        Aus demselben Grund gibt man ja beim fehlgeschlagenen Login-Versuch nicht Preis, ob das Problem am Benutzernamen, am Passwort oder an beidem lag…

        • Avatar von Alexander Bogomolov
          Alexander Bogomolov

          Da hast du natürlich völlig Recht. Ich selber habe das Verhalten bei WordPress abgestellt.
          Ich werde die Tutorials mal auf den neuesten Stand bringen, sobald ich etwas Zeit finde. 😉

  8. Avatar von Frank
    Frank

    Hallo Alexander,

    ich hoffe, du bist noch auf dieser Seite aktiv. Ich habe nun den ersten Teil zum laufen bekommen. Allerdings wird beim aufrufen des links in der Aktivierungsmail nur die Startseite der website (also die index Seite) angezeigt. Muß im link nicht auch ein Hinweis sein, welche Seite damit aufgerufen werden soll oder was mache ich falsch?

    Danke für eine Antwort,
    Frank