CodeGym/Java блог/Случаен/Сигурност в Java: най-добри практики
John Squirrels
Ниво
San Francisco

Сигурност в Java: най-добри практики

Публикувано в групата
Един от най-важните показатели в сървърните applications е сигурността. Това е вид нефункционално изискване . Сигурност в Java: най-добри практики - 1Сигурността включва много компоненти. Разбира се, ще са необходими повече от една статия, за да се покрият напълно всички известни принципи и мерки за сигурност, така че ще се спрем на най-важните. Човек, който е добре запознат с тази тема, може да настрои всички съответни процеси, да избегне създаването на нови дупки в сигурността и ще бъде необходим във всеки екип. Разбира се, не трябва да мислите, че вашето приложение ще бъде 100% защитено, ако следвате тези практики. Не! Но определено с тях ще е по-сигурно. Да тръгваме.

1. Осигурете сигурност на нивото на езика Java

На първо място, сигурността в Java започва точно на нивото на възможностите на езика. Какво щяхме да правим, ако нямаше модификатори за достъп? Нямаше да има нищо друго освен анархия. Езикът за програмиране ни помага да пишем защитен code и също така използва много неявни функции за сигурност:
  1. Силно писане. Java е статично въведен език. Това прави възможно улавянето на грешки, свързани с типа, по време на изпълнение.
  2. Модификатори за достъп. Те ни позволяват да персонализираме достъпа до класове, методи и полета според нуждите.
  3. Автоматично управление на паметта. За тази цел разработчиците на Java имат събирач на отпадъци, който ни освобождава от необходимостта да конфигурираме всичко ръчно. Да, понякога възникват проблеми.
  4. Проверка на byte code : Java се компorра в byte code, който се проверява от средата за изпълнение, преди да бъде изпълнен.
Освен това има препоръки за сигурност на Oracle . Разбира се, не е написано високомерно и може да заспите няколко пъти, докато го четете, но си заслужава. По-специално, важен е documentът, озаглавен Насоки за безопасно codeиране за Java SE . Предоставя съвети How да пишете защитен code. Този document предоставя огромно количество изключително полезна информация. Ако имате възможност, определено трябва да го прочетете. За да предизвикате интереса си към този материал, ето няколко интересни съвета:
  1. Избягвайте сериализирането на чувствителни към сигурността класове. Сериализацията разкрива интерфейса на класа в сериализирания файл, да не говорим за данните, които са сериализирани.
  2. Опитайте се да избягвате променливи класове за данни. Това осигурява всички предимства на неизменните класове (напр. безопасност на нишки). Ако имате променлив обект, това може да доведе до неочаквано поведение.
  3. Направете копия на върнатите променливи обекти. Ако даден метод върне препратка към вътрешен променлив обект, тогава клиентският code може да промени вътрешното състояние на обекта.
  4. И така нататък…
По принцип Указанията за безопасно codeиране за Java SE са колекция от съвети и трикове за това How да пишете Java code правилно и сигурно.

2. Елиминирайте уязвимостите на SQL инжектиране

Това е особен вид уязвимост. Той е специален, защото е едновременно една от най-известните и една от най-често срещаните уязвимости. Ако никога не сте се интересували от компютърна сигурност, тогава няма да знаете за това. Какво е SQL инжектиране? Това е атака на база данни, която включва инжектиране на допълнителен SQL code там, където не се очаква. Да предположим, че имаме метод, който приема няHowъв параметър за заявка към базата данни. Например потребителско име. Уязвимият code би изглеждал така:
// 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
}
В този пример SQL заявка се подготвя предварително на отделен ред. И така, Howъв е проблемът, нали? Може би проблемът е, че би било по-добре да използвате String.format ? Не? Е, Howво тогава? Нека се поставим на мястото на тестер и да помислим Howво може да бъде предадено като стойност на firstName . Например:
  1. Можем да предадем това, което се очаква - потребителско име. Тогава базата данни ще върне всички потребители с това име.
  2. Можем да предадем празен низ. Тогава всички потребители ще бъдат върнати.
  3. Но можем също така да предадем следното: "'; DROP TABLE USERS;". И тук вече имаме огромни проблеми. Тази заявка ще изтрие table от базата данни. Заедно с всички данни. ВСИЧКОТО.
Можете ли да си представите Howви проблеми би причинило това? Освен това можете да пишете Howвото искате. Можете да промените имената на всички потребители. Можете да изтриете техните addressи. Възможностите за саботаж са огромни. За да избегнете това, трябва да предотвратите инжектирането на готова заявка и instead of това да формирате заявката с помощта на параметри. Това трябва да е единственият начин за създаване на заявки към база данни. Ето How можете да премахнете тази уязвимост. Например:
// 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
}
По този начин се избягва уязвимостта. За тези, които искат да се потопят по-дълбоко в тази статия, ето страхотен пример . Как да разберете кога разбирате тази уязвимост? Ако разберете шегата в комикса по-долу, тогава вероятно имате ясна представа за Howво е тази уязвимост :DСигурност в Java: най-добри практики - 2

3. Сканирайте зависимостите и ги поддържайте актуализирани

Какво означава това? Ако не знаете Howво е зависимост, ще обясня. Зависимостта е JAR архив с code, който е свързан към проект с помощта на системи за автоматично изграждане (Maven, Gradle, Ant), за да се използва повторно нечие решение. Например Project Lombok , който генерира гетери, сетери и т.н. за нас по време на изпълнение. Големите applications могат да имат много и много зависимости. Някои са преходни (т.е. всяка зависимост може да има свои собствени зависимости и т.н.). В резултат на това атакуващите все повече обръщат внимание на зависимостите с отворен code, тъй като те се използват редовно и много клиенти могат да имат проблеми поради тях. Важно е да се уверите, че няма известни уязвимости в цялото дърво на зависимостите (да, изглежда като дърво). Има няколко начина да направите това.

Използвайте Snyk за наблюдение на зависимостите

Snyk проверява всички зависимости на проекта и маркира известни уязвимости. Можете да се регистрирате в Snyk и да импортирате проектите си чрез GitHub. Сигурност в Java: най-добри практики - 3Освен това, Howто можете да видите от снимката по-горе, ако дадена уязвимост е коригирана в по-нова version, тогава Snyk ще предложи корекцията и ще създаде заявка за изтегляне. Можете да го използвате безплатно за проекти с отворен code. Проектите се сканират на редовни интервали, например веднъж седмично, веднъж месечно. Регистрирах се и добавих всичките си публични хранorща към сканирането на Snyk (няма нищо опасно в това, тъй като те вече са публични за всички). След това Snyk показа резултата от сканирането: Сигурност в Java: най-добри практики - 4И след известно време Snyk-bot подготви няколко заявки за изтегляне в проекти, където зависимостите трябва да бъдат актуализирани: Сигурност в Java: най-добри практики - 5И също така:Сигурност в Java: най-добри практики - 6Това е чудесен инструмент за намиране на уязвимости и наблюдение на актуализации за нови версии.

Използвайте GitHub Security Lab

Всеки, който работи с GitHub, може да се възползва от вградените му инструменти. Можете да прочетете повече за този подход в техния блог пост, озаглавен Обявяване на GitHub Security Lab . Този инструмент, разбира се, е по-прост от Snyk, но определено не трябва да го пренебрегвате. Нещо повече, броят на известните уязвимости само ще расте, така че Snyk и GitHub Security Lab ще продължат да се разширяват и подобряват.

Активирайте Sonatype DepShield

Ако използвате GitHub за съхранение на вашите хранorща, можете да добавите Sonatype DepShield, едно от applicationsта в MarketPlace, към вашите проекти. Може да се използва и за сканиране на проекти за зависимости. Освен това, ако открие нещо, ще бъде генериран проблем на GitHub с подходящо описание, Howто е показано по-долу:Сигурност в Java: най-добри практики - 7

4. Работете внимателно с поверителни данни

Като алтернатива можем да използваме фразата „чувствителни данни“. Изтичането на лична информация на клиента, номера на кредитни карти и друга чувствителна информация може да причини непоправима вреда. Първо, разгледайте внимателно дизайна на вашето приложение и определете дали наистина имате нужда от тези or онези данни. Може би всъщност не се нуждаете от някои от данните, които имате – данни, които са добавени за бъдеще, което не е дошло и е малко вероятно да дойде. Освен това много от вас неволно изтичат такива данни чрез регистриране. Лесен начин да предотвратите влизането на чувствителни данни във вашите регистрационни файлове е да изчистите методите toString() на обекти на домейн (като потребител, ученик, учител и т.н.). Това ще ви предпази от случайно извеждане на поверителни полета. Ако използвате Lombok за генериране на toString()можете да използвате анотацията @ToString.Exclude , за да предотвратите използването на поле в изхода на метода toString() . Освен това бъдете много внимателни, когато изпращате данни към външния свят. Да предположим, че имаме HTTP крайна точка, която показва имената на всички потребители. Не е необходимо да се показва уникален вътрешен идентификатор на потребителя. Защо? Тъй като нападателят може да го използва, за да получи друга, по-чувствителна информация за потребителя. Например, ако използвате Jackson за сериализиране/десериализиране на POJO към/от JSON , тогава можете да използвате @JsonIgnore и @JsonIgnorePropertiesанотации за предотвратяване на сериализация/десериализация на конкретни полета. Като цяло трябва да използвате различни POJO класове на различни места. Какво означава това?
  1. Когато работите с база данни, използвайте един тип POJO (субект).
  2. Когато работите с бизнес логика, преобразувайте обект в модел.
  3. Когато работите с външния свят и изпращате HTTP заявки, използвайте различни обекти (DTO).
По този начин можете ясно да определите кои полета ще се виждат отвън и кои не.

Използвайте силни алгоритми за криптиране и хеширане

Поверителните данни на клиентите трябва да се съхраняват сигурно. За да направим това, трябва да използваме криптиране. В зависимост от задачата трябва да решите кой тип криптиране да използвате. Освен това по-силното криптиране отнема повече време, така че отново трябва да прецените доколко необходимостта от него оправдава времето, прекарано в него. Разбира се, можете сами да напишете алгоритъм за криптиране. Но това е излишно. Можете да използвате съществуващите решения в тази област. Например 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>
Нека да видим Howво да правим, използвайки този пример, включващ криптиране и декриптиране:
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));
}

Шифроване на пароли

За тази задача е най-безопасно да използвате асиметрично криптиране. Защо? Тъй като приложението всъщност не се нуждае от дешифриране на пароли. Това е стандартният подход. В действителност, когато потребител въведе парола, системата я криптира и я сравнява с това, което съществува в хранorщето за пароли. Извършва се същият процес на криптиране, така че можем да очакваме, че ще съвпаднат, ако е въведена правилната парола, разбира се :) BCrypt и SCrypt са подходящи тук. И двете са еднопосочни функции (криптографски хешове) с изчислително сложни алгоритми, които отнемат много време. Това е точно това, от което се нуждаем, тъй като директните изчисления ще отнемат цяла вечност (е, много, много време). Spring Security поддържа цял набор от алгоритми. Можем да използваме SCryptPasswordEncoder и BCryptPasswordEncoder. Това, което в момента се счита за силен алгоритъм за криптиране, може да се счита за слабо през следващата година. В резултат на това заключаваме, че трябва редовно да проверяваме използваните от нас алгоритми и, ако е необходимо, да актуализираме библиотеките, които съдържат алгоритмите за криптиране.

Вместо заключение

Днес говорихме за сигурност и естествено много неща останаха зад кулисите. Току-що отворих вратата към един нов свят за теб, свят, който има свой собствен живот. Сигурността е точно като политиката: ако не се занимавате с политика, политиката ще се занимава сама с вас. Традиционно предлагам да ме последвате в акаунта в GitHub . Там публикувам своите творения, включващи различни технологии, които изучавам и прилагам в работата си.

Полезни връзки

  1. Guru99: Урок за инжектиране на SQL
  2. Oracle: Център за ресурси за сигурност на Java
  3. Oracle: Насоки за безопасно codeиране за Java SE
  4. Baeldung: Основите на сигурността на Java
  5. Среден: 10 съвета за повишаване на сигурността на Java
  6. Snyk: 10 най-добри практики за сигурност на Java
  7. GitHub: Обявяване на GitHub Security Lab: защита на codeа на света заедно
Коментари
  • Популярен
  • Нов
  • Стар
Трябва да сте влезли, за да оставите коментар
Тази страница все още няма коментари