CodeGym /Java Blog /Random-IT /Sicurezza in Java: best practice
John Squirrels
Livello 41
San Francisco

Sicurezza in Java: best practice

Pubblicato nel gruppo Random-IT
Una delle metriche più importanti nelle applicazioni server è la sicurezza. Questo è un tipo di requisito non funzionale . Sicurezza in Java: best practice - 1La sicurezza include molti componenti. Naturalmente, ci vorrebbe più di un articolo per coprire completamente tutti i principi di sicurezza e le misure di sicurezza conosciuti, quindi ci soffermeremo sui più importanti. Una persona esperta in questo argomento può impostare tutti i processi pertinenti, evitare di creare nuove falle di sicurezza e sarà necessaria in qualsiasi squadra. Ovviamente, non dovresti pensare che la tua applicazione sarà sicura al 100% se segui queste pratiche. NO! Ma sarà sicuramente più sicuro con loro. Andiamo.

1. Fornire sicurezza a livello del linguaggio Java

Prima di tutto, la sicurezza in Java inizia proprio a livello delle capacità del linguaggio. Cosa faremmo se non ci fossero modificatori di accesso? Non ci sarebbe altro che anarchia. Il linguaggio di programmazione ci aiuta a scrivere codice sicuro e utilizza anche molte funzionalità di sicurezza implicite:
  1. Digitazione forte. Java è un linguaggio tipizzato staticamente. Ciò rende possibile rilevare gli errori relativi al tipo in fase di esecuzione.
  2. Modificatori di accesso. Questi ci consentono di personalizzare l'accesso a classi, metodi e campi secondo necessità.
  3. Gestione automatica della memoria. Per questo, gli sviluppatori Java hanno un Garbage Collector che ci libera dal dover configurare tutto manualmente. Sì, a volte sorgono problemi.
  4. Verifica bytecode : Java viene compilato in bytecode, che viene controllato dal runtime prima di essere eseguito.
Inoltre, ci sono raccomandazioni sulla sicurezza di Oracle . Certo, non è scritto in un linguaggio alto e potresti addormentarti più volte mentre lo leggi, ma ne vale la pena. In particolare, è importante il documento intitolato Secure Coding Guidelines for Java SE . Fornisce consigli su come scrivere codice sicuro. Questo documento contiene un'enorme quantità di informazioni estremamente utili. Se ne hai la possibilità, dovresti assolutamente leggerlo. Per alimentare il tuo interesse per questo materiale, ecco alcuni suggerimenti interessanti:
  1. Evitare la serializzazione di classi sensibili alla sicurezza. La serializzazione espone l'interfaccia della classe nel file serializzato, per non parlare dei dati che vengono serializzati.
  2. Cerca di evitare classi mutabili per i dati. Ciò fornisce tutti i vantaggi delle classi immutabili (ad esempio thread safety). Se hai un oggetto mutabile, può portare a un comportamento imprevisto.
  3. Crea copie degli oggetti mutabili restituiti. Se un metodo restituisce un riferimento a un oggetto mutabile interno, il codice client potrebbe modificare lo stato interno dell'oggetto.
  4. E così via…
Fondamentalmente, Linee guida per la codifica sicura per Java SE è una raccolta di suggerimenti e trucchi su come scrivere codice Java in modo corretto e sicuro.

2. Elimina le vulnerabilità di SQL injection

Questo è un tipo speciale di vulnerabilità. È speciale perché è sia una delle vulnerabilità più famose che una delle più comuni. Se non sei mai stato interessato alla sicurezza informatica, allora non lo saprai. Cos'è l'iniezione SQL? Questo è un attacco al database che comporta l'iniezione di codice SQL aggiuntivo dove non è previsto. Supponiamo di avere un metodo che accetti una sorta di parametro per interrogare il database. Ad esempio, un nome utente. Il codice vulnerabile sarebbe simile a questo:

// 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
}
In questo esempio, una query SQL viene preparata in anticipo su una riga separata. Quindi qual è il problema, giusto? Forse il problema è che sarebbe meglio usare String.format ? NO? Bene, allora cosa? Mettiamoci nei panni di un tester e pensiamo a cosa potrebbe essere passato come valore di firstName . Per esempio:
  1. Possiamo trasmettere ciò che ci si aspetta: un nome utente. Quindi il database restituirà tutti gli utenti con quel nome.
  2. Possiamo passare una stringa vuota. Quindi tutti gli utenti verranno restituiti.
  3. Ma possiamo anche passare quanto segue: "'; DROP TABLE USERS;". E qui ora abbiamo enormi problemi. Questa query eliminerà una tabella dal database. Insieme a tutti i dati. TUTTO.
Riesci a immaginare i problemi che ciò causerebbe? Oltre a ciò, puoi scrivere quello che vuoi. Puoi cambiare i nomi di tutti gli utenti. Puoi eliminare i loro indirizzi. Le possibilità di sabotaggio sono immense. Per evitare ciò, è necessario impedire l'inserimento di una query già pronta e formare invece la query utilizzando i parametri. Questo dovrebbe essere l'unico modo per creare query di database. Ecco come eliminare questa vulnerabilità. Per esempio:

// 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
}
In questo modo si evita la vulnerabilità. Per coloro che vogliono approfondire questo articolo, ecco un ottimo esempio . Come fai a sapere quando comprendi questa vulnerabilità? Se ottieni la battuta nel fumetto qui sotto, allora probabilmente hai una chiara comprensione di cosa sia questa vulnerabilità :DSicurezza in Java: best practice - 2

3. Analizza le dipendenze e mantienile aggiornate

Che cosa significa? Se non sai cos'è una dipendenza, te lo spiegherò. Una dipendenza è un archivio JAR con codice connesso a un progetto utilizzando sistemi di compilazione automatici (Maven, Gradle, Ant) per riutilizzare la soluzione di qualcun altro. Ad esempio, Project Lombok , che genera getter, setter, ecc. per noi durante il runtime. Le grandi applicazioni possono avere un sacco di dipendenze. Alcuni sono transitivi (ovvero ogni dipendenza può avere le proprie dipendenze e così via). Di conseguenza, gli aggressori prestano sempre più attenzione alle dipendenze open source, poiché vengono utilizzate regolarmente e molti client possono avere problemi a causa di esse. È importante assicurarsi che non vi siano vulnerabilità note nell'intero albero delle dipendenze (sì, sembra un albero). Ci sono diversi modi per farlo.

Usa Snyk per il monitoraggio delle dipendenze

Snyk controlla tutte le dipendenze del progetto e segnala le vulnerabilità note. Puoi registrarti su Snyk e importare i tuoi progetti tramite GitHub. Sicurezza in Java: best practice - 3Inoltre, come puoi vedere dall'immagine sopra, se una vulnerabilità viene risolta in una versione più recente, Snyk offrirà la correzione e creerà una richiesta pull. Puoi usarlo gratuitamente per progetti open source. I progetti vengono scansionati a intervalli regolari, ad esempio una volta alla settimana, una volta al mese. Mi sono registrato e ho aggiunto tutti i miei repository pubblici alla scansione Snyk (non c'è nulla di pericoloso in questo, dato che sono già pubblici per tutti). Snyk ha quindi mostrato il risultato della scansione: Sicurezza in Java: best practice - 4E dopo un po', Snyk-bot ha preparato diverse richieste pull in progetti in cui le dipendenze devono essere aggiornate: Sicurezza in Java: best practice - 5E anche:Sicurezza in Java: best practice - 6Questo è un ottimo strumento per trovare vulnerabilità e monitorare gli aggiornamenti per le nuove versioni.

Utilizza GitHub Security Lab

Chiunque lavori su GitHub può sfruttare i suoi strumenti integrati. Puoi leggere ulteriori informazioni su questo approccio nel loro post sul blog intitolato Annuncio di GitHub Security Lab . Questo strumento, ovviamente, è più semplice di Snyk, ma sicuramente non dovresti trascurarlo. Inoltre, il numero di vulnerabilità note non farà che aumentare, quindi sia Snyk che GitHub Security Lab continueranno ad espandersi e migliorare.

Abilita Sonatype DepShield

Se utilizzi GitHub per archiviare i tuoi repository, puoi aggiungere Sonatype DepShield, una delle applicazioni nel MarketPlace, ai tuoi progetti. Può anche essere utilizzato per eseguire la scansione dei progetti per le dipendenze. Inoltre, se trova qualcosa, verrà generato un problema GitHub con una descrizione appropriata come mostrato di seguito:Sicurezza in Java: best practice - 7

4. Gestire i dati riservati con cura

Potremmo in alternativa usare l'espressione "dati sensibili". La perdita di informazioni personali, numeri di carte di credito e altre informazioni sensibili di un cliente può causare danni irreparabili. Prima di tutto, dai un'occhiata da vicino al design della tua applicazione e determina se hai davvero bisogno di questo o quel dato. Forse in realtà non hai bisogno di alcuni dei dati che hai, dati che sono stati aggiunti per un futuro che non è arrivato ed è improbabile che arrivi. Inoltre, molti inavvertitamente perdono tali dati attraverso la registrazione. Un modo semplice per evitare che i dati sensibili entrino nei tuoi log consiste nell'eliminare i metodi toString() delle entità di dominio (come Utente, Studente, Insegnante, ecc.). Ciò ti impedirà di generare accidentalmente campi riservati. Se usi Lombok per generare toString()metodo, è possibile utilizzare l' annotazione @ToString.Exclude per evitare che un campo venga utilizzato nell'output del metodo toString() . Inoltre, fai molta attenzione quando invii dati al mondo esterno. Supponiamo di avere un endpoint HTTP che mostri i nomi di tutti gli utenti. Non è necessario mostrare l'ID interno univoco di un utente. Perché? Perché un utente malintenzionato potrebbe utilizzarlo per ottenere altre informazioni più sensibili sull'utente. Ad esempio, se utilizzi Jackson per serializzare/deserializzare un POJO in/da JSON , puoi utilizzare @JsonIgnore e @JsonIgnorePropertiesannotazioni per impedire la serializzazione/deserializzazione di campi specifici. In generale, è necessario utilizzare diverse classi POJO in luoghi diversi. Che cosa significa?
  1. Quando si lavora con un database, utilizzare un tipo di POJO (un'entità).
  2. Quando si lavora con la logica aziendale, convertire un'entità in un modello.
  3. Quando si lavora con il mondo esterno e si inviano richieste HTTP, utilizzare entità diverse (DTO).
In questo modo puoi definire chiaramente quali campi saranno visibili dall'esterno e quali no.

Utilizza crittografia avanzata e algoritmi di hashing

I dati riservati dei clienti devono essere archiviati in modo sicuro. Per fare questo, dobbiamo usare la crittografia. A seconda dell'attività, è necessario decidere quale tipo di crittografia utilizzare. Inoltre, una crittografia più forte richiede più tempo, quindi ancora una volta è necessario considerare quanto la necessità giustifica il tempo speso per essa. Naturalmente, puoi scrivere tu stesso un algoritmo di crittografia. Ma questo non è necessario. È possibile utilizzare le soluzioni esistenti in quest'area. Ad esempio, 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>
Vediamo cosa fare, utilizzando questo esempio che coinvolge la crittografia e la decrittografia:

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

Crittografia delle password

Per questa attività, è più sicuro utilizzare la crittografia asimmetrica. Perché? Perché l'applicazione non ha davvero bisogno di decifrare le password. Questo è l'approccio standard. In realtà, quando un utente inserisce una password, il sistema la crittografa e la confronta con ciò che esiste nell'archivio delle password. Viene eseguito lo stesso processo di crittografia, quindi possiamo aspettarci che corrispondano, se viene inserita la password corretta, ovviamente :) BCrypt e SCrypt sono adatti qui. Entrambe sono funzioni unidirezionali (hash crittografici) con algoritmi computazionalmente complessi che richiedono molto tempo. Questo è esattamente ciò di cui abbiamo bisogno, poiché i calcoli diretti richiederanno un'eternità (beh, molto, molto tempo). Spring Security supporta un'intera gamma di algoritmi. Possiamo usare SCryptPasswordEncoder e BCryptPasswordEncoder. Quello che attualmente è considerato un algoritmo di crittografia forte potrebbe essere considerato debole il prossimo anno. Di conseguenza, concludiamo che dovremmo controllare regolarmente gli algoritmi che utilizziamo e, se necessario, aggiornare le librerie che contengono gli algoritmi di crittografia.

Invece di una conclusione

Oggi abbiamo parlato di sicurezza e, naturalmente, molte cose sono state lasciate dietro le quinte. Ho appena aperto per te la porta di un nuovo mondo, un mondo che ha una vita propria. La sicurezza è proprio come la politica: se non ti occupi di politica, la politica si occuperà di te. Tradizionalmente suggerisco di seguirmi sull'account GitHub . Lì pubblico le mie creazioni che coinvolgono varie tecnologie che sto studiando e applicando sul lavoro.

Link utili

  1. Guru99: Esercitazione sull'iniezione SQL
  2. Oracle: Centro risorse per la sicurezza Java
  3. Oracle: Linee guida per la codifica sicura per Java SE
  4. Baeldung: le basi della sicurezza Java
  5. Medio: 10 consigli per potenziare la tua sicurezza Java
  6. Snyk: 10 migliori pratiche di sicurezza Java
  7. GitHub: annuncio di GitHub Security Lab: protezione del codice mondiale, insieme
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION