The Java platform provides an environment for executing code with different permission levels and a robust basis for secure systems through features such as memory safety. The Java language and virtual machine make application development highly resistant to common programming mistakes, stack smashing, and buffer overflow attacks which are possible in the C and C++ programming language. However, bugs produced through vulnerable programming practices can have serious security ramifications and could appear in any layer of the stack. In this article, we will list some standard programming practices in Java to protect sensitive data.
Secure coding guidelines contain the following parameters which are as follows:
- Releasing Resources
- Destroy sensitive information from exceptions
- Avoid Dynamic SQL
- Limiting Accessibility
- Limiting Extensibility
Let us discuss each of them how these practices do help in protecting our sensitive data in java and will be discussing with help of sample codes where ever required for better understandability.
1. Releasing Resources
Application resources used during execution, such as open files, memory, objects, and locks if not released after their use could cause errors, duplication, and deadlocks during processing. Therefore, resources should always be released after their use by incorporating techniques like Execute Around Method (using lambda) and try-with-resource syntax.
Example 1: Illustrate Execute Around Methods
// Main class class GFG { // Main driver method public static void main(String[] args) { // Inserting code here // Execute Around pattern using Lambda // Clean-up is taken care of by lambda // Out lambda expression long sum = readFileBuffered(InputStream in -> { long current = 0; for (;;) { int b = in.read(); if (b == -1) { return current; } current += b; } }); } }
Example 2: Illustrating try-with-resources syntax
// Main class class GFG { // Main driver method public static void main(String[] args) { // Inserting code here // Try-with-resource syntax public R readFileBuffered( InputStreamHandler handler) throws IOException { // try statement with one or more resources try (final InputStream in = Files.newInputStream(path)) { handler.handle(new BufferedInputStream(in)); } // By now the resource is closed // at the end of the statement execution } } }
2. Destroy sensitive information from exceptions
Many forms of attacks involve guessing or knowing the location of files, and exceptions may include sensitive information about the configuration and internals of the system. Stack traces of thrown exceptions such as java.io.FileNotFoundException can include the file paths of the system which can be used by attackers to backtrack to system files and target sensitive data. Therefore, internal exceptions should be caught and sanitized before propagating them to upstream callers.
3. Avoid Dynamic SQL
Dynamically created SQL statements from the untrusted sources can contain input that is subject to command injection, which means an injection of malicious code through SQL statements to directly affect data. Instead, parameterized SQL statements should be used such as java.sql.PreparedStatement or java.sql.CallableStatement so that the statements are pre-compiled and only parameters are needed to be inserted in order for them to execute.
Sample
String sql = "SELECT * FROM User WHERE userId = ?"; PreparedStatement stmt = con.prepareStatement(sql); stmt.setString(1, userId); ResultSet rs = prepStmt.executeQuery();
4. Limiting Accessibility
Class members, methods, constructors, and interfaces should be declared as private or protected if they are not a part of an API to avoid exposing their implementation. If the implementations are encapsulated and not made public to outside entities then security is maintained as outsiders will not be able to change the internal structure.
5. Limiting Extensibility
If classes or methods are not declared final then an attacker can maliciously override them. A class that does not permit inheritance is easier to implement and verify that it is secure. Prefer composition to inheritance.
Example Limiting Extensibility through Composition and final class
// Class 1 // Helper class- Book class class Book { // Member variables of Book class public String title; public String author; // Parameterized constructor of Book class Book(String title, String author) { // This refers to current object itself this.title = title; this.author = author; } } // Class 2 // Helper class // Unsubclassable class Library with composed behavior. public final class Library { // Composition private final List<Book> books; // Hiding the constructor of this class // by declaring it private private Library(Book book) { books = new ArrayList<Book>(); } // Method of this class // Guarded Construction of class methods private void createLibrary(Book book) { // ...validate any arguments... // ...perform security checks... // adding elements to the book books.add(book); } }
In this article, we addressed some common pitfalls and the surrounding solutions. Hence, the most effective approach to minimizing vulnerabilities is to have obviously no flaws rather than no obvious flaws. These were some many practices that a Java programmer can adhere to in order to produce secure Java applications.