Allgemeine Codesicherheitsprobleme untersuchen
Das Verständnis allgemeiner Sicherheitsrisiken ist wichtig, um Codesicherheitsprobleme effektiv zu identifizieren und zu beheben. Diese Lektion befasst sich mit häufig auftretenden Sicherheitsproblemen im Code, deren Auswirkungen und warum es wichtig ist, sie umgehend anzugehen, um die Anwendungssicherheit zu gewährleisten.
Warum konzentrieren Sie sich auf Sicherheitsprobleme?
Sicherheitsrisiken stellen eine der wichtigsten Kategorien von Softwarefehlern dar. Eine einzelne Sicherheitsanfälligkeit kann zu folgendem Ergebnis führen:
- Datenschutzverletzungen: Gefährdung sensibler Kunden- oder Geschäftsdaten.
- Finanzielle Verluste: Direkte Kosten aus Verstößen, behördlichen Geldbußen und Sanierungsaufwendungen.
- Reputationsschaden: Verlust des Kundenvertrauens und geschäftlicher Glaubwürdigkeit.
- Betriebsunterbrechung: Systemkompromittierungen können Geschäftsvorgänge anhalten.
Funktionale Fehler können einem Entwickler peinlich sein, aber Sicherheitsfehler können schwerwiegende Folgen für eine Organisation und Benutzer haben. Jeder Entwickler muss sicherheitsbewusst sein, unabhängig von seiner Rolle oder Spezialisierung.
Open Web Application Security Project (OWASP)
Das Open Web Application Security Project (OWASP) ist eine gemeinnützige Organisation, die sich auf die Verbesserung der Softwaresicherheit konzentriert. OWASP unterhält die allgemein anerkannte "OWASP Top 10" – eine regelmäßig aktualisierte Liste der kritischsten Sicherheitsrisiken von Webanwendungen basierend auf Daten aus Sicherheitsorganisationen weltweit.
Die OWASP Top 10 dient als Sicherheitsgrundlinie für Entwickler und Organisationen, um zu priorisieren, welche Sicherheitsrisiken zuerst behoben werden sollen. Die Rangfolgen werden im Laufe der Zeit verschoben, während sich Angriffsmuster entwickeln. Beispiel:
- 2017 OWASP Top 10: Einschleusungsfehler standen an 1. Stelle.
- 2021 OWASP Top 10: Injection wurde zu #3 verschoben, da neue Bedrohungen wie unsichere Zugriffssteuerung entstanden sind.
Die OWASP Top 10 spiegelt reale Angriffsdaten wider, nicht theoretische Bedenken. Coderisiken wie SQL-Einfügung und schwache Verschlüsselung zählen zu den wichtigsten Sicherheitsbedenken der Branche.
Injektionsangriffe
Einfügungsangriffe treten auf, wenn nicht vertrauenswürdige Daten als Teil eines Befehls oder einer Abfrage an einen Interpreter gesendet werden. Die feindlichen Daten des Angreifers trickst den Dolmetscher, unbeabsichtigte Befehle auszuführen oder auf nicht autorisierte Daten zuzugreifen.
Einschleusung von SQL-Befehlen
DIE SQL-Injektion ist einer der gefährlichsten und häufigsten Injektionsangriffe. Es tritt auf, wenn eine Anwendung nicht vertrauenswürdige Eingaben direkt in SQL-Abfragen ohne ordnungsgemäße Überprüfung oder Parametrisierung integriert.
Betrachten Sie das folgende Codebeispiel:
// DANGEROUS: Concatenating user input directly into SQL
string query = "SELECT * FROM Users WHERE Username = '" + userInput + "' AND Password = '" + passwordInput + "'";
SqlCommand command = new SqlCommand(query, connection);
SqlDataReader reader = command.ExecuteReader();
Ein Angreifer könnte als Benutzernameneingabe eingeben ' OR '1'='1 und die Abfrage in Folgendes umwandeln:
SELECT * FROM Users WHERE Username = '' OR '1'='1' AND Password = ''
Da '1'='1' diese Abfrage immer wahr ist, gibt diese Abfrage alle Benutzer zurück, wobei die Authentifizierung vollständig umgangen wird.
Reale Effekte
SQL-Injection-Angriffe haben zahlreiche hochkarätige Sicherheitsverletzungen verursacht. Angreifer können:
- Umgehen von Authentifizierungsmechanismen.
- Extrahieren Sie ganze Datenbanken, die vertrauliche Informationen enthalten.
- Ändern oder Löschen von Daten.
- Führen Sie administrative Vorgänge in der Datenbank aus.
Sichere Implementierung
Die sichere Möglichkeit zum Verarbeiten von SQL-Abfragen ist die Verwendung parametrisierter Abfragen (auch als vorbereitete Anweisungen bezeichnet).
Beispiel:
// SECURE: Using parameterized queries
string query = "SELECT * FROM Users WHERE Username = @username AND Password = @password";
SqlCommand command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("@username", userInput);
command.Parameters.AddWithValue("@password", passwordInput);
SqlDataReader reader = command.ExecuteReader();
Parametrisierte Abfragen trennen Code von Daten. Die Datenbank behandelt Parameterwerte nur als Daten, niemals als ausführbarer SQL-Code, um Einfügungsangriffe zu verhindern.
Andere Injektionstypen
Die SQL-Einfügung ist nur eine Form des Einfügungsangriffs, und Entwickler müssen andere Einfügungsrisiken kennen, die die Anwendungssicherheit gefährden können.
Während die SQL-Einfügung am häufigsten ist, gibt es andere Einfügungsrisiken:
- Befehlseinfügung: Einfügen von Systembefehlen in Anwendungseingaben, die Shellbefehle ausführen.
- Lightweight Directory Access Protocol (LDAP)-Einfügung: Bearbeiten von LDAP-Abfragen für den Zugriff auf nicht autorisierte Verzeichnisinformationen.
- NoSQL-Injection: Ausnutzung von NoSQL-Datenbanken durch schädliche Abfragen.
- XML-Einfügung: Einfügen bösartiger XML-Inhalte zum Zugreifen auf oder Ändern von Daten.
Das universelle Muster: Wenn Sie nicht vertrauenswürdige Eingaben in einen Befehl oder eine Abfrage einfügen, die interpretiert wird, riskieren Sie eine Injection. Das Lösungsmuster ist immer ähnlich: Löschen, Überprüfen oder Parametrisieren, um Code von Daten zu trennen.
Schwache Verschlüsselung vertraulicher Daten
Das Speichern oder Übertragen sensibler Daten ohne ordnungsgemäße Verschlüsselung setzt sie einem unbefugten Zugriff aus. Diese Kategorie umfasst sowohl unzureichende Verschlüsselungsmethoden als auch vollständige Verschlüsselungsmangel.
Unsicherer Kennwortspeicher
Kennwörter erfordern besonderen Schutz, da sie für die meisten Anwendungen als primärer Authentifizierungsmechanismus dienen.
Das falsche Speichern von Kennwörtern ist eine wichtige Sicherheitsanfälligkeit.
Nur-Text-Speicher (niemals akzeptabel)
// DANGEROUS: Storing passwords in plaintext
string password = userInput;
database.SavePassword(username, password);
Wenn die Datenbank kompromittiert ist, werden alle Benutzerwörter sofort verfügbar gemacht.
Schwaches Hashing (unzureichend)
// INSUFFICIENT: Using MD5 or SHA1 without salt
using (MD5 md5 = MD5.Create())
{
byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(password));
string hashedPassword = Convert.ToBase64String(hash);
}
MD5 und SHA1 sind kryptografisch beschädigt. Moderne GPUs können Milliarden von Kennwortkombinationen pro Sekunde anhand dieser schnellen Hashes testen. Darüber hinaus können Angreifer, ohne Salz, vorkompilierte Regenbogentabellen verwenden, um Kennwörter sofort zu knacken.
Sicheres Hashing (empfohlen)
// SECURE: Using bcrypt with automatic salt generation
string hashedPassword = BCrypt.Net.BCrypt.HashPassword(password);
// Later, for verification:
bool isValid = BCrypt.Net.BCrypt.Verify(userInput, storedHash);
Für sicheres Kennworthashing ist Folgendes erforderlich:
- Salt: Zufällige Daten, die vor dem Hashing zu Kennwörtern hinzugefügt wurden, verhindern Regenbogentabellenangriffe.
- Langsamer Algorithmus: Funktionen wie bcrypt, scrypt oder Argon2 sind rechenintensiv und begrenzen Brute-Force-Versuche auf Hunderte oder Tausende pro Sekunde anstelle von Milliarden.
Verschlüsselung für ruhende Daten
Über die Kennwortsicherheit hinaus benötigen alle vertraulichen Daten, die auf dem Datenträger oder in Datenbanken gespeichert sind, Schutz durch ordnungsgemäße Verschlüsselung.
Vertrauliche Daten, die ohne Verschlüsselung gespeichert werden, sind anfällig, wenn Speichermedien kompromittiert werden.
Anfälliges Szenario
// VULNERABLE: Writing sensitive data in plaintext
File.WriteAllText("customer_data.txt", sensitiveInformation);
Wenn ein Laptop, der diese Datei enthält, gestohlen wird oder ein Angreifer Zugriff auf das Dateisystem erhält, sind die Daten sofort lesbar.
Sicherer Ansatz
// SECURE: Encrypting data before storage
using (Aes aes = Aes.Create())
{
aes.Key = GetEncryptionKey(); // Securely managed key
aes.GenerateIV();
using (FileStream fileStream = new FileStream("customer_data.enc", FileMode.Create))
{
fileStream.Write(aes.IV, 0, aes.IV.Length);
using (CryptoStream cryptoStream = new CryptoStream(fileStream, aes.CreateEncryptor(), CryptoStreamMode.Write))
using (StreamWriter writer = new StreamWriter(cryptoStream))
{
writer.Write(sensitiveInformation);
}
}
}
Die richtige Verschlüsselung bietet eine Verteidigungsschicht, auch wenn der Speicher kompromittiert ist, vorausgesetzt, Verschlüsselungsschlüssel werden separat ordnungsgemäß verwaltet.
Protokollierung und Fehlerbehandlung
Falsche Protokollierung und Fehlerbehandlung können versehentlich vertrauliche Informationen oder Systemdetails verfügbar machen, die Angreifern helfen.
Protokollierung vertraulicher Daten
Während die Protokollierung für das Debuggen und überwachen wichtig ist, kann es zu einer Sicherheitslücke werden, wenn vertrauliche Informationen erfasst werden.
Anwendungen dürfen niemals vertrauliche Informationen im Nur-Text-Format protokollieren.
Gefährliche Protokollierungsmethoden
// DANGEROUS: Logging sensitive information
logger.LogInformation($"User {username} logged in with password: {password}");
logger.LogInformation($"Credit card processed: {cardNumber}");
logger.LogInformation($"API Key: {apiKey}");
Dieser Code macht vertrauliche Daten in Protokollen verfügbar, die möglicherweise für nicht autorisierte Benutzer zugänglich sind oder über Protokollverwaltungssysteme geleakt werden könnten.
Sichere Protokollierungsmethoden
// SECURE: Logging without sensitive data
logger.LogInformation($"User {username} logged in successfully");
logger.LogInformation($"Payment processed for order {orderId}");
logger.LogInformation($"API call authenticated successfully");
Bewährte Methoden
- Protokollieren Sie niemals Kennwörter, Authentifizierungstoken oder API-Schlüssel.
- Maskieren oder redigieren Sie vertrauliche Informationen wie Kreditkartennummern oder Sozialversicherungsnummern.
- Protokollieren sie Ereignisse und Ergebnisse, keine vertraulichen Datenwerte.
Übermäßige Offenlegung von Fehlerinformationen
Fehlermeldungen dienen einem wichtigen Debuggingzweck, müssen jedoch sorgfältig gestaltet werden, um zu vermeiden, dass interne Systeme für potenzielle Angreifer offengelegt werden.
Detaillierte Fehlermeldungen können Systemarchitektur, Dateipfade, Datenbankschemas und andere informationen offenlegen, die für Angreifer nützlich sind.
Problematische Fehlerbehandlung
// PROBLEMATIC: Exposing detailed error information to users
catch (Exception ex)
{
return $"Error: {ex.Message}\nStack Trace: {ex.StackTrace}\nConnection String: {connectionString}";
}
Dadurch werden interne Systemdetails offengelegt, mit denen Angreifer komplexere Angriffe erstellen können.
Sichere Fehlerbehandlung
// SECURE: User-friendly messages with detailed internal logging
catch (Exception ex)
{
logger.LogError(ex, "Failed to process user request");
return "An error occurred while processing your request. Please try again or contact support.";
}
Benutzer erhalten benutzerfreundliche, minimale Fehlermeldungen, während Entwickler detaillierte Fehlerinformationen über sichere Protokolle erhalten.
Pfaddurchlaufangriff
Pfad-Traversal (auch als Verzeichnis-Traversal bezeichnet) tritt auf, wenn eine Anwendung vom Benutzer bereitgestellte Eingabe verwendet, um Dateipfade ohne ordnungsgemäße Überprüfung zu erstellen. Angreifer können spezielle Zeichensequenzen verwenden, um auf Dateien außerhalb des beabsichtigten Verzeichnisses zuzugreifen.
Berücksichtigen Sie den folgenden anfälligen Code:
// VULNERABLE: Using user input directly in file paths
string filename = Request.Query["file"];
string filePath = Path.Combine(@"C:\uploads\", filename);
string content = File.ReadAllText(filePath);
Ein Angreifer kann Eingaben bereitstellen, z. B. ../../../Windows/System32/config/SAM um auf vertrauliche Systemdateien zuzugreifen oder ../../web.config um Anwendungskonfiguration mit Geheimnissen zu lesen.
Anfälliger Code ermöglicht den folgenden Angriffsmechanismus:
-
..Sequenzen navigieren in Verzeichnisebenen nach oben. - Angreifer können der vorgesehenen Verzeichnis-Sandbox entkommen.
- Zugreifen auf vertrauliche Dateien, Konfigurationsdateien oder Systemdateien.
- Das potenzielle Überschreiben von kritischen Anwendungsdateien.
Berücksichtigen Sie die folgende sichere Implementierung:
// SECURE: Validating and constraining file paths
string filename = Request.Query["file"];
// Remove path traversal sequences
filename = Path.GetFileName(filename);
// Construct full path
string uploadsDirectory = Path.GetFullPath(@"C:\uploads\");
string filePath = Path.GetFullPath(Path.Combine(uploadsDirectory, filename));
// Verify the resulting path is still within the uploads directory
if (!filePath.StartsWith(uploadsDirectory))
{
throw new SecurityException("Invalid file path");
}
string content = File.ReadAllText(filePath);
Sichere Implementierungen zeigen die folgenden Verteidigungsstrategien:
- Verwenden Sie
Path.GetFileName(), um Verzeichnisinformationen zu entfernen. - Zulässige Dateien oder Muster zulassen, anstatt gefährliche Zeichen zu blockieren.
- Überprüfen Sie, ob aufgelöste Pfade in beabsichtigten Verzeichnissen verbleiben.
- Implementieren Sie strenge Dateizugriffsberechtigungen auf Betriebssystemebene.
Weitere Sicherheitsüberlegungen
Zusätzlich zu den in den vorherigen Abschnitten behandelten Sicherheitsrisiken erfordern mehrere andere Sicherheitsprobleme das Bewusstsein für Entwickler.
Websiteübergreifendes Skripting (XSS)
Das websiteübergreifende Skripting ermöglicht Es Angreifern, bösartigen Code in Webanwendungen einzufügen, wodurch Benutzerdaten und Sitzungen potenziell beeinträchtigt werden.
Zwar trifft dies nicht auf Konsolenanwendungen zu, aber Webentwickler müssen alle Benutzereingaben überprüfen und codieren, bevor sie in Browsern angezeigt werden.
Hartcodierte Geheimschlüssel
Anmeldeinformationen und vertrauliche Konfigurationswerte, die direkt im Quellcode eingebettet sind, stellen ein kritisches Sicherheitsrisiko dar, das ganze Systeme verfügbar machen kann.
Durch das Einbetten von API-Schlüsseln, Kennwörtern oder Token direkt in den Quellcode werden sie für alle Benutzer mit Repositoryzugriff verfügbar gemacht. Geheimnisse sollten folgende Merkmale haben:
- In sicheren Konfigurationssystemen oder Tresoren gespeichert.
- Wird nie zur Versionsverwaltung committet.
- Regelmäßig gedreht.
- Verwaltet mit ordnungsgemäßen Zugriffskontrollen.
Ressourcenerschöpfung und Dienstverweigerung
Angreifer nutzen häufig Anwendungen, die Ressourcen nicht ordnungsgemäß verwalten. Angriffe können zu Dienstunterbrechungen oder Systemabstürzen führen.
Schlechte Ressourcenverwaltung kann Denial-of-Service-Angriffe aktivieren. Beispiele sind:
- Vollständige große Dateien werden in den Arbeitsspeicher gelesen (dies führt zu Out-of-Memory-Fehlern).
- Anforderungsgrößen oder Frequenzen werden nicht begrenzt.
- Ineffiziente Algorithmen, die übermäßige CPU verbrauchen.
- Fehler beim ordnungsgemäßen Löschen von Ressourcen.
Identifizieren von Sicherheitsproblemen im Code
Die Identifizierung von Sicherheitsrisiken im Code erfordert einen systematischen Ansatz.
Analysieren der Benutzereingabebehandlung
Die Benutzereingabe stellt den primären Angriffsvektor für die meisten Sicherheitsrisiken dar, wodurch es wichtig ist, zu untersuchen, wie Ihre Code externe Daten verarbeitet.
Jeder Punkt, an dem Ihr Code Benutzereingaben akzeptiert, ist ein potenzieller Einstiegspunkt für Angriffe:
- Suchen Sie nach: Eingaben, die in SQL-Abfragen, Dateipfaden, Systembefehlen oder kritischer Logik verwendet werden.
- Frage: "Vertraue ich diesem Input zu sehr?"
- Berücksichtigen Sie: Injection-Schwachstellen, Pfad-Traversierung, Befehlsinjektion.
Überprüfen kryptografischer Vorgänge
Sicherheitsimplementierungen mit Verschlüsselung, Hashing und Authentifizierung erfordern zusätzliche Überprüfungen, da schwache Kryptografie ganze Systeme kompromittieren kann.
Kryptografiecode erfordert besondere Kontrolle:
-
Suchen Sie nach:
MD5.Create(),SHA1.Create(), Klartext-Passwortspeicher. - Frage: "Gilt diese kryptografische Methode immer noch als sicher?"
- Berücksichtigen Sie: Verwenden von bcrypt, scrypt oder Argon2 für Kennwörter; SHA-256 oder besser für Integritätsprüfungen.
Untersuchung von Protokollierungseinträgen
Protokolle können versehentlich zu Sicherheitsrisiken werden, wenn sie vertrauliche Informationen erfassen, die geschützt bleiben sollen.
Scannen Sie Ihre Codebasis nach vertraulichen Daten in Protokollen:
- Suchen Sie nach: Protokollanweisungen, die Variablen namens "Kennwort", "Geheim", "Token", "apiKey", "cardNumber" enthalten.
- Fragen Sie: "Welche Informationen gebe ich in Protokollen preis?"
- Bedenken Sie: Was geschieht, wenn diese Protokolle kompromittiert oder versehentlich verfügbar gemacht werden?
Überprüfen der Dateivorgänge
Dateiverarbeitungscode stellt eindeutige Sicherheitsprobleme dar, da sie Systemressourcen außerhalb des beabsichtigten Bereichs Ihrer Anwendung verfügbar machen kann.
Dateibehandlungscode erfordert sorgfältige Überprüfung:
-
Suchen Sie nach:
Path.Combinemit Benutzereingaben, Dateivorgängen basierend auf vom Benutzer bereitgestellten Pfaden. - Frage: "Kann ein Benutzer das beabsichtigte Verzeichnis escapen?"
- Erwägen Sie: Pfaddurchlaufangriffe und Verzeichnisfluchttechniken.
Verwenden automatisierter Tools
Während die manuelle Codeüberprüfung unerlässlich ist, können automatisierte Tools große Codebasen effizient scannen und häufige Sicherheitsrisikomuster identifizieren, die bei der manuellen Überprüfung möglicherweise übersehen werden.
Kombinieren Sie die manuelle Codeüberprüfung mit automatisierter Analyse:
- Statische Analyse: Tools wie GitHub CodeQL-Scancode für bekannte Schwachstellenmuster.
- GitHub Copilot: Verwenden Sie den Ask-Modus, um Codeabschnitte zu analysieren: "Gibt es Sicherheitsprobleme in diesem Code?"
- Sicherheitslinters: Sprachspezifische Tools können offensichtliche Sicherheitsfehler kennzeichnen.
GitHub Copilot kann viele häufige Sicherheitsprobleme identifizieren, wenn Sie ihn bitten, Code zu analysieren. Es basiert auf Mustern von Millionen von Codebasen, um Sicherheitsrisiken zu erkennen.
Shift-Left-Sicherheitsansatz
Das Prinzip der "Verschiebung nach links" bedeutet, die Sicherheit früher im Entwicklungslebenszyklus zu adressieren:
- Entwurfsphase: Berücksichtigen Sie sicherheitsrelevante Auswirkungen von Architekturentscheidungen.
- Entwicklungsphase: Schreiben sie sicheren Code von Anfang an. Erkennen Sie Probleme während des Code-Reviews.
- Testphase: Schließen Sie Sicherheitstests zusammen mit Funktionstests ein.
- Bereitstellungsphase: Überprüfung auf Sicherheitsrisiken vor der Veröffentlichung.
Das Abfangen von Sicherheitsproblemen während der Entwicklung ist viel kostengünstiger als die Ermittlung dieser Probleme in der Produktion. Die Kosten für die Behebung von Sicherheitsrisiken steigen exponentiell mit jeder Phase, durch die sie vorankommen.
Zusammenfassung
Häufige Sicherheitsrisiken wie Einfügungsangriffe, schwache Verschlüsselung, unsachgemäße Protokollierung und Pfad-Traversal stellen schwerwiegende Bedrohungen für die Anwendungssicherheit dar. Wenn Sie diese Sicherheitsrisiken verstehen, können Sie sie im Code erkennen und deren Behebung priorisieren. Durch die Kombination von Kenntnissen allgemeiner Sicherheitsrisikomuster mit Tools wie GitHub Copilot können Sie Sicherheitsprobleme effektiver identifizieren und beheben.