Nelle applicazioni server, uno degli indicatori più importanti è la sicurezza. Questo è uno dei tipi di Requisiti Non Funzionali . La 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:- Digitazione forte. Java è un linguaggio tipizzato staticamente che offre la possibilità di rilevare errori di tipo in fase di esecuzione.
- Modificatori di accesso. Grazie a loro possiamo configurare l'accesso a classi, metodi e campi di classe nel modo di cui abbiamo bisogno.
- Gestione automatica della memoria. A questo scopo noi (Javaisti ;)) abbiamo Garbage Collector, che ti libera dalla configurazione manuale. Sì, a volte sorgono problemi.
- Controllo bytecode: Java viene compilato in bytecode, che viene controllato dal runtime prima di eseguirlo.
- 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.
- 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.
- 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.
- E così via…
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:
- Puoi passare ciò che è previsto: il nome utente. Quindi il database restituirà tutti gli utenti con quel nome.
- Puoi passare una stringa vuota: verranno restituiti tutti gli utenti.
- 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.
// Метод достает из базы данных всех пользователей с определенным именем
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
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. Inoltre, 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: E dopo un po', Snyk-bot ha preparato diverse Pull-Request in progetti in cui le dipendenze devono essere aggiornate: Ed eccone un altro: Quindi 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:4. Maneggiare i dati sensibili con cura
Nel 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 metoditoString()
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.Exclude
per 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 @JsonIgnore
e @JsonIgnoreProperties
per impedire che campi specifici vengano serializzati o deserializzati. In generale, è necessario utilizzare classi POJO diverse per luoghi diversi. Cosa significa?
- Per lavorare con il database, utilizzare solo POJO - Entity.
- Per lavorare con la logica aziendale, trasferisci Entità su Modello.
- Per lavorare con il mondo esterno e inviare richieste http, utilizzare entità terze: DTO.
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.SCryptPasswordEncoder
Puoi 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:- Habr: SQL Injection per principianti
- Oracle: Centro risorse per la sicurezza Java
- Oracle: Linee guida per la codifica sicura per Java SE
- Baeldung: Le basi della sicurezza Java
- Medio: 10 suggerimenti per potenziare la sicurezza Java
- Snyk: 10 migliori pratiche per la sicurezza Java
- JR: Annuncio di GitHub Security Lab: proteggere insieme tutto il tuo codice
GO TO FULL VERSION