loading...

10 Java security best practices

brianverm profile image Brian Vermeer Updated on ・6 min read

10 Java security best practices

In this cheat sheet edition, we’re going to focus on ten Java security best practices for both open source maintainers and developers. Although most developers understand that secure coding is important, security is not the first thing on a Developers mind. Using the OWASP Top 10 vulnerabilities as the initial starting point, I created this top 10. For this cheat sheet, I collaborated with Jim Manico, Java Champion and founder of Manicode Security.

So without further due I proudly present the 10 Java security best practices

1. Use query parameterization to prevent injection

In the 2017 version of the OWASP Top 10 vulnerabilities, injection appeared at the top of the list as the number one vulnerability that year. When looking at a typical SQL injection in Java, the parameters of a sequel query are naively concatenated to the static part of the query. The following is an unsafe execution of SQL in Java, which can be used by an attacker to gain more information than otherwise intended.

public void selectExample(String parameter) throws SQLException {
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
   String query = "SELECT * FROM USERS WHERE lastname = " + parameter;
   Statement statement = connection.createStatement();
   ResultSet result = statement.executeQuery(query);

   printResult(result);
}

If the parameter in this example is something like '' OR 1=1, the result contains every single item in the table. This could be even more problematic if the database supports multiple queries and the parameter would be ''; UPDATE USERS SET lastname=''.

To prevent this in Java, we should parameterize the queries by using a prepared statement. This should be the only way to create database queries. By defining the full SQL code and passing in the parameters to the query later, the code is easier to understand. Most importantly, by distinguishing between the SQL code and the parameter data, the query can’t be hijacked by malicious input.

public void prepStatmentExample(String parameter) throws SQLException {
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
   String query = "SELECT * FROM USERS WHERE lastname = ?";
   PreparedStatement statement = connection.prepareStatement(query);
   statement.setString(1, parameter);
   System.out.println(statement);
   ResultSet result = statement.executeQuery();

   printResult(result);
}

In the example above, the input binds to the type String and therefore is part of the query code. This technique prevents the parameter input from interfering with the SQL code.

2. Use OpenID Connect with 2FA

Identity management and access control is difficult and broken authentication is often the reason for data breaches. In fact, this is #2 in the OWASP top 10 vulnerability list. There are many things you should take into account when creating authentication yourself: secure storage of passwords, strong encryption, retrieval of credentials etc. In many cases it is much easier and safer to use exciting solutions like OpenID Connect. OpenID Connect (OIDC) enables you to authenticate users across websites and apps. This eliminates the need to own and manage password files. OpenID Connect is an OAuth 2.0 extension that provides user information. It adds an ID token in addition to an access token, as well as a /userinfo endpoint where you get additional information. It also adds an endpoint discovery feature and dynamic client registration.

Setting up OpenID Connect with libraries like Spring Security is a straightforward and common task. Make sure that your application enforces 2FA (two-factor authentication) or MFA (multi-factor authentication) to add an extra layer of security in your system.

By adding the oauth2-client and Spring security dependencies to your Spring Boot application, you leverage third-party clients like Google, Github and Okta to handle OIDC. After creating your application you just need to connect it to the specific client of your choice, by specifying it in your application configuration; that could be your GitHub or Okta client-id and client-secret, as shown below.

pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

application.yaml

spring:
 security:
   oauth2:
     client:
         registration:
           github:
             client-id: 796b0e5403be4729ca01
             client-secret: f379318daa27502254a05e054361074180b840a9
           okta:
             client-id: 0oa1a4wascEpYu6yk358
             client-secret: hqxj7a9lVe_TudbS2boBW7AWwxTlZiHNrJxdc_Sk
             client-name: Okta
         provider:
           okta:
             issuer-uri: https://dev-844689.okta.com/oauth2/default

3. Scan your dependencies for known vulnerabilities

There’s a good chance you don’t know how many direct dependencies your application uses. It’s also extremely likely you don’t know how many transitive dependencies your application uses either. This is often true, despite dependencies making up for the majority of your overall application. Attackers target open-source dependencies more and more, as their reuse provides a malicious attacker with many victims. It’s important to ensure there are no known vulnerabilities in the entire dependency tree of your application.

Snyk tests your application build artifacts, flagging those dependencies that have known vulnerabilities. It provides you with a list of vulnerabilities that exist in the packages you’re using in your application as a dashboard.

Additionally, it suggests upgrade versions or provide patches to remediate your security issues, via a pull request against your source code repository. Snyk also protects your environment by ensuring that any future pull requests raised on your repository are automatically tested (via webhooks) to make sure they do not introduce new known vulnerabilities.

Snyk is available via a web UI as well as a CLI, so you integrate it with your CI environment and configure it to break your build when vulnerabilities exist with a severity beyond your set threshold.

Use Snyk for free for open source projects or for private projects with a limited number of monthly tests.

4. Handle sensitive data with care

Exposing sensitive data, like personal data or credit card numbers of your client, can be harmful. But even a more subtle case than this can be equally harmful. For example, the exposure of unique identifiers in your system is harmful if that identifier can be used in another call to retrieve additional data.

First of all, you need to look closely at the design of your application and determine if you really need the data. On top of that, make sure that you don’t expose sensitive data, perhaps via logging, autocompletion, transmitting data etc.

An easy way to prevent sensitive data from ending up in your logs, is to sanitize the toString() methods of your domain entities. This way you can’t print the sensitive fields by accident. If you use project Lombok to generate your toString() method, try using @ToString.Exclude to prevent a field from being part of the toString() output.

Also, be very careful with exposing data to the outside world. For instance: If we have an endpoint in a system that shows all usernames, there is no need to show the internal unique identifier. This unique identifier may be used to connect other, more sensitive information to the user by using other endpoints. If you use Jackson to serialize and deserialize POJOs to JSON try using @JsonIgnore and @JsonIgnoreProperties to prevent these properties from being serialized or deserialized.

If you need to send sensitive data to other services, encrypt it properly and ensure that your connection is secured with HTTPS, for instance.

5. Sanitize all input

Cross-site scripting (XSS) is a well-known issue and mostly utilized in JavaScript applications. However, Java is not immune to this. XSS is nothing more than an injection of JavaScript code that’s executed remotely. Rule #0 for preventing XSS, according to OWASP, is “Never insert untrusted data except in allowed locations” The basic solution for this is to prevent untrusted data, as much as possible, and sanitize everything else before using the data. A good starting point is the OWASP Java encoding library that provides you with a lot of encoders.

<dependency>
   <groupId>org.owasp.encoder</groupId>
   <artifactId>encoder</artifactId>
   <version>1.2.2</version>
</dependency>
String untrusted = "<script> alert(1); </script>";
System.out.println(Encode.forHtml(untrusted));

// output: <script> alert(1); </script>

Sanitizing user text input is an obvious one. But what about the data you retrieve from a database, even when it’s your own database? What if your database was breached and someone planted some malicious text in a database field or document?

Also, keep an eye on incoming files. The Zip-slip vulnerability in many libraries exists because the path of the zipped files was not sanitized. Zip-files containing files with paths ../../../../foo.xy could be extracted and potentially override arbitrary files. Although this is not an XSS attack, it is a good example of why you have to sanitize all input. Every input is potentially malicious and should be sanitized accordingly.


These are only the first 5 of my top 10.
Want to know more about the following 5?
Read more ...

Posted on by:

brianverm profile

Brian Vermeer

@brianverm

Java Dev | DevRel | VirtualJug Co-lead | UtrechtJUG Co-lead | MyDevSecOps Co-lead | Dutch Air Reserve | Taekwondo Master | Flag Football CB/WR

Discussion

markdown guide