Noch nie hat eine Religion, weder mittelbar noch unmittelbar, weder als Dogma noch als Gleichnis, eine Wahrheit enthalten.

Willkommen auf meiner Netzseite

Wer auf diesen Seiten tiefschürfende Erkenntnisse, Weltverbesserungsvorschläge oder doch wieder nur Bilder siebenachtelnackter Kopulationszielpersonen sucht, der wird leider enttäuscht werden. Meine Seite ist momentan nur dazu da, mir einen Teil meiner Verweisliste virtuell hinterherzutragen. Nein, nicht wie Mutti früher die Stullenbüchse in die Schule zur Freude der gesamten Klasse, sondern viel cooler (Das sagte man früher so.).
Sollten Sie zufällig ähnliche Männerhobbys sinnvolle Interessen haben, sich zum Beispiel für Geschichte, Rechentechnik, Photographie, lange Sätze oder alte Kraftfahrzeuge interessieren, und hier den ein oder anderen Verweis finden, der dazu führt, daß Sie den Abend vor dem Rechner und nicht mit Ihrer besseren Hälfte verbringen, so tut es mir Leid. Natürlich war das alles überhaupt nicht so gewollt.

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)
           f
Wir 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.


  1. Sollte man keinen Erfolg haben, kann man immer noch auf spezielle Kennwortwörterbücher zurückgreifen.
Frank Busse Dresden 2005