Sicherheitsaspekte bei der Gestaltung von ASP Sites ohne Cookies
Geschrieben von: Rene Drescher-Hackel Jeder, der sich mit der Erstellung komplexer ASP-Anwendungen beschäftigt, hat sich die Frage beantworten müssen, ob er denn nun Cookies einsetzt oder nicht. Denn häufig besteht das Problem, wie sich die ASP-Anwendung z.B. den Anmeldestatus eines Besuchers der Website merkt. Eine Lösung sind die Session-Variablen. Doch da wären wir wieder beim (temporären) Cookie. Umgehen ließe sich das Problem, wenn es eine Möglichkeit gäbe, den Besucher des Webangebotes an einem eindeutigen Kennzeichen zu identifizieren - und dieses Kennzeichen auch nicht zwischen den Seiten verloren geht. Warum weit streifen, wenn das gute so nahe liegt: in der URL wird ein bestimmter - von der ASP-Anwendung festgelegter - Parameter von Seite zu Seite übergeben. ParameterwahlDoch was eignet sich als (eindeutiger) Parameterwert?
Dies ist nicht ganz unproblematisch, da innerhalb einer Sekunde mehr als ein Benutzer das Angebot zeitgleich aufrufen könnten.
Scheidet noch viel eher aus als die Zeit - aus offensichtlichen Gründen.
Diese könnte sich wiederholen, beziehungsweise es könnte der Grenzwert irgendwann erreicht sein. Nicht ganz unkritisch, wenn man bedenkt, daß viele Surfer über Provider ins Netz gehen, die ihrerseits wiederum Proxies einsetzen. So könnte eine IP sich am Tag wiederholen, ohne daß es derselbe Benutzer ist. Für sich alleine ist keiner dieser Parameter geeignet, einen Benutzer eindeutig zu identifizieren. Hingegen eine Kombination aller hier vorgestellten Parameter ergibt jedoch eine derart lange Zeichenfolge, die wohl durchaus einmalig sein dürfte. Zur Sicherheit sollte man aber auch hier noch eine Duplikatsprüfung vornehmen, was ich auch empfehle. Es könnte ja sein, daß zur exakt selben Zeit ein Benutzer über den exakt gleichen Proxy eines Providers Seiten anfordert - und wir durch dummen Zufall die gleiche Zufallszahl generieren. Wie erstellen wir nun diese ID, die dann in der URL von Seite zu Seite mit gegeben werden soll? Hier das entsprechende Code-Beispiel: <% RANDOMIZE(TIME()) ' 1. Parameter - eine Zufallszahl bis 999.999.999 z = Int(999999999 * RND + 1) ' 2. Parameter - eine Zahl aus der aktuellen Zeit erstellt t = Replace(TIME(),":","") ' 3. Parameter - eine Zahl aus dem aktuellen Datum erstellt d = Replace(DATE(),".","") ' 4. Parameter - eine Zahl aus der IP des Besuchers erstellt ip = Replace(Request.ServerVariables("REMOTE_ADDR"),".","") ' ID erzeugen tempID = z & t & d & ip %> Der Zufallsgenerator wird über den Zeitwert (TIME()), immer wieder neu angestoßen. Aus den Werten der Zeit, des Datums und der IP des Benutzers wird mit der Replace-Anweisung die Punktierung entfernt. Anschließend werden alle Werte aneinander gefügt <% tempID = z & t & d & ip %> Würde man statt des & ein + verwenden, so würden alle Zahlenwerte zusammenaddiert werden, was das Risiko wieder mit sich brächte, daß die daraus resultierende Zahl nicht in jedem Fall eindeutig wäre. Unter Verwendung der oben angesprochenen Fehlerprüfung, wäre dies aber machbar. Eine solche tempID könnte dann folgendermaßen aussehen: 641262233163400200501192168120254 Parameterwert speichernDie ID hätten wir. Nun ist die Frage, wo wir diese abspeichern. Sessionvariablen kommen klarerweise nicht in Frage, somit bleibt uns nur eine Datenbank. Für den heutigen Artikel verwende ich eine Access 2000 Datenbank, in der ich eine Tabelle namens tblCount erstellt habe. Um in der INSERT-Anweisung etwas Schreibarbeit zu sparen, habe ich für das Datum und die Zeit in den Eigenschaften einen Standardwert vorgegeben. Nun kann man die generierte ID auch schon eintragen: <% sql = "INSERT INTO tblCount(sessionid) VALUES(" & tempID & ");" call dbconnect() Conn.Execute(sql) call dbclose() %> Das Erstellen der Datenbankverbindung habe ich hier aus Gründen der besseren Handhabung in zwei Unterroutinen "verpackt". <% Private Sub dbconnect() ' Datenbankverbindung aufbauen (mit Fehlerprüfung) If IsObject("Conn") = FALSE Then ' Object erstellen Set Conn = Server.CreateObject("ADODB.Connection") Dim strConnStr ' Verbindungsinformation zur Datenbank(Connectin-String) strConnStr = "Provider=Microsoft.Jet.OLEDB.4.0;" &_ "Data Source=" & Server.MapPath("db/aspproject.mdb") Conn.Open strConnStr End If End Sub Private Sub dbclose() ' Datenbankverbindung schließen und Instanzen zerstören (mit Fehlerprüfung) If Conn.State = 1 Then ' Connection schließen Conn.Close ' und Instanz zerstören Set Conn = Nothing End If End Sub %> Ist alles ordnungsgemäß eingetragen, erhält man folgenden Eintrag in der Datenbank. Gut, wir haben die ID generiert und gespeichert. Nun sollten wir anfangen, diese auch zu verwenden. Unter Verwendung ist zu verstehen, daß die eben erzeugte ID auch allen Links "mitgegeben" wird. Die Übergabe bei allen Links erfolgt dann nach folgendem Muster: <a href="seite.asp?ID=<%=ID%>">Link</a> Wie das ganze geschieht, möchte ich an einem kleinen Beispiel - einer User-Anmeldung - verdeutlichen. Hierzu habe ich die Datei anmeldung.asp, die das Anmeldeformular enthält und bei erfolgter Anmeldung den Anmeldestatus anzeigt, sowie die Datei cookiefree.asp, in der die Logik integriert ist. Damit die Funktionalität der cookiefree.asp auch der anmeldung.asp zur Verfügung steht, wird diese in die anmeldung.asp per #INCLUDE-Anweisung eingeschlossen. <!--#include file = "cookiefree.asp"--> Innerhalb der cookiefree.asp wird als erstes immer geprüft, ob über den URL eine ID mitgegeben wurde. Der entsprechende Code dazu sieht dann folgendermaßen aus:
<% ' SeitenQuerEinstieg prüfen ID = Request.QueryString("ID") If ID="" Then ' es wurde keine ID in der URL übergeben -> neue ID erzeugen ' ID erzeugen durch Function-Aufruf ID = ID_erzeugen() Response.Redirect "anmeldung.asp?ID=" & ID End If %> Zuerst wird die ID aus dem QueryString abgefragt. Ist keine übergeben worden, dann wird diese erzeugt - wie oben beschrieben - und dann mit Response.Redirect an die anmeldung.asp übergeben. (schließt man diese Funktionalität auf allen Seiten ein, so empfiehlt sich immer ein Response.Redirect auf die ASP-Startseite der Anwendung). Wie erfährt die ASP-Anwendung, daß der Benutzer der Seite auch tatsächlich derjenige ist, der er vorgibt zu sein z.B. nach dem Absenden eines Anmeldeformulars? Eine Möglichkeit: man fügt in den URL einen weiteren Parameter ein, z.B. user = anton oder uid=1188 oder...usw. Hier erkennt man jedoch schnell, daß unliebsame Zeitgenossen es nicht lassen werden, eine uid=1188 gegen uid=1187 oder uid=1189 auszutauschen. Das Ausspionieren von Kundendaten ist nicht gerade selten und potentiell gefährlich. Und gerade aus diesem Grund sollten Anmeldeprozeduren innerhalb dynamischer Webanwendungen immer so gestaltet sein, daß eine Manipulation (weitgehend) ausgeschlossen werden kann. Im vorliegenden Beispiel wird im Anmeldeformular die e-Mail-Adresse und ein Passwort abgefragt. Die e-Mail-Adresse ist ebenfalls ein eindeutiger Wert, der sich somit immer als Benutzername eignet. Nach Möglichkeit sollte clientseitig die Eingabe überprüft werden, das heißt, ob überhaupt ein Wert in den entsprechenden Feldern eingetragen wurde. Dann ergibt sich folgender HTML-Code für das Anmeldeformular (anmeldung.asp): <HTML> <HEAD> <META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0"> <SCRIPT LANGUAGE=javascript> <!-- // Diese Function prüft lediglich, ob ein Wert für // Benutzername und Kennwort eingegeben wurde. // Sie ist ggf. an die eigenen Bedürfnisse anzupassen. function checkit() { if(document.anmeldung.benutzer.value=='') { alert("Sie müssen einen Benutzernamen eingeben!"); document.anmeldung.benutzer.focus(); return false; } if(document.anmeldung.kennwort.value=='') { alert("Sie müssen einen Kennwort eingeben!"); document.anmeldung.kennwort.focus(); return false; } else { document.anmeldung.submit(); } } //--> </SCRIPT> </HEAD> <BODY> <FORM action="" method=POST id=anmeldung name=anmeldung> <INPUT type="text" id=benutzer name=benutzer value="<%=benutzer%>"> <INPUT type="password" id=kennwort name=kennwort> <INPUT type="button" value="anmelden" id=anmelden name=anmelden onclick="JavaScript:checkit()"> </FORM> </BODY> </HTML> Nachdem das Formular abgeschickt (hier an sich selbst) wurde, werden dann als erstes die übergebenen Werte ermittelt. Anschließend werden die Übergabewerte entsprechend ausgewertet. Zu diesem Zweck habe ich eine kleine Funktion erstellt, der beim Aufruf dann der Benutzername, das Kennwort und die ID übergeben werden: <% Public Function anmeldung(benStr, kennStr, ID) ... End Function %> In der Function wird dann erst einmal die anmeldung = False gesetzt. Dann kann die eigentliche Prüfung erfolgen: <% sql = "SELECT user_id FROM tblKunden " sql = sql & " WHERE mail ='" & benStr & "' " sql = sql & " AND pwd ='" & kennStr & "';" Set temp = Conn.Execute(sql) If temp.EOF Then ' Benutzerdaten stimmen nicht ErrorTxt = "Ihre Registrierung konnte nicht durchgeführt werden.<br>" ErrorTxt = ErrorTxt & "Überprüfen Sie Ihre Eingaben noch einmal." anmeldung = False Else ' Datensatz (login) aktuallisieren sql = "UPDATE tblKunden SET " sql = sql & "login ='" & ID & "' " sql = sql & " WHERE user_id =" & temp(0) & ";" Conn.Execute(sql) anmeldung = True End If %> Hierbei wird als erstes der Benutzer ermittelt und die Eingaben werden mit den gefundenen Ergebnissen in der Datenbank verglichen. Stimmen diese überein, dann wird der Datensatz geändert, indem die aktuelle ID (als Sitzungsvariable) zum User-Datensatz hinzugefügt wird. Andernfalls <% if temp.EOF then ' kein Datensatz gefunden %> wird das UPDATE auf den Userdatensatz nicht ausgeführt - der eingangs zugewiesene Wert <% anmeldung = FALSE %> bleibt erhalten. Da der Function "anmeldung" ein boolscher Wert zugewiesen wird, kann dann die Prüfung der (erfolgreichen) Anmeldung mit dem Aufruf der Function folgendermaßen durchgeführt werden: <% If anmeldung(benutzer, kennwort, ID) Then %> In den nachfolgenden Grafiken wird deutlich, welchen Wert die Function anmeldung jeweils zurückgegeben hat, bzw. zurückgeben kann. War der Wert "True", dann konnte dem User z.B. eine Mitteilung ausgegeben werden, daß die Registrierung erfolgreich ausgeführt wurde. Ist der Rückgabewert der Function "False", dann könnte man z.B. wieder das Anmeldeformular anzeigen. Weiter ginge es dann erst, wenn der Wert "True" zurückgegeben wird. Funktionalität bereitstellenÜber eine weitere kleine Funktion kann ich jetzt jederzeit den Anmeldestatus ermitteln, wobei auch hier mir ein boolscher Wert zurückgegeben wird: <% Public Function angemeldet(ID) ' Überprüfung, ob der User angemeldet ist angemeldet = False sql = "SELECT COUNT(user_id) FROM tblKunden WHERE " sql = sql & " login ='" & ID & "' ;" call dbconnect() angemeldet = cbool(Conn.Execute(sql)(0)) call dbclose() End Function %> Lediglich anhand der ID wird geprüft, ob eine Anmeldung erfolgreich durchgeführt wurde. Stimmt die in der URL geführte ID mit der in der Kundentabelle im Login geführten ID überein, so ist der User angemeldet (angemeldet = True) andernfalls wird angemeldet = False zurück gegeben. Der Aufruf der Funktion erfolgt ähnlich wie oben die Anmeldeprüfung: <% If angemeldet(ID) Then %> ZusammenfassungDurch die doppelte Speicherung der ID kann man erreichen, daß selbst bei Manipulationen der ID die damit abgesicherten Kundendaten geschützt bleiben. Mir ist es bislang noch nicht untergekommen, daß ein User es geschafft hätte, fremde Benutzerdaten auszuspionieren. SchlußbemerkungSicherlich kann man die hier vorgestellte Funktionalität in der einen oder anderen Art erweitern bzw. den eigenen Bedürfnissen anpassen. So könnte man die ganze Prozedur dahin erweitern, daß man die Vorteile des RemoteScripting nutzt, um Manipulationen in der URL völlig auszuschließen. Auch die Erzeugung der ID könnte z.B. durch Bereitstellung der GUID erfolgen. Doch das bleibt letztlich dem einzelnen selbst überlassen. Dieser hier genannte Lösungsansatz soll in erster Linie eine Anregung darstellen, ASP-Anwendungen im Hinblick auf kundenspezifische Daten sicher zu gestalten - auch ohne Cookies. Download des CodesKlicken Sie hier, um den Download zu starten. Verwandte Artikel
Aber bitte mit Rijndael Wenn Sie jetzt Fragen haben...Wenn Sie Fragen rund um die in diesem Artikel vorgestellte Technologie haben, dann schauen Sie einfach bei uns in den Community Foren der deutschen .NET Community vorbei. Die Teilnehmer helfen Ihnen gerne, wenn Sie sich zur im Artikel vorgestellten Technologie weiterbilden möchten. Haben Sie Fragen die sich direkt auf den Inhalt des Artikels beziehen, dann schreiben Sie dem Autor! Unsere Autoren freuen sich über Feedback zu ihren Artikeln. Ein einfacher Klick auf die Autor kontaktieren Schaltfläche (weiter unten) und schon haben Sie ein für diesen Artikel personalisiertes Anfrageformular.
Und zu guter Letzt möchten wir Sie bitten, den Artikel zu bewerten. Damit helfen Sie uns, die Qualität der Artikel zu verbessern - und anderen Lesern bei der Auswahl der Artikel, die sie lesen sollten.
©2000-2006 AspHeute.com |