"Sicherer" Webseiten Login

Django, Flask, Bottle, WSGI, CGI…
Bodyslam
User
Beiträge: 14
Registriert: Montag 1. Juli 2013, 10:41

Hallo Leute,

ich arbeite mich gerade in Webseitengestaltung ein und habe mit dem Groß davon auch keine Probleme, jedoch bin ich jetzt schon seit einige Tagen dabei, ein ordentlichen Login zu gestalten, also einer der auch vertrauenswürdig ist. Daher hier mal ein paar Fragen.

Im Forum hier steht jetzt schon eine ganze Menge und da hat wohl jeder eine andere Meinung zu. Meine Idee wäre nun folgender, ein User meldet sich an, damit gibt er PW und Username an, dies wird per https gesendet (PW md5 gehasht). Serverseitig wird mit dem Namen in der DB gesucht und mit der dazugehörigen (bei der Registrierung random erstellten) Nummer ein Hexdigest gebildet. Stimmt das dann mit dem PW-Eintrag zusammen folgt alles andere. Da Leute von außen kein Zugriff auf die DB haben und die Daten sicher per https übertragen werden, sollte es doch im Grunde ein "sicherer" Login sein der ohne größeren Aufwand nicht zu knacken ist, wenn ich das falsch sehe berichtigt mich bitte, da mir das jetzt schon ein paar Tage durch den Kopf geht.

MfG
B.
Benutzeravatar
kbr
User
Beiträge: 1509
Registriert: Mittwoch 15. Oktober 2008, 09:27

Im Prinzip sind Deine Überlegungen richtig. Bereits das Login hat per https zu erfolgen, damit die User-Passwort Kombination verschlüsselt übertragen wird. Wie sicher das ist sei jetzt mal dahin gestellt, genauso ob es diejenigen, die es knacken können, interessiert.
Richtig ist weiter, dass auf Serverseite die Passwörter nicht im Klartext vorliegen sollten, sondern als Hash, der keinen Rückschluß auf den ursprünglichen Wert zulässt. md5 ist dieser Hinsicht mittlerweile aber zu schwach.
Bodyslam
User
Beiträge: 14
Registriert: Montag 1. Juli 2013, 10:41

Danke für die schnelle Antwort. Als anstatt md5 könnte man ja auch sha nehmen; was würdest du anders machen von der Logik des Ablaufes? Mir geht es eben darum, dass ich ordentliches Login habe, eine Seite die ständig gehackt wird weil das nicht sicher ist, ist unsinn.
Benutzeravatar
kbr
User
Beiträge: 1509
Registriert: Mittwoch 15. Oktober 2008, 09:27

Der Ablauf ist wie folgt: Benutzername und Passwort werden per https an den Server übertragen. Dort wird aus dem Passwort ein Hashwert gebildet und geprüft, ob die Benutzername-Hashwert Kombination als Login erlaubt ist.
Das Übertragen des Passwort bereits als Hash (falls ich Dich richtig verstanden habe) erhöht die Sicherheit nicht.
Wichtig ist bei diesem Schritt die Sicherheit der Datenübertragung an sich.

Auf Serverseite sieht es so aus, dass ein unknackbarer Server die Passwörter auch im Klartext speichern könnte. In der Praxis ist davon auszugehen, dass die Passwortdatei zugänglich ist, und sei es durch Personal, das einen regulären Zugang hat.

Deshalb dürfen die Passwörter nur verschlüsselt vorliegen, idealerweise in einer Einwegverschlüsselung. Ob die einer "Rückübersetzung" standhält, hängt von der Zahl möglicher Kombinationen und der eingesetzten Rechenleistung ab.
md5 ist heute zu knacken. Der aktuelle Stand bei sha ist mir nicht bekannt.
DanJJo
User
Beiträge: 90
Registriert: Mittwoch 13. Februar 2013, 18:35

Ich habs so gemacht

Code: Alles auswählen

# From https://github.com/mitsuhiko/python-pbkdf2

import os, sys, re
import hashlib
from os import urandom
from base64 import b64encode, b64decode
from itertools import izip
from pbkdf2 import pbkdf2_bin

SALT_LENGTH = 12
KEY_LENGTH = 24
HASH_FUNCTION = 'sha256'
COST_FACTOR = 10000
# Parameters to PBKDF2. Only affect new passwords
def make_hash(password):
    if isinstance(password, unicode):
        password = password.encode('utf-8')
    salt = b64encode(urandom(SALT_LENGTH))
    return 'PBKDF2${}${}${}${}'.format(HASH_FUNCTION,COST_FACTOR,salt,b64encode(pbkdf2_bin(password, salt, COST_FACTOR, KEY_LENGTH,getattr(hashlib, HASH_FUNCTION))))

def check_hash(password, hash_):
    if isinstance(password, unicode):
        password = password.encode('utf-8')
    algorithm, hash_function, cost_factor, salt, hash_a = hash_.split('$')
    assert algorithm == 'PBKDF2'
    hash_a = b64decode(hash_a)
    hash_b = pbkdf2_bin(password, salt, int(cost_factor), len(hash_a),
                        getattr(hashlib, hash_function))
    assert len(hash_a) == len(hash_b)  
    diff = 0
    for char_a, char_b in izip(hash_a, hash_b):
        diff |= ord(char_a) ^ ord(char_b)
    return diff == 0
funktioniert lokal gut...im "internet" irgendwie nicht bekomme ein bad request? liegts daran, dass er auf dem server import os etc nicht findet?
Zuletzt geändert von Anonymous am Mittwoch 3. Juli 2013, 12:14, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@DanJJo: Woran der "Bad Request" liegt kann man so schlecht beantworten, denn das gezeigte hat ja mit Internet/HTTP erst einmal überhaupt nichts zu tun.
DanJJo
User
Beiträge: 90
Registriert: Mittwoch 13. Februar 2013, 18:35

Na Gut also in der main.py

route zum senden der register daten...

Code: Alles auswählen

@route('/register_submit', method='POST')
@route('/register_submit/', method='POST')
def register_submit(db):
    name = (request.forms.get('usernamesignup')).decode('utf-8')
    mail = request.forms.get('emailsignup').decode('utf-8')
    matrnr = request.forms.get('matrnrsignup').decode('utf-8')
    password = (request.forms.get('passwordsignup')).decode('utf-8')
    password_confirm = (request.forms.get('passwordsignup_confirm')).decode('utf-8')
    dat = date.today() 
    sql = db.execute('SELECT StudName, StudMail, StudMatrNr FROM Student WHERE StudName=? or StudMail=? or StudMatrNr=?',(name,mail,matrnr,))
    row = sql.fetchone()
    if row:
      return "/register"
    else:
      if password == password_confirm:
	pwhash = make_hash(password)
	response.set_cookie("account", name, secret='BlaBla', path="/")
	sql=db.execute('INSERT INTO Student(StudName,StudMail,StudMatrNr,StudPassword,StudAdmin,StudTele,FirstName,SecName,TimeStamp,free) VALUES (?,?,?,?,?,?,?,?,?,?)',(name,mail,matrnr,pwhash,'0','123456','Default','Default',dat,'0',))
	return "/hauptmenu"
      else:
	return "/register"
register.tpl

Code: Alles auswählen

<script>     
(function() {
$('.regi').bind("submit", function() {
 
        $.ajax({
            type: "POST",
            url: "{{script}}/register_submit",
            data: "&usernamesignup=" + $("#usernamesignup").val() + "&emailsignup=" + $("#emailsignup").val()  + "&matrnrsignup=" + $("#matrnrsignup").val() + "&passwordsignup=" + $("#passwordsignup").val() + "&passwordsignup_confirm=" + $("#passwordsignup_confirm").val(), 
            success: function(response)
            {
		$("#contenti").hide().load("{{script}}"+response).fadeIn(2000);
            }
        });
        return false;
      });
      })();
</script>
<div id="insert_container" >
    <div id="wrapper_pages_small">
	<div id="pages_style_small" class="regi">
            <form id="submitregi" method="POST" action="/register_submit">
                <h1> Willkommen </h1>
                <h2> REGISTER </h2>
                <ul>
		  <li>
		    <p>
		      <label for="username" class="uname" data-icon="u">Benutzername :</label>
		    </p>
		  </li>
		  <li>
		    <p>
		      <input id="usernamesignup" name="usernamesignup" required="required" type="text" placeholder="Benutzername" />
		    </p>
		  </li>
		  <li>
		    <p>
		      <label for="emailsignup" > E-mail :</label>
		    </p>
		  </li>
		  <li>
		    <p>
		      <input id="emailsignup" name="emailsignup" required="required" type="email" placeholder="beispiel@mail.de"/>
		    </p>
		  </li>
		  <li>
		    <p>
		      <label for="matrnr" class="matnr" data-icon="m">MatrikelNr :</label>
		    </p>
		  </li>
		  <li>
		    <p>
		      <input id="matrnrsignup" name="matrnrsignup" required="required" type="text" placeholder="123456789" />
		    </p>
		  </li>
		  <li>
		    <p>
		      <label for="passwordsignup" class="youpasswd" data-icon="p">Passwort :</label>
		    </p>
		  </li>
		  <li>
		    <p>
		      <input id="passwordsignup" name="passwordsignup" required="required" type="password" placeholder="Passwort"/>
		    </p>
		  </li>
		  <li>
		    <p>
		      <label for="passwordsignup_confirm" class="youpasswd" data-icon="p">Passwort bestätigen :</label>
		    </p>
		  </li>
		  <li>
		    <p>
		      <input id="passwordsignup_confirm" name="passwordsignup_confirm" required="required" type="password" placeholder="Passwort erneut eingeben"/>
		    </p>
		  </li>
		  <li>  
		    <p class="signin button">
		      <input type="submit" value="Registrieren"/>
		    </p>
		  </li>
		  <li>
		    <hr />
		    <p id="tipp">bereits Registriert?<a class="change_link" href="../login"> Login </a></p>
		  </li>
		 <ul>
            </form>
        </div>
    </div>
</div>

so und wenn ich nun

Code: Alles auswählen

pwhash = make_hash(password)
weg lasse läuft er ohne BadRequest durch (im Internet (natürlich muss ich dann auch den eintrag in die DB weg nehmen). Lokal funktioniert das alles problemlos.Kann ich also davon ausgehen, dass es irgendwas mit den Importen zu tun hat ..sprich auf meinem rechner kann ich os..etc importen und auf dem server nicht?
BlackJack

@DanJJo: Nicht zwingend, das kann auch an allem möglichen anderen Sachen liegen. Wenn wir von Internet reden, dann immer noch von Uberspace.de? Da kannst Du Dich doch einfach anmelden und es im Python-Interpreter live ausprobieren ob es sich importieren lässt und auch die Funktion(en) mal ausprobieren.
DanJJo
User
Beiträge: 90
Registriert: Mittwoch 13. Februar 2013, 18:35

ja wir reden von Uberspace. Also die sachen lassen sich importieren. Die Funktion ergibt folgenden fehler


hab mal alles manuel eingegeben

Code: Alles auswählen

    return 'PBKDF2${}${}${}${}'.format('sha256',10000,salt,b64encode(pbkdf2_bin(password, salt, 10000, 24,getattr(hashlib, 'sha256'))))
ValueError: zero length field name in format
BlackJack

@DanJJo: '{}' als Platzhalter geht erst ab Python 2.7. Also entweder Python 2.7 benutzen oder bei 2.6 gültige Platzhalter, also mit Indexnummer, verwenden.
Bodyslam
User
Beiträge: 14
Registriert: Montag 1. Juli 2013, 10:41

kbr hat geschrieben: ...
Das Übertragen des Passwort bereits als Hash (falls ich Dich richtig verstanden habe) erhöht die Sicherheit nicht.
Wichtig ist bei diesem Schritt die Sicherheit der Datenübertragung an sich.
...
Ich versuche Lösungen ohne JS in den sensibelen Bereichen einzusetzen, um die mögliche Fehlerquellen zu begrenzen, darf ich daher deiner Antwort entnehmen, dass das PW nicht gehasht übertragen werden muss bei einer https-Verbindung? Oder meinst andere Methoden um die Sicherheit der Datenübertragung zu verbessern?
Bodyslam
User
Beiträge: 14
Registriert: Montag 1. Juli 2013, 10:41

@DanJJo

Arbeitest du mit einem Framework? Der Code sieht mir komplett selbsterstellt aus, existiert für die Aufgabe, die du realisierst, nicht eine fertige Lösung?
BlackJack

@Bodyslam: DanJJo verwendet das Mikrorahmenwerk Bottle. Aber anscheinend nichts für die Form-Verarbeitung und auch kein ORM. Ich verwende da gerne WTForms und SQLAlchemy wenn ich mit Bottle beziehungsweise mit SQL-Datenbanken im allgemeinen arbeite.

@DanJJo: Schau mal in die Dokumentation wie man Unicode-Objekte von `request.query` beziehungsweise `FormsDict`-Exemplaren im allgemeinen bekommen kann: Request Data.
Bodyslam
User
Beiträge: 14
Registriert: Montag 1. Juli 2013, 10:41

Ich habe mir da eine andere Kombo zusammengestellt. Bottle hatte ich mir auch mal angesehen, aber Cherrypy fand ich dann doch etwas ausgereifter und die Konfigurationoption(en) dort besser. Da ich ungerne mit SQL arbeite, bin ich bei MongoDB geblieben. Mit CherryPy und Moody-Templates ist das schon eine angenehme Sache. Für Angelegenheiten wie hier besprochen muss ich nur mal in JS reinschauen, damit ich auch tatsächlich ein ordentliches Login hinbekomme.

Danke nochmal für die Tipps, damit bekomme ich bestimmt schon was gutes hin, aber der erste Codesnippet von DanJJo ist schon eine starke Nummer :D :D
Benutzeravatar
kbr
User
Beiträge: 1509
Registriert: Mittwoch 15. Oktober 2008, 09:27

Bodyslam hat geschrieben:darf ich daher deiner Antwort entnehmen, dass das PW nicht gehasht übertragen werden muss bei einer https-Verbindung?
Angenommen das Passwort wird als Hashwert übertragen. Dann kann der Server hieraus nicht auf das ursprüngliche Passwort schließen, sondern muss den Hashwert als gültiges Passwort verwenden. Nimm nun weiter an, dass die Übertragung abgehört und entschlüsselt wird. Dann ist es völlig unerheblich, ob das Passwort oder dessen Hash übertragen wurde, da beide gleichwertig ein erfolgreiches Login ermöglichen.
Bodyslam
User
Beiträge: 14
Registriert: Montag 1. Juli 2013, 10:41

Aber ich denke dass die Möglichkeiten des Abhörens mit https eingeschränkt/nicht möglich sind? was stimmt denn nun?
(Ausschnitt aus Wikipedia) ... ist ein Kommunikationsprotokoll im World Wide Web, um Daten abhörsicher zu übertragen.
BlackJack

@Bodyslam: Das Problem bei SSL ist, das kaum ein Anwender tatsächlich die Zertifikate prüft, und viele Anwender selbst bei unpassenden Zertifikaten die Warnungen vom Browser einfach abnicken, und das nicht wirklich jede CA die in den gängigen Browsern als vertrauenswürdig hinterlegt sind, das auch tatsächlich ist.
Bodyslam
User
Beiträge: 14
Registriert: Montag 1. Juli 2013, 10:41

Achso, also sowas wie Fakezertifikate, dass ist doch dann ein Problem des Webseitenbesuchers?. Aber wenn ich das Recht verstehe, sind die Beteiligten doch, der Austeller des Zertifikates, der Nutzer(also User der Internetseite) und der Domainbetreiber. Wenn also der Code sauber programmiert ist, das Zertifikat seriös, der Host up-to-date sollten doch die Fehlerquellen gering sein oder?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

1. CA wird vom Browser als vertrauenswuerdig gefuehrt
2. CA stellt Zertifikate fuer Websites aus - gefaelschte oder "echte" - fuer die es aber schon Zertifikate gibt
3. Angreifer mogelt diese Zertifikate dem Besucher unter
4. Besucher bemerkt bei einem handelsueblichen Browser niemals, dass er falsche Zertifikate bekommen hat

Natuerlich ist das immer ein Problem des Besuchers, wessen auch sonst? Aber wenn du den Aufwand schon treibst, dann willst du ihn doch auch schuetzen? Es gibt sehr weniger Szenarien in denen der Betreiber hier tatsaechlich den Schaden (mit-)traegt.
Bodyslam
User
Beiträge: 14
Registriert: Montag 1. Juli 2013, 10:41

Ok, also ist der Schaden dann obligatorisch beim Nutzer des Dienstes. Wie sieht es dann da mit einer Lösung aus? Könnte man nicht ganz banal (wenn man beim Zertifikat bleibt) dem User einfach Anzeigen welches Zertifikat die Seite nutzt? Da ich mich gerade mit JS einarbeite kann ich da leider nicht viel beisteuern, aber ist es nicht möglich per JS das Zertifikat, welches die Domain nutzt, mit dem zu vergleichen welches im Browser des Nutzers ankommt?
Antworten