JavaRush /Blog Java /Random-PL /Bezpieczeństwo w Javie: najlepsze praktyki
Roman Beekeeper
Poziom 35

Bezpieczeństwo w Javie: najlepsze praktyki

Opublikowano w grupie Random-PL
W aplikacjach serwerowych jednym z najważniejszych wskaźników jest bezpieczeństwo. Jest to jeden z rodzajów wymagań niefunkcjonalnych . Bezpieczeństwo w Javie: najlepsze praktyki - 1Bezpieczeństwo składa się z wielu elementów. Oczywiście, aby w pełni omówić wszystkie znane zasady i działania ochronne, trzeba napisać więcej niż jeden artykuł, dlatego skupmy się na najważniejszych. Osoba dobrze zorientowana w tym temacie, będzie w stanie skonfigurować wszystkie procesy i zadbać o to, aby nie tworzyły nowych luk w zabezpieczeniach, będzie potrzebna w każdym zespole. Oczywiście nie powinieneś sądzić, że jeśli zastosujesz się do tych praktyk, aplikacja będzie całkowicie bezpieczna. NIE! Ale z nimi na pewno będzie bezpieczniej. Iść.

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

Po pierwsze, bezpieczeństwo w Javie zaczyna się już na poziomie funkcji językowych. To właśnie byśmy zrobili, gdyby nie było modyfikatorów dostępu?... Anarchia, nie mniej. Język programowania pomaga nam pisać bezpieczny kod, a także korzystać z wielu ukrytych funkcji bezpieczeństwa:
  1. Mocne pisanie. Java to język o typie statycznym, który umożliwia wykrywanie błędów typów w czasie wykonywania.
  2. Modyfikatory dostępu. Dzięki nim możemy skonfigurować dostęp do klas, metod i pól klas tak, jak potrzebujemy.
  3. Automatyczne zarządzanie pamięcią. W tym celu my (Javaiści ;)) mamy Garbage Collector, który uwalnia Cię od ręcznej konfiguracji. Tak, czasami pojawiają się problemy.
  4. Sprawdzanie kodu bajtowego: Java kompiluje się do kodu bajtowego, który jest sprawdzany przez środowisko wykonawcze przed jego uruchomieniem.
Między innymi istnieją zalecenia Oracle dotyczące bezpieczeństwa. Oczywiście nie jest ona napisana w „wysokim stylu” i przy jej czytaniu można zasnąć kilka razy, ale warto. Szczególnie ważnym dokumentem są Wytyczne dotyczące bezpiecznego kodowania dla Java SE , które zawierają wskazówki dotyczące pisania bezpiecznego kodu. Dokument ten zawiera wiele przydatnych informacji. Jeśli to możliwe, zdecydowanie warto przeczytać. Aby wzbudzić zainteresowanie tym materiałem, oto kilka interesujących wskazówek:
  1. Unikaj serializacji klas wrażliwych na bezpieczeństwo. W tym przypadku interfejs klasy można uzyskać z serializowanego pliku, nie mówiąc już o serializowanych danych.
  2. Staraj się unikać modyfikowalnych klas danych. Daje to wszystkie korzyści klas niezmiennych (np. bezpieczeństwo wątków). Jeśli istnieje obiekt zmienny, może to prowadzić do nieoczekiwanego zachowania.
  3. Utwórz kopie zwróconych obiektów zmiennych. Jeśli metoda zwraca odwołanie do wewnętrznego, modyfikowalnego obiektu, wówczas kod klienta może zmienić wewnętrzny stan obiektu.
  4. I tak dalej…
Ogólnie rzecz biorąc, Wytyczne dotyczące bezpiecznego kodowania dla Java SE zawierają zestaw porad i wskazówek dotyczących prawidłowego i bezpiecznego pisania kodu w Javie.

2. Wyeliminuj lukę w zabezpieczeniach polegającą na wstrzykiwaniu SQL

Unikalna luka. Jej wyjątkowość polega na tym, że jest to zarówno jedna z najsłynniejszych, jak i najpowszechniejszych luk. Jeśli nie interesuje Cię kwestia bezpieczeństwa, to nie będziesz o tym wiedział. Co to jest zastrzyk SQL? Jest to atak na bazę danych polegający na wstrzyknięciu dodatkowego kodu SQL tam, gdzie się tego nie oczekuje. Załóżmy, że mamy metodę, która pobiera pewien parametr do wysyłania zapytań do bazy danych. Na przykład nazwa użytkownika. Kod zawierający lukę będzie wyglądał mniej więcej tak:
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
   // Создается связь с базой данных
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

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

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

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

private List<User> mapToUsers(ResultSet resultSet) {
   //переводит в коллекцию юзеров
}
W tym przykładzie zapytanie sql jest przygotowane wcześniej w osobnej linii. Wydawałoby się, że w czym tkwi problem, prawda? Może problem polega na tym, że lepiej byłoby użyć String.format? NIE? Co wtedy? Postawmy się na miejscu testera i zastanówmy się, co można przekazać w wartości firstName. Na przykład:
  1. Możesz przekazać to, czego się oczekuje - nazwę użytkownika. Następnie baza danych zwróci wszystkich użytkowników o tej nazwie.
  2. Możesz przekazać pusty ciąg znaków: wówczas zwróceni zostaną wszyscy użytkownicy.
  3. Możesz też przekazać następujące polecenie: „”; DROP TABLE UŻYTKOWNIKÓW;”. I tu będą większe problemy. To zapytanie usunie tabelę z bazy danych. Ze wszystkimi danymi. WSZYSCY.
Czy możesz sobie wyobrazić, jakie problemy może to powodować? Wtedy możesz napisać co chcesz. Możesz zmienić nazwę wszystkich użytkowników, możesz usunąć ich adresy. Pole do sabotażu jest ogromne. Aby tego uniknąć, musisz przestać wstrzykiwać gotowe zapytanie i zamiast tego zbudować je za pomocą parametrów. To powinien być jedyny sposób wysyłania zapytań do bazy danych. W ten sposób możesz wyeliminować tę lukę. Przykład:
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
   // Создается связь с базой данных
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

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

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

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

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

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

private List<User> mapToUsers(ResultSet resultSet) {
   //переводим в коллекцию юзеров
}
W ten sposób unika się tej luki. Dla tych, którzy chcą głębiej zagłębić się w ten artykuł, oto świetny przykład . Skąd wiesz, czy rozumiesz tę część? Jeżeli poniższy żart stał się jasny, to jest to pewny znak, że istota luki jest jasna :D Bezpieczeństwo w Javie: najlepsze praktyki - 2

3. Skanuj i aktualizuj zależności

Co to znaczy? Dla tych, którzy nie wiedzą, czym jest zależność, wyjaśnię: jest to archiwum jar z kodem, które jest połączone z projektem za pomocą systemów automatycznego budowania (Maven, Gradle, Ant) w celu ponownego wykorzystania cudzego rozwiązania. Na przykład Project Lombok , który generuje dla nas gettery, settery itp. w czasie wykonywania. A jeśli mówimy o dużych aplikacjach, korzystają one z wielu różnych zależności. Niektóre są przechodnie (tzn. każda zależność może mieć swoje własne zależności itd.). Dlatego napastnicy coraz częściej zwracają uwagę na zależności typu open source, ponieważ są one regularnie używane i mogą powodować problemy dla wielu klientów. Ważne jest, aby upewnić się, że w całym drzewie zależności (i dokładnie tak to wygląda) nie są znane żadne luki. Można to zrobić na kilka sposobów.

Użyj Snyka do monitorowania

Narzędzie Snyk sprawdza wszystkie zależności projektu i sygnalizuje znane luki. Możesz tam zarejestrować i zaimportować swoje projekty, na przykład poprzez GitHub. Bezpieczeństwo w Javie: najlepsze praktyki - 3Ponadto, jak widać na powyższym obrazku, jeśli nowsza wersja zawiera rozwiązanie tej luki, Snyk zaproponuje to i utworzy żądanie ściągnięcia. Można go używać bezpłatnie w projektach open source. Projekty będą skanowane z określoną częstotliwością: raz w tygodniu, raz w miesiącu. Zarejestrowałem się i dodałem wszystkie moje publiczne repozytoria do skanowania Snyk (nie ma w tym nic niebezpiecznego: są już otwarte 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 oto kolejny: Bezpieczeństwo w Javie: najlepsze praktyki - 6Jest to więc doskonałe narzędzie do wyszukiwania podatności i monitorowania aktualizacji nowe wersje.

Skorzystaj z laboratorium bezpieczeństwa GitHub

Osoby pracujące na GitHubie mogą również skorzystać z wbudowanych w nie narzędzi. Więcej o tym podejściu możesz przeczytać w moim tłumaczeniu z ich bloga Announcement of GitHub Security Lab . To narzędzie jest oczywiście prostsze niż Snyk, ale zdecydowanie nie należy go zaniedbywać. Ponadto liczba znanych luk w zabezpieczeniach będzie tylko rosła, więc zarówno Snyk, jak i GitHub Security Lab będą się rozwijać i ulepszać.

Aktywuj Sonatype DepShield

Jeśli do przechowywania swoich repozytoriów używasz GitHuba, możesz dodać do swoich projektów jedną z aplikacji z MarketPlace - Sonatype DepShield. Za jego pomocą można także skanować projekty pod kątem zależności. Co więcej, jeśli coś znajdzie, zostanie utworzony problem GitHub z odpowiednim opisem, jak pokazano poniżej: Bezpieczeństwo w Javie: najlepsze praktyki - 7

4. Ostrożnie obchodź się z wrażliwymi danymi

Bezpieczeństwo w Javie: najlepsze praktyki - 8W mowie angielskiej częściej spotykane jest określenie „dane wrażliwe”. Ujawnienie danych osobowych, numerów kart kredytowych i innych danych osobowych klienta może spowodować nieodwracalne szkody. Przede wszystkim należy dokładnie przyjrzeć się projektowi aplikacji i ustalić, czy rzeczywiście potrzebne są jakiekolwiek dane. Być może niektóre z nich nie są potrzebne, ale zostały dodane ze względu na przyszłość, która nie nadeszła i prawdopodobnie nie nadejdzie. Ponadto podczas logowania projektu takie dane mogą wyciekać. Prostym sposobem zapobiegania przedostawaniu się wrażliwych danych do dzienników jest wyczyszczenie metod toString()jednostek domeny (takich jak użytkownik, uczeń, nauczyciel itd.). Zapobiegnie to przypadkowemu wydrukowaniu wrażliwych pól. Jeśli używasz Lomboka do wygenerowania metody toString(), możesz użyć adnotacji @ToString.Exclude, aby zapobiec użyciu pola w wynikach metody toString(). Należy także zachować szczególną ostrożność podczas udostępniania danych światu zewnętrznemu. Na przykład istnieje punkt końcowy http, który wyświetla nazwy wszystkich użytkowników. Nie ma potrzeby pokazywania wewnętrznego unikalnego identyfikatora użytkownika. Dlaczego? Ponieważ za jego pomocą atakujący może uzyskać inne, bardziej poufne informacje o każdym użytkowniku. Na przykład, jeśli używasz Jacksona do serializacji i deserializacji POJO do formatu JSON , możesz użyć adnotacji @JsonIgnorei @JsonIgnoreProperties, aby zapobiec serializacji i deserializacji określonych pól. Ogólnie rzecz biorąc, musisz używać różnych klas POJO dla różnych miejsc. Co to znaczy?
  1. Do pracy z bazą danych używaj wyłącznie POJO - Entity.
  2. Aby pracować z logiką biznesową, przenieś Entity do Model.
  3. Aby współpracować ze światem zewnętrznym i wysyłać żądania http, korzystaj z podmiotów trzecich - DTO.
W ten sposób można 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ć, musisz użyć szyfrowania. W zależności od zadania musisz zdecydować, jakiego rodzaju szyfrowania użyć. Co więcej, silniejsze szyfrowanie zajmuje więcej czasu, więc ponownie musisz rozważyć, w jakim stopniu jego potrzeba uzasadnia czas poświęcony na to. Algorytm możesz oczywiście napisać samodzielnie. Ale to jest niepotrzebne. Można 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, jak z niego korzystać, korzystając z przykładu szyfrowania w jeden i drugi sposób:
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));
}

Szyfrowanie hasła

Do tego zadania najbezpieczniej jest zastosować szyfrowanie asymetryczne. Dlaczego? Ponieważ aplikacja naprawdę nie musi ponownie odszyfrowywać haseł. To jest ogólne podejście. W rzeczywistości, gdy użytkownik wprowadza hasło, system szyfruje je i porównuje z zawartością magazynu haseł. Szyfrowanie odbywa się przy użyciu tych samych środków, więc możesz się spodziewać, że będą zgodne (oczywiście jeśli wpiszesz prawidłowe hasło ;)). Do tego celu nadają się BCrypt i SCrypt. Obie są funkcjami jednokierunkowymi (skrótami kryptograficznymi) ze złożonymi obliczeniowo algorytmami, które zajmują dużo czasu. Właśnie tego potrzebujesz, ponieważ bezpośrednie rozszyfrowanie zajmie całą wieczność. Na przykład Spring Security obsługuje szereg algorytmów. SCryptPasswordEncoderMożesz także użyć BCryptPasswordEncoder. To, co jest obecnie silnym algorytmem szyfrowania, w przyszłym roku może okazać się słabe. W rezultacie dochodzimy do wniosku, że konieczne jest sprawdzenie stosowanych algorytmów i aktualizacja bibliotek o algorytmy.

Zamiast wyjścia

Dziś rozmawialiśmy o bezpieczeństwie i oczywiście wiele rzeczy pozostało za kulisami. Właśnie otworzyłem dla ciebie drzwi do nowego świata: świata, który żyje własnym życiem. Z bezpieczeństwem jest tak samo, jak z polityką: jeśli nie zaangażujesz się w politykę, polityka zaangażuje się w ciebie. Tradycyjnie proponuję subskrybować moje konto Github . Zamieszczam tam swoje prace dotyczące różnych technologii, które studiuję i stosuję w pracy.

Przydatne linki

Tak, prawie wszystkie artykuły na stronie są napisane w języku angielskim. Czy nam się to podoba, czy nie, angielski jest językiem, w którym programiści porozumiewają się. Wszystkie najnowsze artykuły, książki i czasopisma dotyczące programowania są pisane w języku angielskim. Dlatego moje linki do rekomendacji są głównie w języku angielskim:
  1. Habr: Wstrzykiwanie SQL dla początkujących
  2. Oracle: Centrum zasobów dotyczących bezpieczeństwa Java
  3. Oracle: Wytyczne dotyczące 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 w zakresie bezpieczeństwa Java
  7. JR: Ogłoszenie GitHub Security Lab: wspólna ochrona całego kodu
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION