Een van de belangrijkste statistieken in servertoepassingen is beveiliging. Dit is een soort niet-functionele vereiste . Beveiliging omvat veel componenten. Natuurlijk zou er meer dan één artikel nodig zijn om alle bekende beveiligingsprincipes en beveiligingsmaatregelen volledig te behandelen, dus we zullen stilstaan bij de belangrijkste. Een persoon die goed thuis is in dit onderwerp, kan alle relevante processen opzetten, nieuwe beveiligingslekken voorkomen en is nodig in elk team. Je moet natuurlijk niet denken dat je applicatie 100% veilig is als je deze praktijken volgt. Nee! Maar bij hen zal het zeker veiliger zijn. Laten we gaan.
1. Zorg voor beveiliging op het niveau van de Java-taal
Allereerst begint beveiliging in Java op het niveau van de mogelijkheden van de taal. Wat zouden we doen als er geen toegangsmodificatoren waren? Er zou niets anders zijn dan anarchie. De programmeertaal helpt ons veilige code te schrijven en maakt ook gebruik van vele impliciete beveiligingsfuncties:- Sterk typen. Java is een statisch getypeerde taal. Dit maakt het mogelijk om typegerelateerde fouten tijdens runtime op te vangen.
- Toegang tot modificaties. Hiermee kunnen we de toegang tot klassen, methoden en velden naar behoefte aanpassen.
- Automatisch geheugenbeheer. Hiervoor hebben Java-ontwikkelaars een vuilnisman die ons verlost van alles handmatig te configureren. Ja, er doen zich soms problemen voor.
- Bytecode-verificatie : Java wordt gecompileerd tot bytecode, die wordt gecontroleerd door de runtime voordat deze wordt uitgevoerd.
- Vermijd het serialiseren van beveiligingsgevoelige klassen. Serialisatie legt de klasse-interface bloot in het geserialiseerde bestand, om nog maar te zwijgen van de gegevens die geserialiseerd zijn.
- Probeer veranderlijke klassen voor gegevens te vermijden. Dit biedt alle voordelen van onveranderlijke klassen (bijv. threadveiligheid). Als u een veranderlijk object heeft, kan dit tot onverwacht gedrag leiden.
- Maak kopieën van geretourneerde veranderlijke objecten. Als een methode een verwijzing naar een intern veranderlijk object retourneert, kan de clientcode de interne status van het object wijzigen.
- Enzovoorts…
2. Elimineer kwetsbaarheden voor SQL-injectie
Dit is een speciaal soort kwetsbaarheid. Het is bijzonder omdat het zowel een van de bekendste als een van de meest voorkomende kwetsbaarheden is. Als u nooit geïnteresseerd bent geweest in computerbeveiliging, dan weet u er niets van. Wat is SQL-injectie? Dit is een database-aanval waarbij aanvullende SQL-code wordt geïnjecteerd waar dit niet wordt verwacht. Stel dat we een methode hebben die een soort parameter accepteert om de database te doorzoeken. Bijvoorbeeld een gebruikersnaam. Kwetsbare code ziet er ongeveer zo uit:
// 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 dit voorbeeld wordt vooraf een SQL-query voorbereid op een aparte regel. Dus wat is het probleem, toch? Misschien is het probleem dat het beter zou zijn om String.format te gebruiken ? Nee? Wel, wat dan? Laten we ons in de schoenen van een tester verplaatsen en nadenken over wat zou kunnen worden doorgegeven als de waarde van firstName . Bijvoorbeeld:
- We kunnen doorgeven wat wordt verwacht: een gebruikersnaam. Vervolgens retourneert de database alle gebruikers met die naam.
- We kunnen een lege string doorgeven. Vervolgens worden alle gebruikers geretourneerd.
- Maar we kunnen ook het volgende doorgeven: "'; DROP TABLE USERS;". En hier hebben we nu enorme problemen. Deze query verwijdert een tabel uit de database. Samen met alle gegevens. ALLES.
// 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
}
Op deze manier wordt de kwetsbaarheid vermeden. Voor degenen die dieper in dit artikel willen duiken, hier is een geweldig voorbeeld . Hoe weet je wanneer je deze kwetsbaarheid begrijpt? Als je de grap in de onderstaande strip snapt, dan heb je waarschijnlijk een duidelijk idee van waar deze kwetsbaarheid over gaat :D
3. Scan afhankelijkheden en houd ze up-to-date
Wat betekent dat? Als je niet weet wat een afhankelijkheid is, zal ik het uitleggen. Een afhankelijkheid is een JAR-archief met code dat is verbonden met een project met behulp van automatische buildsystemen (Maven, Gradle, Ant) om de oplossing van iemand anders te hergebruiken. Bijvoorbeeld Project Lombok , dat getters, setters, etc. voor ons genereert in de runtime. Grote applicaties kunnen heel veel afhankelijkheden hebben. Sommige zijn transitief (dat wil zeggen, elke afhankelijkheid kan zijn eigen afhankelijkheden hebben, enzovoort). Als gevolg hiervan besteden aanvallers steeds meer aandacht aan open-source-afhankelijkheden, aangezien deze regelmatig worden gebruikt en veel klanten daardoor problemen kunnen krijgen. Het is belangrijk om ervoor te zorgen dat er geen bekende kwetsbaarheden zijn in de hele afhankelijkheidsboom (ja, het ziet eruit als een boom). Er zijn verschillende manieren om dit te doen.Gebruik Snyk voor afhankelijkheidsbewaking
Snyk controleert alle projectafhankelijkheden en markeert bekende kwetsbaarheden. U kunt zich registreren op Snyk en uw projecten importeren via GitHub. Zoals je op de bovenstaande afbeelding kunt zien, zal Snyk de oplossing aanbieden als een kwetsbaarheid is verholpen in een nieuwere versie en een pull-verzoek maken. U kunt het gratis gebruiken voor open-sourceprojecten. Projecten worden met regelmatige tussenpozen gescand, bijvoorbeeld een keer per week, een keer per maand. Ik heb al mijn openbare repositories geregistreerd en toegevoegd aan de Snyk-scan (hier is niets gevaarlijks aan, aangezien ze al voor iedereen openbaar zijn). Snyk liet toen het scanresultaat zien: En na een tijdje bereidde Snyk-bot verschillende pull-aanvragen voor in projecten waar afhankelijkheden moesten worden bijgewerkt: En ook:Dit is een geweldige tool voor het vinden van kwetsbaarheden en het monitoren van updates voor nieuwe versies.Gebruik GitHub Security Lab
Iedereen die aan GitHub werkt, kan profiteren van de ingebouwde tools. Je kunt meer lezen over deze aanpak in hun blogpost getiteld Announcing GitHub Security Lab . Deze tool is natuurlijk eenvoudiger dan Snyk, maar je mag hem zeker niet verwaarlozen. Bovendien zal het aantal bekende kwetsbaarheden alleen maar groeien, dus zowel Snyk als GitHub Security Lab zullen blijven uitbreiden en verbeteren.Schakel Sonatype DepShield in
Als u GitHub gebruikt om uw repositories op te slaan, kunt u Sonatype DepShield, een van de applicaties in de MarketPlace, aan uw projecten toevoegen. Het kan ook worden gebruikt om projecten te scannen op afhankelijkheden. Bovendien, als het iets vindt, wordt er een GitHub-probleem gegenereerd met een passende beschrijving, zoals hieronder weergegeven:4. Ga zorgvuldig om met vertrouwelijke gegevens
We kunnen ook de uitdrukking "gevoelige gegevens" gebruiken. Het lekken van persoonlijke informatie, creditcardnummers en andere gevoelige informatie van een klant kan onherstelbare schade aanrichten. Kijk allereerst goed naar het ontwerp van uw applicatie en bepaal of u deze of gene gegevens echt nodig heeft. Misschien heb je een aantal van de gegevens die je hebt niet echt nodig - gegevens die zijn toegevoegd voor een toekomst die niet is gekomen en waarschijnlijk ook niet zal komen. Bovendien lekken velen onbedoeld dergelijke gegevens door middel van logboekregistratie. Een eenvoudige manier om te voorkomen dat gevoelige gegevens uw logboeken binnendringen, is door de toString()- methoden van domeinentiteiten (zoals Gebruiker, Student, Docent, enz.) te scrubben. Dit voorkomt dat u per ongeluk vertrouwelijke velden uitvoert. Als u Lombok gebruikt om de toString() te genererenkunt u de annotatie @ToString.Exclude gebruiken om te voorkomen dat een veld wordt gebruikt in de uitvoer van de methode toString() . Wees ook zeer voorzichtig bij het versturen van gegevens naar de buitenwereld. Stel dat we een HTTP-eindpunt hebben dat de namen van alle gebruikers laat zien. Het is niet nodig om de unieke interne ID van een gebruiker te tonen. Waarom? Omdat een aanvaller het zou kunnen gebruiken om andere, meer gevoelige informatie over de gebruiker te verkrijgen. Als u bijvoorbeeld Jackson gebruikt om een POJO te serialiseren/deserialiseren naar/van JSON , dan kunt u de @JsonIgnore en @JsonIgnoreProperties gebruikenannotaties om serialisatie/deserialisatie van specifieke velden te voorkomen. Over het algemeen moet u op verschillende plaatsen verschillende POJO-klassen gebruiken. Wat betekent dat?- Gebruik bij het werken met een database één type POJO (een entiteit).
- Wanneer u met bedrijfslogica werkt, zet u een entiteit om in een model.
- Gebruik bij het werken met de buitenwereld en het verzenden van HTTP-verzoeken verschillende entiteiten (DTO's).
Gebruik sterke encryptie- en hash-algoritmen
Vertrouwelijke gegevens van klanten moeten veilig worden opgeslagen. Om dit te doen, moeten we encryptie gebruiken. Afhankelijk van de taak moet u beslissen welk type codering u wilt gebruiken. Bovendien kost sterkere codering meer tijd, dus nogmaals, u moet overwegen in hoeverre de behoefte eraan de tijd die eraan wordt besteed rechtvaardigt. U kunt natuurlijk zelf een versleutelingsalgoritme schrijven. Maar dit is onnodig. U kunt op dit gebied bestaande oplossingen gebruiken. Bijvoorbeeld 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>
Laten we eens kijken wat we moeten doen, aan de hand van dit voorbeeld met codering en decodering:
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));
}
Versleutelen van wachtwoorden
Voor deze taak is het het veiligst om asymmetrische codering te gebruiken. Waarom? Omdat de applicatie niet echt wachtwoorden hoeft te decoderen. Dit is de standaard aanpak. In werkelijkheid, wanneer een gebruiker een wachtwoord invoert, versleutelt het systeem het en vergelijkt het met wat er in de wachtwoordopslag bestaat. Hetzelfde coderingsproces wordt uitgevoerd, dus we kunnen verwachten dat ze overeenkomen, als het juiste wachtwoord wordt ingevoerd natuurlijk :) BCrypt en SCrypt zijn hier geschikt. Beide zijn eenrichtingsfuncties (cryptografische hashes) met rekenkundig complexe algoritmen die lang duren. Dit is precies wat we nodig hebben, aangezien de directe berekeningen een eeuwigheid zullen duren (nou ja, een lange, lange tijd). Spring Security ondersteunt een hele reeks algoritmen. We kunnen SCryptPasswordEncoder en BCryptPasswordEncoder gebruiken. Wat momenteel als een sterk versleutelingsalgoritme wordt beschouwd, kan volgend jaar als zwak worden beschouwd. Daarom concluderen we dat we regelmatig de algoritmen die we gebruiken moeten controleren en, indien nodig, de bibliotheken moeten updaten die de versleutelingsalgoritmen bevatten.In plaats van een conclusie
Vandaag hebben we het over veiligheid gehad en natuurlijk zijn er veel dingen achter de schermen gebleven. Ik heb zojuist de deur naar een nieuwe wereld voor je opengebroken, een wereld die een eigen leven leidt. Veiligheid is net als politiek: als jij je niet met politiek bezighoudt, zal de politiek zich met jou bezig houden. Ik stel traditioneel voor dat je mij volgt op GitHub-account . Daar plaats ik mijn creaties met verschillende technologieën die ik bestudeer en op het werk toepas.Handige links
- Guru99: zelfstudie over SQL-injectie
- Oracle: Java-beveiligingsresourcecentrum
- Oracle: richtlijnen voor veilige codering voor Java SE
- Baeldung: de basisprincipes van Java-beveiliging
- Gemiddeld: 10 tips om uw Java-beveiliging te versterken
- Snyk: 10 best practices voor Java-beveiliging
- GitHub: aankondiging van GitHub Security Lab: samen de code van de wereld beveiligen
GO TO FULL VERSION