CodeGym/Blog Java/Random-PL/Bezpieczeństwo w Javie: najlepsze praktyki
John Squirrels
Poziom 41
San Francisco

Bezpieczeństwo w Javie: najlepsze praktyki

Opublikowano w grupie Random-PL
Jednym z najważniejszych wskaźników w aplikacjach serwerowych jest bezpieczeństwo. Jest to rodzaj wymagania niefunkcjonalnego . Bezpieczeństwo w Javie: najlepsze praktyki - 1Bezpieczeństwo obejmuje wiele elementów. Oczywiście pełne omówienie wszystkich znanych zasad bezpieczeństwa i środków bezpieczeństwa zajęłoby więcej niż jeden artykuł, więc skupimy się na najważniejszych. Osoba dobrze zorientowana w tym temacie potrafi skonfigurować wszystkie istotne procesy, uniknąć tworzenia nowych luk w zabezpieczeniach i będzie potrzebna w każdym zespole. Oczywiście nie powinieneś myśleć, że Twoja aplikacja będzie w 100% bezpieczna, jeśli zastosujesz się do tych praktyk. NIE! Ale z nimi na pewno będzie bezpieczniej. Chodźmy.

1. Zapewnij bezpieczeństwo na poziomie języka Java

Przede wszystkim bezpieczeństwo w Javie zaczyna się już na poziomie możliwości tego języka. Co byśmy zrobili, gdyby nie było modyfikatorów dostępu? Nie byłoby nic poza anarchią. Język programowania pomaga nam pisać bezpieczny kod, a także wykorzystuje wiele ukrytych funkcji bezpieczeństwa:
  1. Mocne pisanie. Java jest językiem o typie statycznym. Dzięki temu możliwe jest wychwytywanie błędów związanych z typem w czasie wykonywania.
  2. Modyfikatory dostępu. Dzięki temu możemy dostosować dostęp do klas, metod i pól zgodnie z potrzebami.
  3. Automatyczne zarządzanie pamięcią. W tym celu programiści Java mają moduł wyrzucania elementów bezużytecznych, który uwalnia nas od konieczności ręcznego konfigurowania wszystkiego. Tak, czasami pojawiają się problemy.
  4. Weryfikacja kodu bajtowego : Java jest kompilowana do kodu bajtowego, który jest sprawdzany przez środowisko wykonawcze przed wykonaniem.
Ponadto istnieją zalecenia firmy Oracle dotyczące bezpieczeństwa . Oczywiście nie jest napisana wzniosłym językiem i podczas jej czytania można kilka razy zasnąć, ale warto. W szczególności ważny jest dokument zatytułowany Secure Coding Guidelines for Java SE . Zawiera porady dotyczące pisania bezpiecznego kodu. Ten dokument zawiera ogromną ilość bardzo przydatnych informacji. Jeśli masz taką możliwość, zdecydowanie powinieneś ją przeczytać. Aby wzbudzić zainteresowanie tym materiałem, oto kilka interesujących wskazówek:
  1. Unikaj serializacji klas wrażliwych na zabezpieczenia. Serializacja udostępnia interfejs klasy w pliku serializowanym, nie wspominając o danych, które są serializowane.
  2. Staraj się unikać modyfikowalnych klas danych. Zapewnia to wszystkie korzyści niezmiennych klas (np. bezpieczeństwo wątków). Jeśli masz zmienny obiekt, może to prowadzić do nieoczekiwanego zachowania.
  3. Wykonaj kopie zwróconych mutable obiektów. Jeśli metoda zwraca odwołanie do wewnętrznego obiektu zmiennego, kod klienta może zmienić stan wewnętrzny obiektu.
  4. I tak dalej…
Zasadniczo, Secure Coding Guidelines for Java SE to zbiór porad i wskazówek, jak poprawnie i bezpiecznie pisać kod Java.

2. Wyeliminuj luki w zabezpieczeniach SQL Injection

Jest to szczególny rodzaj wrażliwości. Jest wyjątkowy, ponieważ jest zarówno jedną z najbardziej znanych, jak i najczęstszych luk w zabezpieczeniach. Jeśli nigdy nie interesowałeś się bezpieczeństwem komputerowym, nie będziesz o tym wiedział. Co to jest iniekcja SQL? Jest to atak na bazę danych polegający na wstrzyknięciu dodatkowego kodu SQL tam, gdzie nie jest to oczekiwane. Załóżmy, że mamy metodę, która akceptuje jakiś parametr do zapytania do bazy danych. Na przykład nazwa użytkownika. Wrażliwy kod wyglądałby mniej więcej tak:
// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
   // Connect to the database
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Compose a SQL database query with our firstName
   String query = "SELECT * FROM USERS WHERE firstName = " + firstName;

   // Execute the query
   Statement statement = connection.createStatement();
   ResultSet result = statement.executeQuery(query);

   // Use mapToUsers to convert the ResultSet into a collection of users.
   return mapToUsers(result);
}

private List mapToUsers(ResultSet resultSet) {
   // Converts to a collection of users
}
W tym przykładzie zapytanie SQL jest przygotowywane z wyprzedzeniem w osobnej linii. Więc w czym problem, prawda? Może problem polega na tym, że lepiej byłoby użyć String.format ? NIE? Cóż, co wtedy? Postawmy się w sytuacji testera i zastanówmy się, co można przekazać jako wartość firstName . Na przykład:
  1. Możemy przekazać to, czego się oczekuje — nazwę użytkownika. Następnie baza danych zwróci wszystkich użytkowników o tej nazwie.
  2. Możemy przekazać pusty ciąg. Wtedy wszyscy użytkownicy zostaną zwróceni.
  3. Ale możemy również przekazać: "'; DROP TABLE USERS;". I tutaj mamy teraz huuuuuuużo problemów. To zapytanie usunie tabelę z bazy danych. Wraz ze wszystkimi danymi. WSZYSTKO.
Czy możesz sobie wyobrazić problemy, jakie by to spowodowało? Poza tym możesz pisać, co chcesz. Możesz zmienić nazwy wszystkich użytkowników. Możesz usunąć ich adresy. Możliwości sabotażu są ogromne. Aby tego uniknąć, należy zapobiec wstrzyknięciu gotowego zapytania i zamiast tego utworzyć zapytanie za pomocą parametrów. To powinien być jedyny sposób tworzenia zapytań do bazy danych. W ten sposób możesz wyeliminować tę lukę. Na przykład:
// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
   // Connect to the database
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Create a parameterized query.
   String query = "SELECT * FROM USERS WHERE firstName = ?";

   // Create a prepared statement with the parameterized query
   PreparedStatement statement = connection.prepareStatement(query);

   // Pass the parameter's value
   statement.setString(1, firstName);

   // Execute the query
   ResultSet result = statement.executeQuery(query);

   // Use mapToUsers to convert the ResultSet into a collection of users.
   return mapToUsers(result);
}

private List mapToUsers(ResultSet resultSet) {
   // Converts to a collection of users
}
W ten sposób unika się luki w zabezpieczeniach. Dla tych, którzy chcą zagłębić się w ten artykuł, oto świetny przykład . Skąd wiesz, kiedy rozumiesz tę lukę? Jeśli rozumiesz żart z poniższego komiksu, prawdopodobnie dobrze rozumiesz, o co chodzi w tej luce :DBezpieczeństwo w Javie: najlepsze praktyki - 2

3. Skanuj zależności i aktualizuj je

Co to znaczy? Jeśli nie wiesz, czym jest zależność, wyjaśnię. Zależność to archiwum JAR z kodem, które jest połączone z projektem za pomocą automatycznych systemów kompilacji (Maven, Gradle, Ant) w celu ponownego wykorzystania czyjegoś rozwiązania. Na przykład Project Lombok , który generuje dla nas gettery, settery itp. w czasie wykonywania. Duże aplikacje mogą mieć wiele zależności. Niektóre są przechodnie (to znaczy każda zależność może mieć swoje własne zależności itd.). W rezultacie atakujący coraz częściej zwracają uwagę na zależności open source, ponieważ są one regularnie używane i wielu klientów może mieć z ich powodu problemy. Ważne jest, aby upewnić się, że nie ma znanych luk w całym drzewie zależności (tak, wygląda jak drzewo). Można to zrobić na kilka sposobów.

Użyj Snyk do monitorowania zależności

Snyk sprawdza wszystkie zależności projektu i oznacza znane luki w zabezpieczeniach. Możesz zarejestrować się na Snyk i importować swoje projekty przez GitHub. Bezpieczeństwo w Javie: najlepsze praktyki - 3Ponadto, jak widać na powyższym obrazku, jeśli luka zostanie naprawiona w nowszej wersji, Snyk zaoferuje poprawkę i utworzy żądanie ściągnięcia. Możesz go używać za darmo do projektów open source. Projekty są skanowane w regularnych odstępach czasu, np. raz w tygodniu, raz w miesiącu. Zarejestrowałem się i dodałem wszystkie moje publiczne repozytoria do skanu Snyk (nie ma w tym nic niebezpiecznego, ponieważ są one już publiczne dla wszystkich). Następnie Snyk pokazał wynik skanowania: Bezpieczeństwo w Javie: najlepsze praktyki - 4Po chwili Snyk-bot przygotował kilka żądań ściągnięcia w projektach, w których należy zaktualizować zależności: Bezpieczeństwo w Javie: najlepsze praktyki - 5A także:Bezpieczeństwo w Javie: najlepsze praktyki - 6To świetne narzędzie do znajdowania luk w zabezpieczeniach i monitorowania aktualizacji dla nowych wersji.

Skorzystaj z laboratorium bezpieczeństwa GitHub

Każdy, kto pracuje na GitHub, może skorzystać z wbudowanych narzędzi. Więcej informacji na temat tego podejścia można znaleźć w poście na blogu zatytułowanym Announcing GitHub Security Lab . To narzędzie jest oczywiście prostsze niż Snyk, ale zdecydowanie nie należy go zaniedbywać. Co więcej, liczba znanych luk w zabezpieczeniach będzie tylko rosła, więc zarówno Snyk, jak i GitHub Security Lab będą nadal się rozwijać i ulepszać.

Włącz Sonatype DepShield

Jeśli używasz GitHub do przechowywania swoich repozytoriów, możesz dodać Sonatype DepShield, jedną z aplikacji w MarketPlace, do swoich projektów. Może być również używany do skanowania projektów pod kątem zależności. Ponadto, jeśli coś znajdzie, zostanie wygenerowany GitHub Issue z odpowiednim opisem, jak pokazano poniżej:Bezpieczeństwo w Javie: najlepsze praktyki - 7

4. Ostrożnie obchodź się z poufnymi danymi

Możemy alternatywnie użyć wyrażenia „dane wrażliwe”. Wyciek danych osobowych klienta, numerów kart kredytowych i innych poufnych informacji może spowodować nieodwracalne szkody. Przede wszystkim przyjrzyj się dokładnie projektowi swojej aplikacji i ustal, czy naprawdę potrzebujesz tych czy innych danych. Być może tak naprawdę nie potrzebujesz niektórych posiadanych danych — danych, które zostały dodane z myślą o przyszłości, która nie nadeszła i jest mało prawdopodobne, że nadejdzie. Ponadto wielu z was nieumyślnie ujawnia takie dane poprzez rejestrowanie. Prostym sposobem zapobiegania wprowadzaniu poufnych danych do dzienników jest czyszczenie metod toString() jednostek domeny (takich jak Użytkownik, Student, Nauczyciel itp.). Zapobiegnie to przypadkowemu wyświetleniu poufnych pól. Jeśli używasz Lombok do generowania toString()możesz użyć adnotacji @ToString.Exclude , aby uniemożliwić użycie pola w danych wyjściowych metody toString() . Należy również zachować szczególną ostrożność podczas wysyłania danych do świata zewnętrznego. Załóżmy, że mamy punkt końcowy HTTP, który pokazuje nazwy wszystkich użytkowników. Nie ma potrzeby pokazywania unikalnego wewnętrznego identyfikatora użytkownika. Dlaczego? Ponieważ atakujący mógłby go użyć do uzyskania innych, bardziej wrażliwych informacji o użytkowniku. Na przykład, jeśli używasz Jacksona do serializacji/deserializacji POJO do/z JSON , możesz użyć @JsonIgnore i @JsonIgnorePropertiesadnotacje zapobiegające serializacji/deserializacji określonych pól. Ogólnie rzecz biorąc, musisz używać różnych klas POJO w różnych miejscach. Co to znaczy?
  1. Pracując z bazą danych, użyj jednego typu POJO (jednostki).
  2. Podczas pracy z logiką biznesową przekonwertuj jednostkę na model.
  3. Podczas pracy ze światem zewnętrznym i wysyłania żądań HTTP używaj różnych encji (DTO).
W ten sposób możesz jasno określić, które pola będą widoczne z zewnątrz, a które nie.

Używaj silnych algorytmów szyfrowania i mieszania

Poufne dane klientów muszą być bezpiecznie przechowywane. Aby to zrobić, musimy użyć szyfrowania. W zależności od zadania musisz zdecydować, jakiego rodzaju szyfrowania użyć. Dodatkowo silniejsze szyfrowanie zajmuje więcej czasu, więc ponownie musisz się zastanowić, na ile potrzeba tego uzasadnia czas poświęcony na to. Oczywiście możesz sam napisać algorytm szyfrowania. Ale to jest niepotrzebne. Możesz skorzystać z istniejących rozwiązań w tym zakresie. Na przykład 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>
Zobaczmy, co zrobić, korzystając z tego przykładu dotyczącego szyfrowania i deszyfrowania:
private static void encryptDecryptExample() {
   AeadConfig.register();
   KeysetHandle handle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_CTR_HMAC_SHA256);

   String plaintext = "Elvis lives!";
   String aad = "Buddy Holly";

   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));
}

Szyfrowanie haseł

Do tego zadania najbezpieczniej jest użyć szyfrowania asymetrycznego. Dlaczego? Ponieważ aplikacja tak naprawdę nie musi odszyfrowywać haseł. To jest standardowe podejście. W rzeczywistości, gdy użytkownik wprowadza hasło, system szyfruje je i porównuje z tym, co istnieje w magazynie haseł. Wykonywany jest ten sam proces szyfrowania, więc możemy spodziewać się, że będą się zgadzać, oczywiście jeśli zostanie wprowadzone poprawne hasło :) Odpowiednie są tutaj BCrypt i SCrypt. Obie są funkcjami jednokierunkowymi (skrótami kryptograficznymi) ze złożonymi obliczeniowo algorytmami, które zajmują dużo czasu. To jest dokładnie to, czego potrzebujemy, ponieważ bezpośrednie obliczenia będą trwać wiecznie (cóż, bardzo, bardzo długo). Spring Security obsługuje całą gamę algorytmów. Możemy użyć SCryptPasswordEncoder i BCryptPasswordEncoder. To, co obecnie uważa się za silny algorytm szyfrowania, w przyszłym roku może zostać uznane za słabe. W rezultacie dochodzimy do wniosku, że powinniśmy regularnie sprawdzać stosowane przez nas algorytmy iw razie potrzeby aktualizować biblioteki zawierające algorytmy szyfrowania.

Zamiast konkluzji

Dzisiaj rozmawialiśmy o bezpieczeństwie i oczywiście wiele rzeczy zostało pozostawionych za kulisami. Właśnie otworzyłem ci drzwi do nowego świata, świata, który żyje własnym życiem. Z bezpieczeństwem jest tak jak z polityką: jeśli nie będziesz się zajmował polityką, polityka zajmie się tobą. Tradycyjnie sugeruję śledzenie mnie na koncie GitHub . Zamieszczam tam swoje dzieła z wykorzystaniem różnych technologii, które studiuję i stosuję w pracy.

Przydatne linki

  1. Guru99: samouczek wstrzykiwania kodu SQL
  2. Oracle: Centrum Zasobów Bezpieczeństwa Javy
  3. Oracle: Wytyczne bezpiecznego kodowania dla Java SE
  4. Baeldung: Podstawy bezpieczeństwa Java
  5. Średni: 10 wskazówek, jak zwiększyć bezpieczeństwo Java
  6. Snyk: 10 najlepszych praktyk bezpieczeństwa Java
  7. GitHub: ogłaszamy GitHub Security Lab: wspólne zabezpieczanie światowego kodu
Komentarze
  • Popularne
  • Najnowsze
  • Najstarsze
Musisz się zalogować, aby dodać komentarz
Ta strona nie ma jeszcze żadnych komentarzy