Synchronisation MySQL<->Windows-Passwort
Benjamin S.
- datenbank
Hallo Forum,
gibt es eine Möglichkeit, dass Windows-Anmelde- (Domänen-) Passwort mit einer MySQL-Datenbank (Intranet) zu synchronisieren? Als bisher einzige Möglichkeit ist mir in den Sinn gekommen, die Hashes aus dem SAM-File von Windows regelmäßig mit "PWDumpX" auszulesen. Doch irgendwie bereitet mir das Bauchschmerzen, schließlich ist das sicherheitstechnisch doch etwas bedenklich... Und welcher Algorithmus wird eigentlich in den SAM-Dateien verwendet? Ich habs probiert, MD5 und SHA1 ist es nicht.
Dankeschön für eure Mithilfe.
MfG Benjamin.
Moin!
gibt es eine Möglichkeit, dass Windows-Anmelde- (Domänen-) Passwort mit einer MySQL-Datenbank (Intranet) zu synchronisieren?
Möglichkeiten gibts bestimmt ganz viele, allerdings nichts, was bei MySQL mit aus der Tüte fällt.
Die allererste Frage, die sich stellt: Wozu brauchst du im Intranet MySQL-Useraccounts synchronisiert mit der Domäne? Gewöhnlich greifen User nicht direkt auf MySQL zu, sondern ein Skript tut es. Wie melden sich denn deine User an, und wo überall? Was genau machst du da eigentlich?
- Sven Rautenberg
Hallo Sven, Hallo Forum,
da war ich wohl etwas unspezifisch. Es geht nicht um die Synchronisation Windows-Account<->MySQL-Account, sondern um eine Synchronisation Windows-Account<->Datensatz-in-einer-MySQL-Tabelle. In aller Ausführlichkeit ist das Szenario folgendes:
In einer Firma melden sich Nutzer im Firmennetz mit ihren Windows-Zugangsdaten an. Ihr Passwort dürfen sie nicht selbst ändern, allerdings wird in regelmäßigen Abständen "von oben" ein neues Passwort erstellt (eine, wie ich finde, eigenartige Regelung, aber ich muss damit leben).
Außerdem gibt es meine PHP-Webanwendung im Intranet, in die sich die Nutzer sessionbasiert einloggen. Die Accounts dafür sind in einer MySQL-Tabelle gespeichert (Passwort ist MD5-verschlüsselt). Es ist auch hier nicht gewünscht, dass ein Nutzer sein Passwort von Hand ändern kann.
Das Dilema: Ich stelle für die Webanwendung die Passwörter ein, welche die Nutzer für ihren Windows-Zugang nutzen. Nach einigen Wochen (wenn ich nicht mehr hier bin) ändert sich ihr Windows-Passwort. Die Nutzer selbst dürfen ihr Webanwendungs-Passwort nicht ändern. Nun müssen sie sich also zwei Passwörter merken. Und darüber wurde sich beschwert.
Ich dachte mir also, man könnte vielleicht irgendwie an die Windows-Passwort-Hashes herankommen. Man könnte also eine Batchdatei (oder whatever) schreiben, welche quasi als Cronjob gestartet wird, die Hashes ausliest, sie in eine Datei packt und dann ein PHP-Skript aufruft, welches wiederum die erzeugte Datei nutzt, um die in der Datenbank gespeicherten Hashes zu aktualisieren.
Die entscheidenden Hacken bei der Sache:
* Die Hashes sind bei Windows in der SAM-Datei gespeichert (Security Access Manager). Diese ist selbst für Admins nicht lesbar (nur für den Nutzer SYSTEM). Da dies eine gewollte Sicherheitsfunktion ist, möchte ich sie eigentlich ungern umgehen.
* Es gibt zwar ein Programm, welches per "DLL-Injection" auf die SAM-Dateien zugreifen kann (besagtes "PWDumpX" - oder auch "PWDump", "PWDump2", "PWDump3", "PWDump3e"), und so die Hashes auslesen kann, dies ist allerdings auch als Hacker-Tool verschrien. Als ich es auf meinem Privat-Rechner ausprobiert habe, hat es nebenbei noch ein paar Sachen ausgespuckt, die ich eigentlich nicht wollte, dass sie irgendjemand lesen kann.
* Mit den Hashes, die "PWDumpX" ausspuckt, kann ich nix anfangen, denn sie sind mit einer mir unbekannten Hash-Funktion verschlüsselt. Die englische Wikipedia meint zu SAM, dass ein s.g. LM Hash oder NTLM Hash verwendet wird, welcher auf DES basiert... und als ich den letzten Artikel angeschaut hab, bin ich ausgestiegen. :-) Es ist ein Wahnsinn, das jetzt zu implementieren um es anstelle einem simplen md5(...) in der PHP-Webanwendung einzusetzen. Es muss doch eine bessere Lösung geben...
MfG Benjamin.
In aller Ausführlichkeit ist das Szenario folgendes:
Nette Geschichte :)
Aus meiner Zeit als Systemadministrator in irgendeiner deutschen Behörde ist mir in Erinnerung, dass das Verschlüsselungsverfahren für SAM auf Kerberos basiert und mangels Offenlegung durch den Marktführer nur durch BruteForce oder mit Hilfe von Dictionaries knackbar ist. Man hört zwar, dass es mittlerweile (legale) Software gibt, die SAM-Passwörter in Klartext umwandeln können, allerdings kosten die Dinger mehrere hundert Euro, und das ist es m.E. nicht wert. Lass dich nicht von irgendwelchem OpenSource-Gedöns (z.B. Live-CDs mit Zugriff auf das NTFS u.ä.) blenden, die überschreiben die SAM-Einträge nur oder stellen eine Sicherheitskopie wieder her - Auslesen im Klartext ist nicht möglich.
Eventuell solltest du dort ansetzen, wo die Windows-Passwörter geändert werden - schließlich müssen sie irgendwo im Klartext vorliegen. Wenn du hier in eine entsprechende Logik (Shellscript o.ä.) verzweigen könntest, die neben dem eigentlichen Ändern des Windows-Passwortes auch die MySQL-Passwörter ändert, ist dir m.E. mehr geholfen.
Siechfred
Hallo Siechfried, Hallo Forum,
naja, also in Klartext umwandeln / knacken kommt sowieso nicht in Frage, das wäre ja noch ein größeres Sicherheitsrisiko. Ausgeschlossen. Es genügt mir vollkommen, wenn ich weiß, wie ich
1. an den SAM-Hash in seiner ursprünglichen (verschlüsselten) Form komme und
2. aus dem Passwort, welches der Nutzer bei meiner PHP-Anwendung eingibt, einen zweiten Hash erzeugen kann, der mit dem selben Algorithmus wie SAM verschlüsselt ist und
3. ich die beiden dann gegeneinander prüfen kann.
1. (PWDumpX, an so einer Variante führt sicher kein Weg vorbei) und 3. ("==") ist ja jetzt schon geklärt, 2. eben noch nicht. Der Vorschlag, den du gemacht hast, ist mir auch schon durch den Kopf gegangen, ist imho aber nicht so elegant, zumal die Passwortänderung in Windows sicher noch per Hand erfolgt (muss mich da nochmal erkundigen, der Mensch, der die Sachen macht, hat hier gerade Urlaub :-D).
LG Benjamin.
Moin!
Der Vorschlag, den du gemacht hast, ist mir auch schon durch den Kopf gegangen, ist imho aber nicht so elegant, zumal die Passwortänderung in Windows sicher noch per Hand erfolgt (muss mich da nochmal erkundigen, der Mensch, der die Sachen macht, hat hier gerade Urlaub :-D).
Szenario 3: Der Passwortänderer kriegt die Aufgabe, das Passwort an zwei Stellen zu ändern, nämlich auch in deiner Datenbank.
- Sven Rautenberg
Moin!
In einer Firma melden sich Nutzer im Firmennetz mit ihren Windows-Zugangsdaten an.
Außerdem gibt es meine PHP-Webanwendung im Intranet, in die sich die Nutzer sessionbasiert einloggen.
Da ist die Diskrepanz, die zu eliminieren wäre.
Zwei Ansätze fallen mir ein, nur kurz skizziert:
1. Deine Anwendung authentifiziert gegen die falsche Userdatenbank. Anstelle des Abgleichs der Userdaten mit der MySQL-Tabelle sollte dein Skript lieber gegen die Windows-Domäne (ActiveDirectory, eine Abart von LDAP) authentifizieren.
2. Deine Anwendung könnte auch direkt die sessionbasierte Anmeldung sein lassen, und stattdessen HTTP-Authentifikation der Geschmacksrichtung NTLM benutzen - und diese Anmeldedaten dann wieder gegen die Domäne abgleichen.
Der Rest ergibt sich dann mehr oder weniger von selbst. Für deine Anwendung würde Szenario 1 bedeuten, dass deine Spalte Passwort in der Tabelle künftig nutzlos leer bleibt, und dass ggf. Extracode einzufügen ist, um einen neu in der Domäne aufgetauchten Benutzer, der sich erstmalig anmeldet, auch in diese Tabelle einzutragen (nur Username). Das aber auch nur, sofern in deiner Anwendung mehr mit einem Account zusammenhängt, als nur der Benutzername. Wenns nur darum geht, allgemein den Zugang zu allem entweder zu erlauben oder zu verweigern, dann wird's natürlich einfacher.
- Sven Rautenberg
Hallo Sven, Hallo Forum,
- Deine Anwendung authentifiziert gegen die falsche Userdatenbank. Anstelle des Abgleichs der Userdaten mit der MySQL-Tabelle sollte dein Skript lieber gegen die Windows-Domäne (ActiveDirectory, eine Abart von LDAP) authentifizieren.
Danke, davon hatte ich heut morgen noch keine Ahnung, das scheint aber eine gute Idee zu sein. Jetzt hab ich mich eingelesen. Ich habe jetzt die LDAP-Erweiterung im Webserver aktiviert und folgendes Skript geschrieben:
define( LDAP_HOST, "<<Domänencontroller>>" );
define( LDAP_DOMAIN, "<<Domäne>>" );
function ldap_checkUser( $user, $pass ) {
$ldap_host = "ldap://" . LDAP_HOST;
$ldap_user = $username . "@" . LDAP_DOMAIN;
$ldap_conn = @ldap_connect( $ldap_host );
if ( $ldap_conn ) {
$ldap_bind = @ldap_bind( $ldap_conn, $ldap_user, $pass );
if ( $ldap_bind ) {
return true;
} else {
// echo ldap_error($ldap_conn);
return false;
}
}
}
ldap_connect(...) funktioniert, ldap_bind(...) schlägt fehl, ganz gleich, ob die Zugangsdaten gültig sind oder nicht, ldap_error(...) sagt, es liegt an "Invalid credentials". Komischerweise funktioniert anonymes Binden, also ldap_bind($ldap_conn). Hmm...
MfG Benjamin.
Hallo Benjamin,
ldap_connect(...) funktioniert, ldap_bind(...) schlägt fehl, ganz gleich, ob die Zugangsdaten gültig sind oder nicht, ldap_error(...) sagt, es liegt an "Invalid credentials". Komischerweise funktioniert anonymes Binden, also ldap_bind($ldap_conn). Hmm...
Ich kenne mich mit der Active Directory nicht aus, dafür aber mit LDAP an sich. LDAP kennt 2 verschiedene Mechanismen, um einen Bind durchzuführen:
* Simple bind, d.h. einfach mit Usernamen + Passwort im Klartext über die
Leitung (bzw. wenn SSL/TLS verwendet wird halt schon verschlüsselt, aber
nicht auf LDAP-Protokollebene)
* Bind über SASL (was z.B. auch bei SMTP Auth verwendet wird) - SASL ist
eigentlich keine Authentifizierungsart, sondern ein Standard, bei dem
Client und Server einen sicheren Authentifizierungsmechanismus aushandeln
können - bzw. als Fallback existiert immer noch Username + Passwort im
Klartext - *allerdings* ist SASL + Klartextauth nicht das gleiche wie
ein simple bind!
PHPs ldap_bind() führt einen simple bind durch. Keine Ahnung, ob's zutrifft, aber es kann sein, dass die Active Directory einen SASL bind haben will (ich habe allerdings nicht den blassesten Schimmer!) - allerdings ist die PHP-Funktion ldap_sasl_bind nicht wirklich dokumentiert und ich habe sie auch noch nie verwendet.
Allerdings: es könnte auch an etwas anderem liegen:
$ldap_user = $username . "@" . LDAP_DOMAIN;
Das scheint mir *sehr* seltsam für einen LDAP-User zu sein. Kann natürlich sein, dass Active Directory das so will, allerdings sehen LDAP-User normalerweise anders aus.
LDAP ist ja ein Verzeichnis, d.h. eine Baumstruktur, in der alle Objekte gespeichert werden. Stellen wir uns vor, Deine Windows-Domäne heißt test.lan, dann wäre der DN im LDAP für die Domäne dn: dc=test,dc=lan. Wenn Du jetzt eine "Organisational Unit" namens "Users" darin hättest und darin befände sich ein User "benjamin", dann würde man den z.B. über dn: cn=benjamin,ou=Users,dc=test,dc=lan ansprechen können - d.h. Du würdest $ldap_user = "cn=".$username.",ou=Users,dc=test,dc=lan"; angeben. Der genaue Aufbau davon hängt stark von der Struktur des Verzeichnisses ab - ich habe keine Ahnung, ob die Active Directory da Vorgaben macht oder nicht, das müsstest Du mal nachlesen.
Im Zweifel würde ich mal OpenLDAP installieren und dann mit »ldapsearch« per anonymous bind auf den AD-Server zugreifen und einfach mal alles, was öffentlich zugänglich ist, dumpen. Vielleicht bekommst Du dann eine Info, welche Struktur Deinem LDAP innewohnt.
Viele Grüße,
Christian
Hallo Christian, Hallo Forum,
mir hat das Tool LdapSearch 1.2 sehr weiter geholfen, und es ist in der Tat so, wie du es gesagt hast, genauer gesagt, wird als DN etwas in der Form
cn=<<Nutzer>>, cn=Users, dc=<<DomäneL1>>, dc=<<DomäneL0>>
erwartet. So funktioniert es. Juhu, Juhu! War ja ein tüchtiger Kraftakt, hätte nicht gedacht, dass es noch was wird, danke für eure Hilfe.
Es sei noch angemerkt, dass für <<Nutzer>> der _vollständige_ Nutzernamen einzusetzen ist, der höchstwahrscheinlich nicht mit dem Anmeldenamen übereinstimmt. Diesen kann man auf der Kommandozeile herausfinden mit
net user /domain <<Benutzername>>
Hier also das korrigierte, damit funktionierende und außerdem mit Fehlerbehandlungen gepimpte Skript:
define( LDAP_HOST, "<<Domänencontroller>>" );
define( LDAP_DN, "cn=%s,cn=Users,dc=<<DomäneL1>>,dc=<<DomäneL0>>" );
function ldap_checkUser( $user, $pass ) {
$ldap_host = "ldap://" . LDAP_HOST;
$ldap_user = sprintf( LDAP_DN, $user );
$ldap_conn = @ldap_connect( $ldap_host );
if ( $ldap_conn ) {
$ldap_bind = @ldap_bind( $ldap_conn, $ldap_user, $pass );
if ( $ldap_bind ) {
ldap_close($ldap_conn);
return true;
} else {
if (ldap_error($ldap_conn) != "Invalid credentials")
die(ldap_error($ldap_conn));
ldap_close($ldap_conn);
return false;
}
} else die(ldap_error($ldap_conn));
}
Mahlzeit. ;-)
MfG Benjamin.