W aplikacjach serwerowych jednym z najważniejszych wskaźników jest bezpieczeństwo. Jest to jeden z rodzajów wymagań niefunkcjonalnych . Bezpieczeń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:- Mocne pisanie. Java to język o typie statycznym, który umożliwia wykrywanie błędów typów w czasie wykonywania.
- Modyfikatory dostępu. Dzięki nim możemy skonfigurować dostęp do klas, metod i pól klas tak, jak potrzebujemy.
- 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.
- Sprawdzanie kodu bajtowego: Java kompiluje się do kodu bajtowego, który jest sprawdzany przez środowisko wykonawcze przed jego uruchomieniem.
- 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.
- 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.
- 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.
- I tak dalej…
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:
- Możesz przekazać to, czego się oczekuje - nazwę użytkownika. Następnie baza danych zwróci wszystkich użytkowników o tej nazwie.
- Możesz przekazać pusty ciąg znaków: wówczas zwróceni zostaną wszyscy użytkownicy.
- 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.
// Метод достает из базы данных всех пользователей с определенным именем
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
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. Ponadto, 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: Po chwili Snyk-bot przygotował kilka żądań ściągnięcia w projektach, w których należy zaktualizować zależności: A oto kolejny: Jest 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:4. Ostrożnie obchodź się z wrażliwymi danymi
W 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 metodtoString()
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 @JsonIgnore
i @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?
- Do pracy z bazą danych używaj wyłącznie POJO - Entity.
- Aby pracować z logiką biznesową, przenieś Entity do Model.
- Aby współpracować ze światem zewnętrznym i wysyłać żądania http, korzystaj z podmiotów trzecich - DTO.
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.SCryptPasswordEncoder
Moż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:- Habr: Wstrzykiwanie SQL dla początkujących
- Oracle: Centrum zasobów dotyczących bezpieczeństwa Java
- Oracle: Wytyczne dotyczące bezpiecznego kodowania dla Java SE
- Baeldung: Podstawy bezpieczeństwa Java
- Średni: 10 wskazówek, jak zwiększyć bezpieczeństwo Java
- Snyk: 10 najlepszych praktyk w zakresie bezpieczeństwa Java
- JR: Ogłoszenie GitHub Security Lab: wspólna ochrona całego kodu
GO TO FULL VERSION