Login mit C



  • Folgendes Programm habe ich geschrieben. Da ich aber erst seit rund einer Woche C lerne, weiß ich nicht, ob es etwas zu verbessern gibt.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(){
    
        char user[] = "USER1";
        char pass[] = "PASS1";
        char password[20];
        char username[20];
    
        printf("Please enter your username: \n");
        scanf("%s", username);
        printf("Wie ist dein Passwort?\n");
        scanf("%s", password);
    
        if (memcmp(user, username, sizeof(user)) == 0 && memcmp(pass, password, sizeof(pass))== 0) {
            printf("Hallo USER1.");
        } else {
            printf("Du bist nicht USER1.");
        }
    
        return 0;
    
    }
    

    Passt das alles so mit den Arrays und dem Rest? Wie sieht es mit der Funktion memcmp aus? Ich hab keine Alternative dafür gefunden, um die beiden Arrays abzugleichen.



  • Für memcmp kannste strcmp nehmen.
    Ansonsten ist es keine gute Idee, das Passwort im Klartext im Code zu haben.


  • Mod

    Wundervoll. Da gebe ich doch einfach mal mehr als 20 Zeichen ein und kann damit selber das Passwort setzen!

    Selbst wenn dieser Exploit nicht funktionieren würde, würde dein System einen falschen Nutzernamen und Passwort akzeptieren, sofern die ersten paar Buchstaben jeweils richtig sind, da du das memcmp nur bis zum Ende des richtigen Nutzernamen und Passworts durchführst. Wenn der Nutzer etwas zu langes eingibt, ignorierst du diesen Unterschied*.

    Daher dringende Verbesserungen: Bei der Eingabe Längenbegrenzungen setzen, guck in der Dokumentation von scanf nach, wie das geht. strcmp böte sich viel eher an als memcmp.

    Allgemeiner Designfehler: Das richtige Passwort darf eigentlich nirgendwo gespeichert sein. Du speicherst einen (gesalzenen) Hash des richtigen Passworts und vergleichst dann den Hash des eingegebenen Passworts mit dem Hash des richtigen Passworts. So ist es kein Sicherheitsproblem, wenn die Passwortdatenbank kompromittiert wird, da man von dem Hash nicht auf das richtige Passwort schließen kann.
    Das korrekt umzusetzen ist nach einer Woche C vermutlich viel zu hoch, aber du kannst ja wenigstens das Prinzip üben, indem du als Hash beispielsweise die Quersumme des Passworts nimmst.

    *: Falls du dies nun ausprobierst, wirst du feststellen, dass es bei dir dann zufällig doch funktioniert. Das ist aber eher darauf zurück zu führen, dass sich hier zwei ungünstige Designentscheidungen gegenseitig aufheben: Das scanf mit %s wird bei Whitespaces die Eingabe teilen, das memcpy vergleicht aber auch das abschließende Nullzeichen des gespeicherten Passworts mit. Der böswillige Nutzer kann daher kein Nullzeichen in den Namen schmuggeln. Aber wenn du irgendwelche kleinen Änderungen vornimmst (und du solltest noch einiges ändern, siehe oben), werden die Umstände sich wahrscheinlich nicht mehr so glücklich ergänzen.



  • SeppJ schrieb:

    Wundervoll. Da gebe ich doch einfach mal mehr als 20 Zeichen ein

    Haha, Fieslinge unter sich. Das war auch meine erste, spontane Idee. 😉



  • In Zeile 18 solltest du den richtigen Namen (aus user) ausgeben. Dann geht das auch, wenn du den Namen mal änderst
    In Zeile 20 solltest du den keinen Namen ausgeben. Sonnst kennt der Hacker nach dem ersten Fehlversuch den Namen.



  • SeppJ schrieb:

    Daher dringende Verbesserungen: Bei der Eingabe Längenbegrenzungen setzen, guck in der Dokumentation von scanf nach, wie das geht. strcmp böte sich viel eher an als memcmp.

    Wenn nun beispielsweise die Passworteingabe mit Passwörtern aus einer Datenbank abgeglichen wird und ich die Passwortlänge auf 20 festsetze, der User hingegen nur ein Passwort von 15 Zeichen hat: besteht dann nicht dasselbe Problem, dass der User mehr Zeichen angeben kann und das Passwort als richtig angesehen wird?

    Ich habe nun mal strcmp anstelle von memcmp eingebaut und eine Zeichenbegrenzung eingebaut.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(){
    
        char user[] = "USER1";
        char pass[] = "PASS1";
        char password[20];
        char username[20];
    
        printf("Please enter your username: \n");
        scanf("%19s", username);
        printf("Wie ist dein Passwort?\n");
        scanf("%19s", password);
    
        if (strcmp(user, username) == 0 && strcmp(pass, password) == 0) {
            printf("Hallo %s.", user);
        } else {
            printf("Hast du dein Passwort vergessen?");
        }
    
        return 0;
    
    }
    

    Warum genau kann man das Passwort selbst festlegen, wenn zu viele Zeichen eingegeben werden und wie?



  • CrispyTurtleAlligator schrieb:

    Warum genau kann man das Passwort selbst festlegen, wenn zu viele Zeichen eingegeben werden und wie?

    Das liegt an der Anordnung der Variablen im Speicher.

    Lokale Variablen liegen i.A. auf dem Stack und der wächst von oben (hohe Adresse) nach unten.
    Neue Variablen liegen vor den Älteren.

    Bei wäre das dann

    username[20]        password[20]        pass[]user[]
    uuuuuuuuuuuuuuuuuuuuppppppppppppppppppppPASS1_USER1_
    

    u ist der Platz für username, p für password, die _ stehen für \0

    Wie du siehst, kannst du durch schreiben über die Arraygrenzen hinaus, ganz einfach die anderen Variablen ändern.



  • Ist es dann ausreichend, das Array für Passwort und Username auf 19 bei scanf zu begrenzen? Oder ist es vielleicht sogar sinnvoller fgets zu nutzen?



  • CrispyTurtleAlligator schrieb:

    Ist es dann ausreichend, das Array für Passwort und Username auf 19 bei scanf zu begrenzen?

    Ja, das reicht.

    Aber ...
    ... es ist sehr umständlich, wenn du mal die mögliche Länge der Eingaben verändern willst.

    CrispyTurtleAlligator schrieb:

    Oder ist es vielleicht sogar sinnvoller fgets zu nutzen?

    Das speichert das '\n' von der Entertaste mit ab.

    Zudem bleibt bei beiden Versionen der Rest von der Eingabe (die mehr als 19 Zeichen) noch im Eingabestrom stehen.
    Das gibt dann Probleme bei der nächsten Zeile.
    Das kann man aber auch umgehen: https://www.c-plusplus.net/forum/p1146014#1146014



  • Die Begrenzung auf 19 ist prinzipiell richtig, wenn auch hier nicht ausreichend.
    Ob scanf oder fgets ist Geschmackssache, wenn man den nötigen Überblick hat, was die Vor-/Nachteile sind.

    scanf("%19[^\n]",username); while( getchar()!='\n' );
    scanf("%19[^\n]",password); while( getchar()!='\n' );
    

    sollte für dich erstmal reichen



  • DirkB schrieb:

    CrispyTurtleAlligator schrieb:

    Ist es dann ausreichend, das Array für Passwort und Username auf 19 bei scanf zu begrenzen?

    Ja, das reicht.

    Aber ...
    ... es ist sehr umständlich, wenn du mal die mögliche Länge der Eingaben verändern willst.

    CrispyTurtleAlligator schrieb:

    Oder ist es vielleicht sogar sinnvoller fgets zu nutzen?

    Das speichert das '\n' von der Entertaste mit ab.

    Zudem bleibt bei beiden Versionen der Rest von der Eingabe (die mehr als 19 Zeichen) noch im Eingabestrom stehen.
    Das gibt dann Probleme bei der nächsten Zeile.
    Das kann man aber auch umgehen: https://www.c-plusplus.net/forum/p1146014#1146014

    Ist dies dann hiermit getan, bzw. reicht das aus um den Rest der Eingabe zu entfernen?

    scanf("%19[^\n]",username); while( getchar()!='\n' ); 
    scanf("%19[^\n]",password); while( getchar()!='\n' );
    

  • Mod

    Ja, das sollte alle realistischen Fälle abdecken.



  • Ist das identisch zu dem Folgenden?

    while( getchar()!='\n' ) {
    scanf("%19[^\n]",username); 
    } 
    while( getchar()!='\n' ) {
    scanf("%19[^\n]",password);
    }
    

    @SeppJ
    Was wären denn unrealistische Fälle?


  • Mod

    CrispyTurtleAlligator schrieb:

    Ist das identisch zu dem Folgenden?

    😕 Nein, natürlich nicht, das ist ganz was anderes. Verstehst du nicht, was das while(bedingung); macht? Das ist einfach nur eine Schleife mit einem leeren Körper. Da hier bereits die Prüfung der Schleifenbedingung den gewünschten Effekt hat.

    @SeppJ
    Was wären denn unrealistische Fälle?

    Die ganze Idee, Passwörter einfach von der Standardeingabe zu lesen ist halt nicht besonders gut. Die Standardeingabe ist nicht zwangsläufig an eine Tastatur gebunden. Und wenn sie es nicht ist, dann funktioniert dein interaktives Eingabekonzept nicht mehr so wirklich. Wenn du interaktive Nutzerschnittstellen möchtest, dann brauchst du letztlich eine GUI oder TUI oder etwas vergleichbares. Aber das würde an dieser Stelle viel zu weit führen. Für ein paar Experimente ist das was du hier machst bei weitem ausreichend.



  • SeppJ schrieb:

    CrispyTurtleAlligator schrieb:

    Ist das identisch zu dem Folgenden?

    😕 Nein, natürlich nicht, das ist ganz was anderes. Verstehst du nicht, was das while(bedingung); macht? Das ist einfach nur eine Schleife mit einem leeren Körper. Da hier bereits die Prüfung der Schleifenbedingung den gewünschten Effekt hat.

    @SeppJ
    Was wären denn unrealistische Fälle?

    Die ganze Idee, Passwörter einfach von der Standardeingabe zu lesen ist halt nicht besonders gut. Die Standardeingabe ist nicht zwangsläufig an eine Tastatur gebunden. Und wenn sie es nicht ist, dann funktioniert dein interaktives Eingabekonzept nicht mehr so wirklich. Wenn du interaktive Nutzerschnittstellen möchtest, dann brauchst du letztlich eine GUI oder TUI oder etwas vergleichbares. Aber das würde an dieser Stelle viel zu weit führen. Für ein paar Experimente ist das was du hier machst bei weitem ausreichend.

    Ich bin nur ein wenig überrascht von der Stelle, an der die Schleife genutzt wird. So habe ich das nämlich bisher noch nirgends gesehen. 🙂

    Kannst du mir den Unterschied zwischen den beiden Codezeilen erklären?


  • Mod

    CrispyTurtleAlligator schrieb:

    Kannst du mir den Unterschied zwischen den beiden Codezeilen erklären?

    Habe ich doch.

    In C ist das Semikolon das Trennzeichen für Anweisungen. Zeilenumbrüche dienen nur der Lesbarkeit für Menschen. Wenn es dir besser passt, kannst du Wutz Code auch so schreiben:

    scanf("%19[^\n]",username); 
    while( getchar()!='\n' );
    scanf("%19[^\n]",password); 
    while( getchar()!='\n' );
    

    Nun verständlicher? Beachte meine Einrückung, beziehungsweise das Ausbleiben derselben. Alle diese Anweisungen sind auf der gleichen logischen Ebene. Der Schleifenkörper der while-Schleifen ist leer, die scanfs haben überhaupt nichts mit den whiles zu tun. Und das ist selbstverständlich ganz was anderes als dein Code, bei dem du scanfs innerhalb des Körpers von while-Schleifen hast.

    Oder noch einmal deutlicher:

    scanf("%19[^\n]",username); 
    while( getchar()!='\n' ) {
    }
    scanf("%19[^\n]",password); 
    while( getchar()!='\n' ) {
    }
    


  • Wobei für mich aber noch die Frage bleibt: Warum wird dann der Rest der Eingabe dadurch entfernt?



  • scanf hinterlässt immer das ungelesene '\n' in stdin und 'davor' die evtl. nicht gelesenen Zeichen, wenn mehr als 19 Zeichen eingegeben wurden.
    Mit while wird getchar() solange aufgerufen und damit jeweils genau 1 beliebiges Zeichen abgeräumt, bis '\n' vorkommt und somit alle Zeichen aus stdin 'entfernt' wurden, damit diese Restzeichen das folgende scanf nicht stören.



  • Pwned:

    Please enter your username:
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    Wie ist dein Passwort?
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    Hallo USER1.

    Shit happens. 😉

    (Tested and verified - VS 2005)



  • Bleibe in der Schleife, solange das (neu) gelesene Zeichen ungleich dem Zeilenende ist.

    Es wird solange gelesen, solange das ZEichen von der Entertaste nicht gefunden wurde.


Anmelden zum Antworten