CodeGym /Blog Java /Aleatoriu /Securitate în Java: cele mai bune practici
John Squirrels
Nivel
San Francisco

Securitate în Java: cele mai bune practici

Publicat în grup
Una dintre cele mai importante valori în aplicațiile server este securitatea. Acesta este un tip de cerință nefuncțională . Securitate în Java: cele mai bune practici - 1Securitatea include multe componente. Desigur, ar fi nevoie de mai mult de un articol pentru a acoperi pe deplin toate principiile și măsurile de securitate cunoscute, așa că ne vom opri asupra celor mai importante. O persoană bine versată în acest subiect poate configura toate procesele relevante, poate evita crearea de noi găuri de securitate și va fi necesară în orice echipă. Desigur, nu ar trebui să credeți că aplicația dvs. va fi 100% sigură dacă urmați aceste practici. Nu! Dar cu siguranță va fi mai sigur cu ei. Să mergem.

1. Asigurați securitate la nivelul limbajului Java

În primul rând, securitatea în Java începe chiar de la nivelul capacităţilor limbajului. Ce am face dacă nu ar exista modificatori de acces? Nu ar fi altceva decât anarhie. Limbajul de programare ne ajută să scriem cod securizat și, de asemenea, face uz de multe caracteristici implicite de securitate:
  1. Tastare puternică. Java este un limbaj tipizat static. Acest lucru face posibilă detectarea erorilor legate de tip în timpul execuției.
  2. Modificatori de acces. Acestea ne permit să personalizăm accesul la clase, metode și câmpuri după cum este necesar.
  3. Gestionarea automată a memoriei. Pentru aceasta, dezvoltatorii Java au un colector de gunoi care ne eliberează de a fi nevoiți să configurați totul manual. Da, uneori apar probleme.
  4. Verificare bytecode : Java este compilat în bytecode, care este verificat de runtime înainte de a fi executat.
În plus, există recomandările de securitate ale Oracle . Desigur, nu este scris într-un limbaj înalt și s-ar putea să adormi de mai multe ori în timp ce o citești, dar merită. În special, documentul intitulat Secure Coding Guidelines for Java SE este important. Oferă sfaturi despre cum să scrieți codul securizat. Acest document transmite o cantitate imensă de informații extrem de utile. Dacă aveți ocazia, cu siguranță ar trebui să o citiți. Pentru a vă stârni interesul pentru acest material, iată câteva sfaturi interesante:
  1. Evitați serializarea claselor sensibile la securitate. Serializarea expune interfața de clasă în fișierul serializat, ca să nu mai vorbim de datele care sunt serializate.
  2. Încercați să evitați clasele mutabile pentru date. Acest lucru oferă toate beneficiile claselor imuabile (de exemplu, siguranța firelor). Dacă aveți un obiect mutabil, acesta poate duce la un comportament neașteptat.
  3. Faceți copii ale obiectelor mutabile returnate. Dacă o metodă returnează o referință la un obiect intern mutabil, atunci codul clientului ar putea schimba starea internă a obiectului.
  4. Și așa mai departe…
Practic, Secure Coding Guidelines for Java SE este o colecție de sfaturi și trucuri despre cum să scrieți codul Java corect și sigur.

2. Eliminați vulnerabilitățile de injectare SQL

Acesta este un tip special de vulnerabilitate. Este special pentru că este atât una dintre cele mai cunoscute, cât și una dintre cele mai comune vulnerabilități. Dacă nu ați fost niciodată interesat de securitatea computerelor, atunci nu veți ști despre asta. Ce este injectarea SQL? Acesta este un atac de bază de date care implică injectarea de cod SQL suplimentar acolo unde nu este așteptat. Să presupunem că avem o metodă care acceptă un fel de parametru pentru a interoga baza de date. De exemplu, un nume de utilizator. Codul vulnerabil ar arăta cam așa:

// 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
}
În acest exemplu, o interogare SQL este pregătită în avans pe o linie separată. Deci care este problema, nu? Poate că problema este că ar fi mai bine să folosiți String.format ? Nu? Ei bine, atunci ce? Să ne punem în locul unui tester și să ne gândim la ceea ce ar putea fi trecut drept valoare pentru firstName . De exemplu:
  1. Putem transmite ceea ce este de așteptat - un nume de utilizator. Apoi baza de date va returna toți utilizatorii cu acest nume.
  2. Putem trece un șir gol. Apoi toți utilizatorii vor fi returnați.
  3. Dar putem trece și următoarele: "'; DROP TABLE USERS;". Și iată că acum avem uuuuuuge probleme. Această interogare va șterge un tabel din baza de date. Împreună cu toate datele. TOTUL.
Vă puteți imagina problemele pe care le-ar cauza asta? Dincolo de asta, poți scrie orice vrei. Puteți schimba numele tuturor utilizatorilor. Le puteți șterge adresele. Spațiul pentru sabotaj este imens. Pentru a evita acest lucru, trebuie să preveniți injectarea unei interogări gata făcute și, în schimb, să formați interogarea folosind parametri. Aceasta ar trebui să fie singura modalitate de a crea interogări la baza de date. Acesta este modul în care puteți elimina această vulnerabilitate. De exemplu:

// 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
}
Astfel, vulnerabilitatea este evitată. Pentru cei care doresc să se aprofundeze în acest articol, iată un exemplu grozav . De unde știi când înțelegi această vulnerabilitate? Dacă înțelegi gluma din benzile desenate de mai jos, atunci probabil că ai o înțelegere clară despre ce este această vulnerabilitate :DSecuritate în Java: cele mai bune practici - 2

3. Scanați dependențele și mențineți-le actualizate

Ce înseamnă asta? Dacă nu știi ce este o dependență, îți explic. O dependență este o arhivă JAR cu cod care este conectată la un proiect utilizând sisteme automate de construire (Maven, Gradle, Ant) pentru a reutiliza soluția altcuiva. De exemplu, Project Lombok , care generează getters, setters etc. pentru noi în timpul de execuție. Aplicațiile mari pot avea multe și multe dependențe. Unele sunt tranzitive (adică fiecare dependență poate avea propriile sale dependențe și așa mai departe). Drept urmare, atacatorii acordă din ce în ce mai multă atenție dependențelor open-source, deoarece acestea sunt utilizate în mod regulat și mulți clienți pot avea probleme din cauza lor. Este important să vă asigurați că nu există vulnerabilități cunoscute în întregul arbore de dependență (da, arată ca un arbore). Există mai multe moduri de a face acest lucru.

Utilizați Snyk pentru monitorizarea dependenței

Snyk verifică toate dependențele proiectului și semnalează vulnerabilitățile cunoscute. Vă puteți înregistra pe Snyk și vă puteți importa proiectele prin GitHub. Securitate în Java: cele mai bune practici - 3De asemenea, după cum puteți vedea din imaginea de mai sus, dacă o vulnerabilitate este remediată într-o versiune mai nouă, atunci Snyk va oferi remedierea și va crea o cerere de extragere. Îl puteți folosi gratuit pentru proiecte open-source. Proiectele sunt scanate la intervale regulate, de exemplu o dată pe săptămână, o dată pe lună. M-am înregistrat și am adăugat toate depozitele mele publice la scanarea Snyk (nu este nimic periculos în acest sens, deoarece sunt deja publice pentru toată lumea). Snyk a arătat apoi rezultatul scanării: Securitate în Java: cele mai bune practici - 4Și după un timp, Snyk-bot a pregătit mai multe cereri de extragere în proiecte în care dependențele trebuie actualizate: Securitate în Java: cele mai bune practici - 5Și, de asemenea:Securitate în Java: cele mai bune practici - 6Acesta este un instrument excelent pentru găsirea vulnerabilităților și monitorizarea actualizărilor pentru noile versiuni.

Utilizați GitHub Security Lab

Oricine lucrează pe GitHub poate profita de instrumentele sale încorporate. Puteți citi mai multe despre această abordare în postarea lor de blog intitulată Anunțarea GitHub Security Lab . Acest instrument, desigur, este mai simplu decât Snyk, dar cu siguranță nu ar trebui să îl neglijezi. În plus, numărul vulnerabilităților cunoscute va crește, așa că atât Snyk, cât și GitHub Security Lab vor continua să se extindă și să se îmbunătățească.

Activați Sonatype DepShield

Dacă utilizați GitHub pentru a vă stoca depozitele, puteți adăuga Sonatype DepShield, una dintre aplicațiile din MarketPlace, la proiectele dvs. Poate fi folosit și pentru a scana proiecte pentru dependențe. În plus, dacă găsește ceva, va fi generată o problemă GitHub cu o descriere adecvată, așa cum se arată mai jos:Securitate în Java: cele mai bune practici - 7

4. Manipulați cu grijă datele confidențiale

Alternativ, am putea folosi expresia „date sensibile”. Scurgerea informațiilor personale ale unui client, a numerelor de card de credit și a altor informații sensibile poate provoca daune ireparabile. Mai întâi de toate, aruncați o privire atentă asupra designului aplicației dvs. și stabiliți dacă aveți într-adevăr nevoie de aceasta sau de acele date. Poate că de fapt nu aveți nevoie de unele dintre datele pe care le aveți - date care au fost adăugate pentru un viitor care nu a venit și este puțin probabil să vină. În plus, mulți vă scurgeți din neatenție astfel de date prin înregistrare. O modalitate ușoară de a preveni intrarea datelor sensibile în jurnalele dvs. este să curățați metodele toString() ale entităților de domeniu (cum ar fi Utilizator, Student, Profesor etc.). Acest lucru vă va împiedica să afișați accidental câmpuri confidențiale. Dacă utilizați Lombok pentru a genera toString()metoda, puteți utiliza adnotarea @ToString.Exclude pentru a preveni utilizarea unui câmp în ieșirea metodei toString() . De asemenea, fiți foarte atenți când trimiteți date către lumea exterioară. Să presupunem că avem un punct final HTTP care arată numele tuturor utilizatorilor. Nu este nevoie să afișați ID-ul intern unic al unui utilizator. De ce? Pentru că un atacator l-ar putea folosi pentru a obține alte informații mai sensibile despre utilizator. De exemplu, dacă utilizați Jackson pentru a serializa/deserializa un POJO către/de la JSON , atunci puteți utiliza @JsonIgnore și @JsonIgnorePropertiesadnotări pentru a preveni serializarea/deserializarea câmpurilor specifice. În general, trebuie să utilizați diferite clase POJO în locuri diferite. Ce înseamnă asta?
  1. Când lucrați cu o bază de date, utilizați un tip de POJO (o entitate).
  2. Când lucrați cu logica de afaceri, convertiți o entitate într-un model.
  3. Când lucrați cu lumea exterioară și trimiteți solicitări HTTP, utilizați diferite entități (DTO).
În acest fel puteți defini clar ce câmpuri vor fi vizibile din exterior și care nu.

Utilizați algoritmi puternici de criptare și hashing

Datele confidențiale ale clienților trebuie să fie stocate în siguranță. Pentru a face acest lucru, trebuie să folosim criptarea. În funcție de sarcină, trebuie să decideți ce tip de criptare să utilizați. În plus, o criptare mai puternică necesită mai mult timp, așa că din nou trebuie să luați în considerare cât de mult justifică necesitatea ei timpul petrecut cu ea. Desigur, puteți scrie singur un algoritm de criptare. Dar acest lucru este inutil. Puteți utiliza soluțiile existente în acest domeniu. De exemplu, 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>
Să vedem ce să facem, folosind acest exemplu care implică criptarea și decriptarea:

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

Criptarea parolelor

Pentru această sarcină, cel mai sigur este să utilizați criptarea asimetrică. De ce? Pentru că aplicația nu are nevoie să decripteze parolele. Aceasta este abordarea standard. În realitate, atunci când un utilizator introduce o parolă, sistemul o criptează și o compară cu ceea ce există în depozitul de parole. Se efectuează același proces de criptare, așa că ne putem aștepta ca acestea să se potrivească, dacă se introduce parola corectă, desigur :) BCrypt și SCrypt sunt potrivite aici. Ambele sunt funcții unidirecționale (hash-uri criptografice) cu algoritmi complecși din punct de vedere computațional care durează mult timp. Este exact ceea ce avem nevoie, deoarece calculele directe vor dura pentru totdeauna (ei bine, mult, mult timp). Spring Security acceptă o întreagă gamă de algoritmi. Putem folosi SCryptPasswordEncoder și BCryptPasswordEncoder. Ceea ce este considerat în prezent un algoritm de criptare puternic poate fi considerat slab anul viitor. Ca urmare, concluzionăm că ar trebui să verificăm în mod regulat algoritmii pe care îi folosim și, după caz, să actualizăm bibliotecile care conțin algoritmii de criptare.

În loc de concluzie

Astăzi am vorbit despre securitate și, firește, multe lucruri au rămas în culise. Tocmai am deschis ușa către o lume nouă pentru tine, o lume care are o viață proprie. Securitatea este la fel ca politica: dacă nu te ocupi cu politică, politica se va ocupa cu tine. În mod tradițional, vă sugerez să mă urmăriți pe contul GitHub . Acolo îmi postez creațiile care implică diverse tehnologii pe care le studiez și le aplic la locul de muncă.

Link-uri utile

  1. Guru99: Tutorial de injectare SQL
  2. Oracle: Centrul de resurse de securitate Java
  3. Oracle: Secure Coding Guidelines for Java SE
  4. Baeldung: Bazele securității Java
  5. Medie: 10 sfaturi pentru a vă spori securitatea Java
  6. Snyk: 10 cele mai bune practici de securitate Java
  7. GitHub: Anunțul GitHub Security Lab: securizarea codului lumii, împreună
Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION