JavaRush /Java Blog /Random-IT /Sicurezza in Java: migliori pratiche
Roman Beekeeper
Livello 35

Sicurezza in Java: migliori pratiche

Pubblicato nel gruppo Random-IT
Nelle applicazioni server, uno degli indicatori più importanti è la sicurezza. Questo è uno dei tipi di Requisiti Non Funzionali . Sicurezza in Java: migliori pratiche - 1La sicurezza ha molti componenti. Naturalmente, per coprire in modo completo tutti quei principi e azioni protettive conosciute, è necessario scrivere più di un articolo, quindi concentriamoci su quello più importante. Una persona esperta in questo argomento, sarà in grado di impostare tutti i processi e garantire che non creino nuove falle nella sicurezza, sarà necessaria in qualsiasi team. Naturalmente, non dovresti pensare che se segui queste pratiche, l’applicazione sarà completamente sicura. NO! Ma con loro sarà sicuramente più sicuro. Andare.

1. Fornire sicurezza a livello del linguaggio Java

Prima di tutto, la sicurezza in Java inizia proprio a livello di funzionalità del linguaggio. Questo è cosa faremmo se non ci fossero i modificatori di accesso?... Anarchia, niente meno. Un linguaggio di programmazione ci aiuta a scrivere codice sicuro e anche a sfruttare molte funzionalità di sicurezza implicite:
  1. Digitazione forte. Java è un linguaggio tipizzato staticamente che offre la possibilità di rilevare errori di tipo in fase di esecuzione.
  2. Modificatori di accesso. Grazie a loro possiamo configurare l'accesso a classi, metodi e campi di classe nel modo di cui abbiamo bisogno.
  3. Gestione automatica della memoria. A questo scopo noi (Javaisti ;)) abbiamo Garbage Collector, che ti libera dalla configurazione manuale. Sì, a volte sorgono problemi.
  4. Controllo bytecode: Java viene compilato in bytecode, che viene controllato dal runtime prima di eseguirlo.
Tra le altre cose ci sono raccomandazioni di Oracle sulla sicurezza. Certo, non è scritto in “alto stile” e potresti addormentarti più volte mentre lo leggi, ma ne vale la pena. Un documento particolarmente importante sono le Linee guida per la codifica sicura per Java SE , che forniscono suggerimenti su come scrivere codice sicuro. Questo documento contiene molte informazioni utili. Se possibile, vale sicuramente la pena leggerlo. Per suscitare interesse verso questo materiale, ecco alcuni suggerimenti interessanti:
  1. Evitare la serializzazione di classi sensibili alla sicurezza. In questo caso, puoi ottenere l'interfaccia della classe dal file serializzato, per non parlare dei dati che vengono serializzati.
  2. Cerca di evitare classi di dati mutabili. Ciò offre tutti i vantaggi delle classi immutabili (ad esempio la sicurezza del thread). Se è presente un oggetto mutabile, ciò potrebbe portare a un comportamento imprevisto.
  3. Crea copie degli oggetti mutabili restituiti. Se un metodo restituisce un riferimento a un oggetto modificabile interno, il codice client può modificare lo stato interno dell'oggetto.
  4. E così via…
In generale, le Linee guida per la codifica sicura per Java SE contengono una serie di suggerimenti e trucchi su come scrivere codice in Java in modo corretto e sicuro.

2. Eliminare la vulnerabilità dell'SQL injection

Vulnerabilità unica. La sua unicità sta nel fatto che è una delle vulnerabilità più famose e allo stesso tempo più comuni. Se non sei interessato alla questione della sicurezza, non lo saprai. Cos'è l'iniezione SQL? Si tratta di un attacco a un database inserendo codice SQL aggiuntivo dove non è previsto. Diciamo di avere un metodo che accetta alcuni parametri per interrogare il database. Ad esempio, nome utente. Il codice con la vulnerabilità sarà simile a questo:
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
   // Создается связь с базой данных
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

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

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

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

private List<User> mapToUsers(ResultSet resultSet) {
   //переводит в коллекцию юзеров
}
In questo esempio la query SQL viene preparata in anticipo in una riga separata. Sembrerebbe che qual è il problema, giusto? Forse il problema è che sarebbe meglio usare String.format? NO? Cosa poi? Mettiamoci nei panni di un tester e pensiamo a cosa può essere trasmesso nel valore firstName. Per esempio:
  1. Puoi passare ciò che è previsto: il nome utente. Quindi il database restituirà tutti gli utenti con quel nome.
  2. Puoi passare una stringa vuota: verranno restituiti tutti gli utenti.
  3. Oppure puoi passare quanto segue: “''; UTENTI DELLA TABELLA DROP;”. E qui ci saranno problemi più grandi. Questa query rimuoverà la tabella dal database. Con tutti i dati. TUTTI.
Riuscite ad immaginare quali problemi ciò potrebbe causare? Poi puoi scrivere quello che vuoi. Puoi cambiare il nome di tutti gli utenti, puoi eliminare i loro indirizzi. Le possibilità di sabotaggio sono vaste. Per evitare ciò, è necessario smettere di inserire una query già pronta e costruirla invece utilizzando i parametri. Questo dovrebbe essere l'unico modo per interrogare il database. In questo modo puoi eliminare questa vulnerabilità. Esempio:
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
   // Создается связь с базой данных
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

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

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

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

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

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

private List<User> mapToUsers(ResultSet resultSet) {
   //переводим в коллекцию юзеров
}
In questo modo si evita questa vulnerabilità. Per coloro che vogliono approfondire questo articolo, ecco un ottimo esempio . Come fai a sapere se hai capito questa parte? Se la battuta qui sotto è diventata chiara, allora questo è un segno sicuro che l'essenza della vulnerabilità è chiara :D Sicurezza in Java: migliori pratiche - 2

3. Scansiona e mantieni aggiornate le dipendenze

Cosa significa? Per chi non sa cosa sia una dipendenza, lo spiego: è un archivio jar con codice che viene collegato a un progetto utilizzando sistemi di build automatici (Maven, Gradle, Ant) per riutilizzare la soluzione di qualcun altro. Ad esempio, Project Lombok , che genera getter, setter, ecc. per noi in fase di runtime. E se parliamo di applicazioni di grandi dimensioni, utilizzano molte dipendenze diverse. Alcuni sono transitivi (ovvero, ogni dipendenza può avere le proprie dipendenze e così via). Pertanto gli aggressori prestano sempre più attenzione alle dipendenze open source, poiché vengono utilizzate regolarmente e possono causare problemi a molti client. È importante assicurarsi che non siano presenti vulnerabilità note nell'intero albero delle dipendenze (che è esattamente quello che sembra). E ci sono diversi modi per farlo.

Usa Snyk per il monitoraggio

Lo strumento Snyk controlla tutte le dipendenze del progetto e segnala le vulnerabilità note. Lì puoi registrarti e importare i tuoi progetti, ad esempio tramite GitHub. Sicurezza in Java: migliori pratiche - 3Inoltre, come puoi vedere dall'immagine sopra, se una versione più recente ha una soluzione a questa vulnerabilità, Snyk si offrirà di farlo e creerà una pull-request. Può essere utilizzato gratuitamente per progetti open source. I progetti verranno scansionati con una certa frequenza: una volta alla settimana, una volta al mese. Mi sono registrato e ho aggiunto tutti i miei repository pubblici allo scan di Snyk (non c'è nulla di pericoloso in questo: sono già aperti a tutti). Successivamente, Snyk ha mostrato il risultato della scansione: Sicurezza in Java: migliori pratiche - 4E dopo un po', Snyk-bot ha preparato diverse Pull-Request in progetti in cui le dipendenze devono essere aggiornate: Sicurezza in Java: migliori pratiche - 5Ed eccone un altro: Sicurezza in Java: migliori pratiche - 6Quindi questo è uno strumento eccellente per la ricerca di vulnerabilità e il monitoraggio dell'aggiornamento nuove versioni.

Utilizza GitHub Security Lab

Chi lavora su GitHub può anche trarre vantaggio dai suoi strumenti integrati. Puoi leggere ulteriori informazioni su questo approccio nella mia traduzione dal loro blog Annuncio di GitHub Security Lab . Questo strumento, ovviamente, è più semplice di Snyk, ma sicuramente non dovresti trascurarlo. Inoltre, il numero di vulnerabilità conosciute non farà altro che crescere, quindi sia Snyk che GitHub Security Lab si espanderanno e miglioreranno.

Attiva Sonatype DepShield

Se usi GitHub per archiviare i tuoi repository, puoi aggiungere una delle applicazioni ai tuoi progetti da MarketPlace - Sonatype DepShield. Con il suo aiuto, puoi anche scansionare i progetti per le dipendenze. Inoltre, se trova qualcosa, verrà creato un GitHub Issue con una descrizione corrispondente, come mostrato di seguito: Sicurezza in Java: migliori pratiche - 7

4. Maneggiare i dati sensibili con cura

Sicurezza in Java: migliori pratiche - 8Nel linguaggio inglese, la frase “dati sensibili” è più comune. La divulgazione di informazioni personali, numeri di carta di credito e altre informazioni personali del cliente può causare danni irreparabili. Prima di tutto è necessario esaminare attentamente la progettazione dell'applicazione e determinare se i dati sono effettivamente necessari. Forse alcuni di essi non ce n’è bisogno, ma sono stati aggiunti per un futuro che non è arrivato ed è improbabile che arrivi. Inoltre, durante la registrazione del progetto, tali dati potrebbero fuoriuscire. Un modo semplice per impedire che dati sensibili entrino nei tuoi log è pulire i metodi toString()delle entità di dominio (come Utente, Studente, Insegnante e così via). Ciò impedirà la stampa accidentale di campi sensibili. Se utilizzi Lombok per generare un metodo toString(), puoi utilizzare un'annotazione @ToString.Excludeper impedire che il campo venga utilizzato nell'output tramite il metodo toString(). Inoltre, fai molta attenzione quando condividi dati con il mondo esterno. Ad esempio, esiste un endpoint http che mostra i nomi di tutti gli utenti. Non è necessario mostrare l'ID univoco interno dell'utente. Perché? Perché utilizzandolo, un utente malintenzionato può ottenere altre informazioni più riservate su ciascun utente. Ad esempio, se utilizzi Jackson per serializzare e deserializzare POJO in JSON , puoi utilizzare le annotazioni @JsonIgnoree @JsonIgnorePropertiesper impedire che campi specifici vengano serializzati o deserializzati. In generale, è necessario utilizzare classi POJO diverse per luoghi diversi. Cosa significa?
  1. Per lavorare con il database, utilizzare solo POJO - Entity.
  2. Per lavorare con la logica aziendale, trasferisci Entità su Modello.
  3. Per lavorare con il mondo esterno e inviare richieste http, utilizzare entità terze: DTO.
In questo modo potrai definire chiaramente quali campi saranno visibili dall'esterno e quali no.

Utilizza algoritmi di crittografia e hashing avanzati

I dati riservati dei clienti devono essere archiviati in modo sicuro. Per fare ciò è necessario utilizzare la crittografia. A seconda dell'attività, è necessario decidere quale tipo di crittografia utilizzare. Inoltre, una crittografia più potente richiede più tempo, quindi, ancora una volta, è necessario considerare quanto la sua necessità giustifichi il tempo impiegato su di essa. Naturalmente puoi scrivere tu stesso l'algoritmo. Ma questo non è necessario. Puoi sfruttare 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 come usarlo, usando l'esempio di come crittografare in un modo e nell'altro:
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));
}

Crittografia della password

Per questo compito è più sicuro utilizzare la crittografia asimmetrica. Perché? Perché l'applicazione non ha davvero bisogno di decrittografare le password. Questo è l'approccio generale. In realtà, quando un utente inserisce una password, il sistema la crittografa e la confronta con quella presente nel deposito password. La crittografia viene eseguita utilizzando gli stessi mezzi, quindi puoi aspettarti che corrispondano (se inserisci la password corretta;, ovviamente). BCrypt e SCrypt sono adatti a questo scopo. Entrambe sono funzioni unidirezionali (hash crittografici) con algoritmi computazionalmente complessi che richiedono molto tempo. Questo è esattamente ciò di cui hai bisogno, poiché per decifrarlo frontalmente ci vorrà un'eternità. Ad esempio, Spring Security supporta una gamma di algoritmi. SCryptPasswordEncoderPuoi anche usare BCryptPasswordEncoder. Ciò che oggi è un algoritmo di crittografia potente potrebbe rivelarsi debole l’anno prossimo. Di conseguenza, concludiamo che è necessario verificare gli algoritmi utilizzati e aggiornare le librerie con algoritmi.

Invece di uscita

Oggi abbiamo parlato di sicurezza e, ovviamente, molte cose sono state lasciate dietro le quinte. Ti ho appena aperto la porta verso un nuovo mondo: un mondo che vive di vita propria. Con la sicurezza è come con la politica: se tu non fai politica, la politica si impegnerà con te. Tradizionalmente, suggerisco di iscrivermi al mio account Github . Lì pubblico il mio lavoro su varie tecnologie che studio e applico sul lavoro.

link utili

Sì, quasi tutti gli articoli sul sito sono scritti in inglese. Che ci piaccia o no, l'inglese è la lingua con cui i programmatori comunicano. Tutti gli articoli, i libri e le riviste più recenti sulla programmazione sono scritti in inglese. Ecco perché i miei link ai consigli sono per lo più in inglese:
  1. Habr: SQL Injection per principianti
  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 suggerimenti per potenziare la sicurezza Java
  6. Snyk: 10 migliori pratiche per la sicurezza Java
  7. JR: Annuncio di GitHub Security Lab: proteggere insieme tutto il tuo codice
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION