JavaScript-Zugangsschutz für Spaßvögel II
Der unglaublich schlechte SiteProtector-Algorithmus des ersten Teils hat mich noch nicht ganz losgelassen. Kann so etwas wirklich auf dem eigenen Mist wachsen oder braucht man dazu Hilfe? Apollon benötigte wohl Hilfe von einem gewissen Dion:
The generated script is adapted from the Login-Creator from Dion. Many thanks to him for his help.
Also habe ich mich auf die Suche gemacht und das besagte Skript wohl auch gefunden. Zumindest ist es von einem Dion und der Algorithmus ist sehr ähnlich. Diesmal bestätigt allerdings kein Smiley die Sicherheit des Algorithmus, sondern der Anbieter selbst:
This is undoubtedly the best password protection JavaScript you'll ever find. Besides supporting multiple users, multiple passwords, and even multiple destination pages (after they successfully login), this script is presented in a very easy-to-use interface that you're sure to love! We *wish* we wrote this script, it's that good!
Die Beurteilungskompetenz wird ebenfalls bestätigt:
*Honestly, we don't even totally understand this JavaScript!*
So kompliziert ist es nun auch wieder nicht. Das Skript nimmt Nutzernamen, Paßwörter und Seitennamen entgegen und generiert daraus ein weiteres Skript, das die Hashes und die "verschlüsselten Seitennamen" enthält und in die eigene Netzseite eingebunden werden kann. Interessant ist nur letzteres.
Übernehmen wir doch gleich die Standardeinstellungen, fügen noch zwei weitere Einträge hinzu (Frank Busse, kLmiURtW, logintop) & (Karl Müller, aSdDtcVl, logintop) und lassen uns das Skript generieren:
<form name=login> <table border=1 cellpadding=3> <!-- Original: Dion --> <!-- Web Site: http://www.iinet.net.au/~biab/ --> <!-- This script and many more are available free online at --> <!-- The JavaScript Source!! http://javascript.internet.com --> <tr> <td colspan=2 align=center><font size="+2"> <b>Members-Only Area!</b> </font></td> </tr> <tr> <td>Username:</td> <td> <select name=memlist> <option value='x'> <option value='Karl Müller|17979|NXKONYRR'>Karl Müller <option value='Peter Jones|52219|GNLVAPMV'>Peter Jones <option value='Sue Brown|18215|PXAPGWKY'>Sue Brown <option value='Sally West|64403|NUIRTURT'>Sally West <option value='John Smith|42691|NGLOQEMM'>John Smith <option value='Frank Busse|37505|MQJMOWOR'>Frank Busse </select> </td> </tr> <tr> <td>Password:</td> <td><input type=password size=10 maxlength=8 name=pass></td> </tr> <tr> <td colspan=2 align=center> <input type=button value="Login" onclick="check(this.form)"> </td> </tr> </table> </form> <SCRIPT LANGUAGE="JavaScript"> <!-- Begin var params=new Array(4); var alpha="ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHI"; function check(form) { which=form.memlist.selectedIndex; choice = form.memlist.options[which].value+"|"; if (choice=="x|") { alert("Please Select Your Name From The List"); return; } p=0; for (i=0;i<3;i++) { a=choice.indexOf("|",p); params[i]=choice.substring(a,p); p=a+1; } h1=makehash(form.pass.value,3); h2=makehash(form.pass.value,10)+" "; if (h1!=params[1]) { alert("Incorrect Password!"); return; }; var page=""; for (var i=0;i<8;i++) { letter=params[2].substring(i,i+1) ul=letter.toUpperCase(); a=alpha.indexOf(ul,0); a-=(h2.substring(i,i+1)*1); if (a<0) a+=26; page+=alpha.substring(a,a+1); }; top.location=page.toLowerCase()+".html"; } function makehash(pw,mult) { pass=pw.toUpperCase(); hash=0; for (i=0;i<8;i++) { letter=pass.substring(i,i+1); c=alpha.indexOf(letter,0)+1; hash=hash*mult+c; } return(hash); } // End --> </script>
Auf den ersten Blick ist es natürlich erfreulich, daß das
mod 27
nicht enthalten ist und wir
"ein paar" mehr Möglichkeiten testen müss(t)en. Leider gibt
es noch genug andere Mängel, die ich hier kurz skizzieren will.
Schwächen
Um das Paßwort und die "versteckte" Seite zu finden, haben wir als ersten Anhaltspunkt den Hash-Wert des jeweiligen Paßwortes. Die erste Idee wäre natürlich, alle Zeichenketten auszurechnen, die diesen Hash-Wert besitzen. Normalerweise ist das allerdings keine gute Idee, da die Größe des Suchraumes und die Nichtstetigkeit der Hash-Funktion den Rechner für eine unbestimmte längere Zeit auslasten würden.
Bei SiteProtector war es trivial, da nur 27 verschiedene Werte
zurückgegeben wurden. Diesmal ist es trivial + 0.1
, da der
Autor von sich aus den Suchraum extrem einschränkt. Wir müssen keine
Zeichenketten verschiedener Länge prüfen, unser Zeichenvorrat ist auf
die Großbuchstaben A-Z eingeschränkt und die Hash-Funktion ist auch diesmal
unbrauchbar.
Die Hash-Funktion
Unser Kennwort habe die Form "ABCDEFGH". Jeder Buchstabe steht für seine Position im Alphabet, so daß:
hash("ABCDEFGH") = ((((((3*A + B)3 + C)3 + D)3 + E)3 + F)3 + G)3 + H = ((((((3*1 + 2)3 + 3)3 + 4)3 + 5)3 + 6)3 + 7)3 + 8 = 4916
Wie man jetzt leicht erkennen kann, werden die Zeichenpositionen unterschiedlich stark gewichtet. Das können wir ausnutzen, um dem Suchbaum einen schicken Winterschnitt zu verpassen.
Für den Hash-Wert 42691 nähern wir uns beidseitig an:
1. Stelle Kennwort Hash -------- ----- AZZZZZZZ 30605 BZZZZZZZ 32792 CZZZZZZZ 34979 DZZZZZZZ 37166 EZZZZZZZ 39353 FZZZZZZZ 41540 GZZZZZZZ 43727 * [...] SAAAAAAA 42646 * TAAAAAAA 44833 UAAAAAAA 47020 VAAAAAAA 49207 WAAAAAAA 51394 XAAAAAAA 53581 YAAAAAAA 55768 ZAAAAAAA 57955 => G-S 2. Stelle (1. Stelle = G) Kennwort Hash -------- ----- GAZZZZZZ 25502 GBZZZZZZ 26231 GCZZZZZZ 26960 GDZZZZZZ 27689 GEZZZZZZ 28418 GFZZZZZZ 29147 GGZZZZZZ 29876 GHZZZZZZ 30605 GIZZZZZZ 31334 GJZZZZZZ 32063 GKZZZZZZ 32792 GLZZZZZZ 33521 GMZZZZZZ 34250 GNZZZZZZ 34979 GOZZZZZZ 35708 GPZZZZZZ 36437 GQZZZZZZ 37166 GRZZZZZZ 37895 GSZZZZZZ 38624 GTZZZZZZ 39353 GUZZZZZZ 40082 GVZZZZZZ 40811 GWZZZZZZ 41540 GXZZZZZZ 42269 GYZZZZZZ 42998 * GZAAAAAA 34627 * => Y-Z ...
Das wiederholen wir mit allen acht Stellen. An der letzten Stelle können wir
noch berücksichtigen, daß hash(_) - 'achte Stelle'
durch drei teilbar
sein muß. Unser Baum ist jetzt dermaßen beschnitten, daß eine Berechnung der
möglichen Kennwörter selbst auf älteren Rechnern nur wenige Sekunden benötigt.
Vorausgesetzt, man implementiert den Algorithmus etwas effizienter (zB. ohne
die ständige Umwandlung Zeichen ⇔ Zahl) und benutzt kein JavaScript.
Eine mögliche Ausgabe für den Hashwert 42691 könnte folgendermaßen aussehen:
Kennwort hash_10 Netzseite -------- --------- --------- GYYZXYZY 97786785 EZEGKXEH GYYZXZWY 97786855 EZEGKWHH GYYZXZXV 97786862 EZEGKWGK [...] SAAABBBG 191112227 MXKNPCKK SAAABBCD 191112234 MXKNPCKJ SAAABBDA 191112241 MXKNPCKI SAAABCAA 191112311 MXKNPCJL
Wörterbuchangriff mit bösen Hackertools
In unseren Beispielfällen umfaßt eine solche Ausgabe zwischen zwei und vier Millionen Zeilen. Ein Ausprobieren aller Seiten würde vlt. zu lange dauern und evtl. doch dem Seitenbetreiber auffallen. ;) Gemeinerweise können wir diesmal auch nicht das ".htm" heraussuchen, da die Endung nicht mitkodiert wird.
Wir gehen daher den skrupellosen Terroristenweg und benutzen fiese Hacker-Tools: aspell, comm und grep.
Da Seitenbetreiber, die solche Software einsetzen, ein eher unterentwickeltes Verständnis von Sicherheit haben, dürfte die Wahrscheinlichkeit, daß das Paßwort oder der Seitenname "lesbar" ist, relativ hoch sein. Zum Glück ist es in unserem Beispiel nicht anders und wir versuchen unser Glück erst einmal mit aspell.
Dazu verwenden wir die Liste der möglichen Kennwörter und Netzseiten,
wenden darauf aspell -l --ignore-case
an und lassen uns damit die
Liste der Wörter ausgeben, die aspell nicht kennt. Ein anschließendes
comm -3
zeigt uns die Differenzen:
Hash comm -3 Auszug der ausf. Ausgabe ----- -------- --------------------------- 17979 - 18215 NOWHERES ASDFVCXZ 29482566 NOWHERES 37505 - 42691 PASSWORD PASSWORD 163114684 MAINPAGE 52219 FEISTIER RLMGFPXA 193377841 FEISTIER 64403 -
Ein Seitenname (NOWHERES), ein Paßwort (PASSWORD) und ein Fehlergebnis (FEISTIER) – für eine Rechtschreibprüfung schon gar nicht schlecht.
Für die anderen Seiten nutzen wir eines der Wörterbücher aus den Weiten des Netzes. Ich habe mich für das English-Deutsch-Wörterbuch von dict.cc entschieden1, es um den deutschen Teil erleichtert, alle Klammern entfernt, Leerzeichen umgebrochen, die Zeichenketten vorerst auf fünf bis acht Zeichen eingegrenzt und das Ganze nochmal durch uniq und sort geschickt.
Um aus der "verschlüsselten Seite" den Seitennamen zu gewinnen, wird aus dem Kennwort ein Hash mit dem Faktor 10 berechnet (wie oben, nur statt 3 eben 10). Dieser Wert wird dann vom "verschlüsselten Text" abgezogen:
crypt : NGLOQEMM -------- hash_10 : 163114684 (letzte Stelle wird ignoriert) ======== site : MAINPAGE
Wir versuchen uns am Hashwert 52219 mit der Seite GNLVAPMV. Zuerst führen wir
mit grep -f
einen Angriff auf die Seitennamen aus. Diese Liste nutzen
wir, um die betreffenden (Paßwort,Seite)-Paare zu finden und schließlich noch
einen Angriff auf die Paßwörter durchzuführen:
Ausgangsliste: Kennwort hash_10 Netzseite -------- --------- --------- KZYZZYYY 138788775 FKDOSHFO KZYZZYZV 138788782 FKDOSHFN KZYZZZVY 138788845 FKDOSHER KZYZZZWV 138788852 FKDOSHEQ ... (fast 4Mio. Einträge) Netzseiten mögliche Kennwörter Netzseiten nach Angriff Kennwörter nach Angriff ---------- ------------ ---------- ------------ EKJUYOKS EKKTALKS LXYVPRBV MWWHURLS EKJUYOKT EKKTANGO LXYVPRCS MXULAPIS EKJUYOKU EKKTULIP LXYWMQLA OXBHURLS EKJUYOLP EKKTUNER LXYWMRBV POWDERVP ... ... ... ... (~3Mio) (~9k) (~11k) (24) Endliste: Kennwort hash_10 Netzseite -------- --------- --------- MWWHURLS 155402939 FIGRANDS MXULAPIS 156222709 FIFTYNFV OXBHURLS 174302939 FGHSANDS POWDERVP 177347036 FGESWIMS PPOSERZD 177697064 FGEPRIMP PTGBURLS 180742939 FFLOWNDS PUBHURLS 181302939 FFKSANDS QQCRISIS 187491009 FFERROMV QQCRIVED 187491254 FFERROKQ QRBHURLS 188302939 FFDSANDS ROBHURLS 195302939 FEGSANDS SLBHURLS 202302939 ENJSANDS TELLYYLJ 206347630 ENFSWIGS TELLYYMG 206347637 ENFSWIGS TELLYZLA 206347721 ENFSWIFT TGHHURLS 207902939 ENEMANDS THEIRPUJ 208609820 ENDPAGET THEIRPVG 208609827 ENDPAGET THEIRPWD 208609834 ENDPAGES THEIRPXA 208609841 ENDPAGER THGBURLS 208742939 ENDOWNDS TIBHURLS 209302939 ENCSANDS UFBHURLS 216302939 EMFSANDS VCBHURLS 223302939 ELISANDS
Die Beispielwerte sind natürlich sehr einfach, die Idee sollte aber erkennbar sein.
Ein weiteres Problem stellt die Verwendung mehrerer Kennwörter für dieselbe Seite dar, da man aus den resultierenden Seitenlisten des Angriffs nur die Schnittmenge bilden muß:
ohne Wörterbuchangriff: +-----+ |17979|-------------------------+ +-----+ | c (~2,1Mio) | o | m +---+ +-----+ +--- m ---| / | |37505|------ c | +---+ +-----+ | o | -1 (2082) (~3,2Mio) | m +-----+ | -2 +--- m ---| / |---+ +-----+ | +-----+ |64403|------ -1 (80449) +-----+ -2 (~2,7Mio) mit Wörterbuchangriff: W ö +-----+ r +-----+ |17979|--- t ---|17979|--------------------+ +-----+ e +-----+ | c (~2,1Mio) r (~3,5k) | o b | m +---+ +-----+ u +-----+ +--- m ---| / | |37505|--- c ---|37505|--- c | +---+ +-----+ h +-----+ | o | -1 (18!) (~3,2Mio) a (~4,5k) | m +---+ | -2 n +--- m ---| / |---+ +-----+ g +-----+ | +---+ |64403|--- r ---|64403|--- -1 (389) +-----+ i +-----+ -2 (~2,7Mio) f (~9,5k) fWir erhalten die Ergebnislisten:
17979: Kennwort hash_10 Netzseite -------- --------- --------- ASDDTBXO 29460455 LOGINUMM ASDDTBYL 29460462 LOGINULP ASDDTCVL 29460532 LOGINTOP ASDDTCYC 29460553 LOGINTMO ASDDTDOX 29460574 LOGINTKN ASDDTDRO 29460595 LOGINTIM ASDDTELX 29460644 LOGINSNN ASDDTEOO 29460665 LOGINSLM ASDDTEPL 29460672 LOGINSKP ASDDTESC 29460693 LOGINSIO ASDDTFLO 29460735 LOGINROM ASDDTFML 29460742 LOGINRNP ASDDTFPC 29460763 LOGINRLO ASDDTGFX 29460784 LOGINRJN ASDDTGMC 29460833 LOGINQOO ASDDTHCX 29460854 LOGINQMN ASDDTHFO 29460875 LOGINQKM ASDDTHGL 29460882 LOGINQJP 37505: Kennwort hash_10 Netzseite -------- --------- --------- KLMITTWW 123412253 LOGINUMM KLMITUTW 123412323 LOGINULP KLMIURTW 123413023 LOGINTOP KLMIUUKW 123413233 LOGINTMO KLMIUXBW 123413443 LOGINTKN KLMIVNWW 123413653 LOGINTIM KLMIVUBW 123414143 LOGINSNN KLMIWKWW 123414353 LOGINSLM KLMIWLTW 123414423 LOGINSKP KLMIWOKW 123414633 LOGINSIO KLMIXHWW 123415053 LOGINROM KLMIXITW 123415123 LOGINRNP KLMIXLKW 123415333 LOGINRLO KLMIXOBW 123415543 LOGINRJN KLMIYIKW 123416033 LOGINQOO KLMIYLBW 123416243 LOGINQMN KLMIZBWW 123416453 LOGINQKM KLMIZCTW 123416523 LOGINQJP 64403: Kennwort hash_10 Netzseite -------- --------- --------- ZAQXRYGE 262960575 LOGINUMM ZAQXRZDE 262960645 LOGINULP ZAQXSWDE 262961345 LOGINTOP ZAQXTMYE 262961555 LOGINTMO ZAQXTPPE 262961765 LOGINTKN ZAQXTSGE 262961975 LOGINTIM ZAQXUMPE 262962465 LOGINSNN ZAQXUPGE 262962675 LOGINSLM ZAQXUQDE 262962745 LOGINSKP ZAQXVGYE 262962955 LOGINSIO ZAQXVMGE 262963375 LOGINROM ZAQXVNDE 262963445 LOGINRNP ZAQXWDYE 262963655 LOGINRLO ZAQXWGPE 262963865 LOGINRJN ZAQXXAYE 262964355 LOGINQOO ZAQXXDPE 262964565 LOGINQMN ZAQXXGGE 262964775 LOGINQKM ZAQXXHDE 262964845 LOGINQJP
Die Probleme mit den schwachen Kennwörtern gibt es auch in Dions Variante, so daß zB. "jiTJjKAa" lediglich zum Inkrementieren der einzelnen Buchstaben führt:
34474, HFIFJNFT: Kennwort hash_10 Netzseite -------- --------- --------- JITJJKAA 111111111 GEHEIMES
Auch das Berechnen der Kennwörter bei bekannten Hash-Werten ist wieder möglich. Die Auswahl ist nur kleiner. ;)
Minischlußbetrachtung
Mit den zahlreichen genannten Schwächen verhält sich der Zugangsschutz wie eine Personal Firewall: nach der Installation ist das System unsicherer als vorher. Ich kann nur abraten, solche Skripte einzusetzen und hoffe, daß zumindest einige Leser vorsichtiger und kreativer im Umgang mit fremdem (oder eigenem) Code und der Wahl der Kennwörter werden.
An die mitlesenden Politiker
Die eingesetzten Werkzeuge waren:
- aspell
- comm
- firefox
- gcc
- grep
- nedit
- python
- sort
- split
- uniq
- vim
Bitte alles verbieten. Leider sind diese Werkzeuge auf nahezu jedem Linuxsystem verfügbar. Ein Verbot von Linux ist da wohl leider unumgänglich. Schade.
- Sollte man keinen Erfolg haben, kann man immer noch auf spezielle Kennwortwörterbücher zurückgreifen.