JavaRush /Java-Blog /Random-DE /Sicherheit in Java: Best Practices

Sicherheit in Java: Best Practices

Veröffentlicht in der Gruppe Random-DE
Bei Serveranwendungen ist Sicherheit einer der wichtigsten Indikatoren. Dies ist eine der Arten nichtfunktionaler Anforderungen . Sicherheit in Java: Best Practices – 1Sicherheit besteht aus vielen Komponenten. Um alle bekannten Schutzprinzipien und -maßnahmen vollständig abzudecken, müssen Sie natürlich mehr als einen Artikel schreiben. Konzentrieren wir uns also auf das Wichtigste. Eine Person, die sich mit diesem Thema gut auskennt, in der Lage ist, alle Prozesse einzurichten und sicherzustellen, dass dadurch keine neuen Sicherheitslücken entstehen, wird in jedem Team benötigt. Natürlich sollten Sie nicht glauben, dass die Anwendung völlig sicher ist, wenn Sie diese Vorgehensweisen befolgen. Nein! Aber sicherer wird es mit ihnen auf jeden Fall sein. Gehen.

1. Sorgen Sie für Sicherheit auf Java-Sprachebene

Zunächst einmal beginnt die Sicherheit in Java bereits auf der Ebene der Sprachfunktionen. Das würden wir tun, wenn es keine Zugriffsmodifikatoren gäbe? ... Anarchie, nicht weniger. Eine Programmiersprache hilft uns, sicheren Code zu schreiben und auch viele implizite Sicherheitsfunktionen zu nutzen:
  1. Starkes Tippen. Java ist eine statisch typisierte Sprache, die die Möglichkeit bietet, Typfehler zur Laufzeit zu erkennen.
  2. Zugriffsmodifikatoren. Dank ihnen können wir den Zugriff auf Klassen, Methoden und Klassenfelder nach Bedarf konfigurieren.
  3. Automatische Speicherverwaltung. Zu diesem Zweck haben wir (Javaisten ;)) den Garbage Collector, der Sie von der manuellen Konfiguration befreit. Ja, manchmal treten Probleme auf.
  4. Bytecode-Prüfung: Java kompiliert zu Bytecode, der vor der Ausführung von der Laufzeit überprüft wird .
Unter anderem gibt es Empfehlungen von Oracle zur Sicherheit. Natürlich ist es nicht in einem „hohen Stil“ geschrieben und man könnte beim Lesen mehrmals einschlafen, aber es lohnt sich. Ein besonders wichtiges Dokument sind die Secure Coding Guidelines for Java SE , die Tipps zum Schreiben von sicherem Code geben. Dieses Dokument enthält viele nützliche Informationen. Wenn möglich, ist es auf jeden Fall lesenswert. Um das Interesse an diesem Material zu wecken, hier einige interessante Tipps:
  1. Vermeiden Sie die Serialisierung sicher sensibler Klassen. In diesem Fall können Sie die Klassenschnittstelle aus der serialisierten Datei abrufen, ganz zu schweigen von den Daten, die serialisiert werden.
  2. Vermeiden Sie veränderliche Datenklassen. Dies bietet alle Vorteile unveränderlicher Klassen (z. B. Thread-Sicherheit). Wenn ein veränderliches Objekt vorhanden ist, kann dies zu unerwartetem Verhalten führen.
  3. Erstellen Sie Kopien der zurückgegebenen veränderlichen Objekte. Wenn eine Methode einen Verweis auf ein internes veränderbares Objekt zurückgibt, kann Clientcode den internen Status des Objekts ändern.
  4. Usw…
Im Allgemeinen enthalten die Secure Coding Guidelines für Java SE eine Reihe von Tipps und Tricks, wie man Code in Java korrekt und sicher schreibt.

2. Beseitigen Sie die SQL-Injection-Schwachstelle

Einzigartige Verwundbarkeit. Seine Einzigartigkeit liegt darin, dass es sich sowohl um eine der bekanntesten als auch um eine der häufigsten Schwachstellen handelt. Wenn Sie sich nicht für das Thema Sicherheit interessieren, wissen Sie nichts davon. Was ist SQL-Injection? Dabei handelt es sich um einen Angriff auf eine Datenbank, bei dem zusätzlicher SQL-Code dort eingefügt wird, wo er nicht erwartet wird. Nehmen wir an, wir haben eine Methode, die einige Parameter benötigt, um die Datenbank abzufragen. Zum Beispiel Benutzername. Der Code mit der Sicherheitslücke sieht etwa so aus:
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
   // Создается связь с базой данных
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Пишем sql Anfrage в базу данных с нашим firstName
   String query = "SELECT * FROM USERS WHERE firstName = " + firstName;

   // выполняем Anfrage
   Statement statement = connection.createStatement();
   ResultSet result = statement.executeQuery(query);

   // при помощи mapToUsers переводит ResultSet в коллекцию юзеров.
   return mapToUsers(result);
}

private List<User> mapToUsers(ResultSet resultSet) {
   //переводит в коллекцию юзеров
}
In diesem Beispiel wird die SQL-Abfrage vorab in einer separaten Zeile vorbereitet. Es scheint doch das Problem zu sein, oder? Vielleicht liegt das Problem darin, dass es besser wäre, String.format? Nein? Was dann? Versetzen wir uns in die Lage eines Testers und überlegen wir, was sich mit dem Wert ausdrücken lässt firstName. Zum Beispiel:
  1. Sie können das Erwartete übergeben: den Benutzernamen. Dann gibt die Datenbank alle Benutzer mit diesem Namen zurück.
  2. Sie können eine leere Zeichenfolge übergeben: Dann werden alle Benutzer zurückgegeben.
  3. Oder Sie können Folgendes übergeben: „''; DROP TABLE USERS;“. Und hier wird es größere Probleme geben. Diese Abfrage entfernt die Tabelle aus der Datenbank. Mit allen Daten. ALLE.
Können Sie sich vorstellen, welche Probleme dies verursachen könnte? Dann können Sie schreiben, was Sie wollen. Sie können den Namen aller Benutzer ändern und deren Adressen löschen. Der Spielraum für Sabotage ist riesig. Um dies zu vermeiden, müssen Sie aufhören, eine vorgefertigte Abfrage einzufügen, und sie stattdessen mithilfe von Parametern erstellen. Dies sollte die einzige Möglichkeit sein, die Datenbank abzufragen. Auf diese Weise können Sie diese Schwachstelle beseitigen. Beispiel:
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
   // Создается связь с базой данных
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Создаем параметризированный Anfrage.
   String query = "SELECT * FROM USERS WHERE firstName = ?";

   // Создаем подготовленный стейтмент с параметризованным Anfrageом
   PreparedStatement statement = connection.prepareStatement(query);

   // Передаем Bedeutung параметра
   statement.setString(1, firstName);

   // выполняем Anfrage
   ResultSet result = statement.executeQuery(query);

   // при помощи mapToUsers переводим ResultSet в коллекцию юзеров.
   return mapToUsers(result);
}

private List<User> mapToUsers(ResultSet resultSet) {
   //переводим в коллекцию юзеров
}
Auf diese Weise wird diese Schwachstelle vermieden. Für diejenigen, die sich tiefer mit der Thematik befassen möchten als mit diesem Artikel, gibt es hier ein tolles Beispiel . Woher wissen Sie, ob Sie diesen Teil verstehen? Wenn der Witz unten klar geworden ist, dann ist das ein sicheres Zeichen dafür, dass das Wesen der Schwachstelle klar ist :D Sicherheit in Java: Best Practices – 2

3. Scannen Sie Abhängigkeiten und halten Sie sie auf dem neuesten Stand

Was bedeutet das? Für diejenigen, die nicht wissen, was eine Abhängigkeit ist, erkläre ich es: Es handelt sich um ein JAR-Archiv mit Code, das über automatische Build-Systeme (Maven, Gradle, Ant) mit einem Projekt verbunden wird, um die Lösung eines anderen wiederzuverwenden. Zum Beispiel Project Lombok , das zur Laufzeit Getter, Setter usw. für uns generiert. Und wenn wir über große Anwendungen sprechen, verwenden sie viele verschiedene Abhängigkeiten. Einige sind transitiv (d. h. jede Abhängigkeit kann ihre eigenen Abhängigkeiten haben usw.). Daher achten Angreifer zunehmend auf Open-Source-Abhängigkeiten, da diese regelmäßig genutzt werden und für viele Clients Probleme bereiten können. Es ist wichtig sicherzustellen, dass im gesamten Abhängigkeitsbaum (der genau so aussieht) keine Schwachstellen bekannt sind. Und es gibt mehrere Möglichkeiten, dies zu tun.

Verwenden Sie Snyk zur Überwachung

Das Snyk- Tool überprüft alle Projektabhängigkeiten und weist auf bekannte Schwachstellen hin. Dort können Sie Ihre Projekte beispielsweise über GitHub registrieren und importieren. Sicherheit in Java: Best Practices – 3Wie Sie auf dem Bild oben sehen können, bietet Snyk außerdem an, dies zu tun und einen Pull-Request zu erstellen, wenn eine neuere Version eine Lösung für diese Schwachstelle bietet. Es kann kostenlos für Open-Source-Projekte genutzt werden. Projekte werden in einer bestimmten Häufigkeit gescannt: einmal pro Woche, einmal im Monat. Ich habe alle meine öffentlichen Repositorys registriert und zum Snyk-Scan hinzugefügt (daran ist nichts Gefährliches: Sie sind bereits für alle zugänglich). Als nächstes zeigte Snyk das Ergebnis des Scans: Sicherheit in Java: Best Practices – 4Und nach einer Weile bereitete Snyk-Bot mehrere Pull-Requests in Projekten vor, in denen Abhängigkeiten aktualisiert werden müssen: Sicherheit in Java: Best Practices – 5Und hier ist noch etwas: Sicherheit in Java: Best Practices – 6Dies ist also ein hervorragendes Tool zur Suche nach Schwachstellen und zur Überwachung bei Aktualisierungen neue Versionen.

Verwenden Sie GitHub Security Lab

Wer auf GitHub arbeitet, kann auch deren integrierte Tools nutzen. Mehr über diesen Ansatz können Sie in meiner Übersetzung aus ihrem Blog Announcement of GitHub Security Lab lesen . Dieses Tool ist natürlich einfacher als Snyk, aber Sie sollten es auf keinen Fall vernachlässigen. Darüber hinaus wird die Zahl der bekannten Schwachstellen weiter zunehmen, sodass sowohl Snyk als auch GitHub Security Lab erweitert und verbessert werden.

Aktivieren Sie Sonatype DepShield

Wenn Sie GitHub zum Speichern Ihrer Repositorys verwenden, können Sie eine der Anwendungen von MarketPlace zu Ihren Projekten hinzufügen – Sonatype DepShield. Mit seiner Hilfe können Sie auch Projekte nach Abhängigkeiten durchsuchen. Wenn außerdem etwas gefunden wird, wird ein GitHub-Problem mit einer entsprechenden Beschreibung erstellt, wie unten gezeigt: Sicherheit in Java: Best Practices – 7

4. Gehen Sie sorgfältig mit sensiblen Daten um

Sicherheit in Java: Best Practices – 8Im Englischen ist der Ausdruck „sensible Daten“ häufiger anzutreffen. Die Offenlegung persönlicher Daten, Kreditkartennummern und anderer persönlicher Informationen des Kunden kann zu irreparablen Schäden führen. Zunächst müssen Sie sich das Anwendungsdesign genau ansehen und feststellen, ob tatsächlich Daten benötigt werden. Vielleicht besteht für einige davon kein Bedarf, aber sie wurden für eine Zukunft hinzugefügt, die noch nicht gekommen ist und wahrscheinlich auch nicht kommen wird. Darüber hinaus können solche Daten während der Projektprotokollierung verloren gehen. toString()Eine einfache Möglichkeit, zu verhindern, dass vertrauliche Daten in Ihre Protokolle gelangen, besteht darin, die Methoden von Domänenentitäten (z. B. Benutzer, Schüler, Lehrer usw.) zu bereinigen . Dadurch wird verhindert, dass sensible Felder versehentlich gedruckt werden. Wenn Sie Lombok zum Generieren einer Methode verwenden toString(), können Sie eine Annotation verwenden, @ToString.Excludeum zu verhindern, dass das Feld in der Ausgabe über die Methode verwendet wird toString(). Seien Sie außerdem sehr vorsichtig, wenn Sie Daten mit der Außenwelt teilen. Beispielsweise gibt es einen HTTP-Endpunkt, der die Namen aller Benutzer anzeigt. Es ist nicht erforderlich, die interne eindeutige ID des Benutzers anzuzeigen. Warum? Denn damit kann ein Angreifer andere, vertraulichere Informationen über jeden Benutzer erhalten. Wenn Sie beispielsweise Jackson zum Serialisieren und Deserialisieren von POJOs in JSON verwenden , können Sie mithilfe der Annotationen @JsonIgnoreund @JsonIgnorePropertiesverhindern, dass bestimmte Felder serialisiert und deserialisiert werden. Im Allgemeinen müssen Sie für verschiedene Orte unterschiedliche POJO-Klassen verwenden. Was bedeutet das?
  1. Um mit der Datenbank zu arbeiten, verwenden Sie nur POJO – Entity.
  2. Um mit Geschäftslogik zu arbeiten, übertragen Sie die Entität auf das Modell.
  3. Um mit der Außenwelt zusammenzuarbeiten und HTTP-Anfragen zu senden, verwenden Sie dritte Entitäten – DTO.
So können Sie klar definieren, welche Felder von außen sichtbar sind und welche nicht.

Verwenden Sie starke Verschlüsselungs- und Hashing-Algorithmen

Vertrauliche Kundendaten müssen sicher gespeichert werden. Dazu müssen Sie eine Verschlüsselung verwenden. Abhängig von der Aufgabe müssen Sie entscheiden, welche Art der Verschlüsselung Sie verwenden möchten. Darüber hinaus nimmt eine stärkere Verschlüsselung mehr Zeit in Anspruch, sodass Sie erneut überlegen müssen, inwieweit die Notwendigkeit dafür den dafür aufgewendeten Zeitaufwand rechtfertigt. Natürlich können Sie den Algorithmus auch selbst schreiben. Aber das ist unnötig. In diesem Bereich können Sie auf bestehende Lösungen zurückgreifen. Zum Beispiel Google Tink :
<!-- https://mvnrepository.com/artifact/com.google.crypto.tink/tink -->
<dependency>
   <groupId>com.google.crypto.tink</groupId>
   <artifactId>tink</artifactId>
   <version>1.3.0</version>
</dependency>
Sehen wir uns an, wie man es verwendet, indem wir als Beispiel zeigen, wie man in die eine oder andere Richtung verschlüsselt:
private static void encryptDecryptExample() {
   AeadConfig.register();
   KeysetHandle handle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_CTR_HMAC_SHA256);

   String plaintext = "Цой жив!";
   String aad = "Юрий Клинских";

   Aead aead = handle.getPrimitive(Aead.class);
   byte[] encrypted = aead.encrypt(plaintext.getBytes(), aad.getBytes());
   String encryptedString = Base64.getEncoder().encodeToString(encrypted);
   System.out.println(encryptedString);

   byte[] decrypted = aead.decrypt(Base64.getDecoder().decode(encrypted), aad.getBytes());
   System.out.println(new String(decrypted));
}

Passwortverschlüsselung

Für diese Aufgabe ist es am sichersten, asymmetrische Verschlüsselung zu verwenden. Warum? Denn die Anwendung muss Passwörter wirklich nicht entschlüsseln. Dies ist der allgemeine Ansatz. Wenn ein Benutzer in Wirklichkeit ein Passwort eingibt, verschlüsselt das System es und vergleicht es mit dem, was sich im Passwort-Tresor befindet. Die Verschlüsselung erfolgt mit den gleichen Mitteln, sodass Sie davon ausgehen können, dass sie übereinstimmen (natürlich nur, wenn Sie das richtige Passwort eingeben ;). Hierfür eignen sich BCrypt und SCrypt. Bei beiden handelt es sich um Einwegfunktionen (kryptografische Hashes) mit rechentechnisch komplexen Algorithmen, die viel Zeit in Anspruch nehmen. Das ist genau das, was Sie brauchen, denn die direkte Entschlüsselung wird ewig dauern. Spring Security unterstützt beispielsweise eine Reihe von Algorithmen. SCryptPasswordEncoderSie können auch verwenden BCryptPasswordEncoder. Was derzeit ein starker Verschlüsselungsalgorithmus ist, könnte nächstes Jahr schwach sein. Daraus schließen wir, dass es notwendig ist, die verwendeten Algorithmen zu überprüfen und Bibliotheken mit Algorithmen zu aktualisieren.

Statt Ausgabe

Heute haben wir über Sicherheit gesprochen und natürlich blieben viele Dinge hinter den Kulissen. Ich habe dir gerade die Tür zu einer neuen Welt geöffnet: einer Welt, die ihr eigenes Leben führt. Mit der Sicherheit ist es dasselbe wie mit der Politik: Wenn Sie sich nicht in der Politik engagieren, wird sich die Politik mit Ihnen befassen. Normalerweise empfehle ich, mein Github-Konto zu abonnieren . Dort veröffentliche ich meine Arbeiten zu verschiedenen Technologien, die ich studiere und bei der Arbeit anwende.

Nützliche Links

Ja, fast alle Artikel auf der Website sind auf Englisch verfasst. Ob es uns gefällt oder nicht, Englisch ist die Kommunikationssprache für Programmierer. Alle neuesten Artikel, Bücher und Zeitschriften zum Thema Programmierung sind auf Englisch verfasst. Deshalb sind meine Links zu Empfehlungen größtenteils auf Englisch:
  1. Habr: SQL-Injection für Anfänger
  2. Oracle: Java Security Resource Center
  3. Oracle: Richtlinien zur sicheren Codierung für Java SE
  4. Zusammenfassung: Die Grundlagen der Java-Sicherheit
  5. Mittel: 10 Tipps zur Verbesserung Ihrer Java-Sicherheit
  6. Snyk: 10 Best Practices für die Java-Sicherheit
  7. JR: Ankündigung des GitHub Security Lab: Gemeinsamen Schutz Ihres gesamten Codes
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION