Introduction to Secure Java Coding

Secure Java coding is a vast topic; therefore, this article is just an introduction to it. I will discuss the most frequent attacks, mitigations, and some traps that developers usually fall into either because of partial or complete lack of familiarity with Java security. 

Basic Coding Practices

Before dealing with specific security attacks, let’s review some basic coding practices that should be used in all programming languages:

Input validation

Insufficient input validation is probably the number one cause of security vulnerabilities in web applications. The goal is to prevent malformed data from persisting in the database and triggering malfunction of downstream components.

Least Privilege

According to the principle of least privilege, only the minimum necessary rights should be assigned to a subject that requests access to a resource and should be in effect for the shortest duration necessary. This principle helps us minimize the damage when something does go wrong.

Failsafe defaults

In general software is used in it’s default form. Therefore, making sure the default is secure is a requirement. Be sure to provide a safe, secure configuration. 

Use of standard security frameworks

It’s a good idea to delegate security audit of code or solutions to experts who can focus exclusively on it. Let the experts do the work for you, and use standard security frameworks that are provided for your programming language.

Now that we have the basic coding practices out the way, let’s focus on most frequent attacks and mitigation techniques. 

SQL Injection

The first type of attack that we will discuss, SQL Injection, happens when malicious user can execute SQL statements (also known as malicious payload) that control a web application’s database server. For example:

txtUserId = getRequestString("UserId");
txtSQL = "select * from users where userid = " + txtUserId;

If the attacker enters “105 OR 1=1” for the userId, txtSQL becomes:

"select * from users where userid = 105 OR 1=1";

which will return all rows of users table since the where clause is always true.

To prevent this type of attack, use prepared statements. For example:

PreparedStatement statement = connection.prepareStatement( " select * from users where userid =?");
Statement.setString(1, txtUserId);

ORM also can be used to prevent this type of attack. Hibernate, for example, can protect against this type of attack but it needs to be used correctly.

Query hqlQuery = session.createQuery("from Employees as emp where emp.incentive > :incentive");
List results = hqlQuery.setLong("incentive", new Long(10000)).list();

SQL injection can still be exploited in prepared statements if type binding is not used. Therefore, it’s very important to use prepared statements with data binding as already shown.

Another important point is that even though variable binding can protect against SQL Injection attacks, if not protected, sensitive data that the business does not want to make public could still be accessed. Case in point is an airline reservation with query for past ticket prices. No SQL Injection exploitation but the airline may not want that data publicly available.

Therefore enforcing type safety is not sufficient. To provide full protection you need to:

Always protect with variable binding AND make sure the business logic cannot be subverted by data that conforms to the specified types.

Cross-side scripting (XSS)

Another type of attack is Cross-side scripting, also known as Reflected XSS. Cross-site scripting is a type of injection security attack in which an attacker injects data, such as a malicious script, into content from otherwise trusted websites. Cross-site scripting attacks happen when an untrusted source is allowed to inject its own code into a web application, and that malicious code is included with dynamic content delivered to a victim’s browser.

Simple Example:

PrintWriter out = res.getWriter();
out.println("<div>Hello" + username + "!</div>");

One expects that the code above will print as:

<div>Hello Will!</div>

But if a malicious code is entered in the input, the following HTML is obtained:

<div>Hello Will</div><script>alert("Danger Will Robinson!");</script><div>!</div>

If the input is not validated before it gets displayed back on the webpage, the attacker may be able to inject some JavaScript and have the malicious code execute in the context of the user’s browser session. The attacker injects malicious code inside the HTML page. This is a very brief demonstration of this type of attack more detailed explanation can be found in Open Web Application Security Project (https://www.owasp.org/index.php/Cross-site_Scripting_(XSS))

There are two prevention techniques:

  • Input Validation: make sure the input is what you expect it to be. You can either filter the input based on values that should be valid (whitelisting) or based on values that should be rejected (blacklisting). Arguably, the former is easier to specify than the latter. For example, it’s easier to have a list of users that have permissions, rather than having a list of everyone else. Another important point: completely reject invalid input, instead of trying to fix it, because the attacker may still try to dodge the validation process and eventually succeed in injecting malicious code. Sometimes it may be very difficult to sanitize the input for all scenarios. Therefore, it’s always a good practice to implement input validation with output encoding, which is discussed next.
  • Output Encoding: is more robust than input validation. The reason is when the text is being displayed to the user, the context is defined and therefore the developer has all the information he/she needs. The implementation usually comprises of HTML editing. But depending on the context where the output is going to be placed JavaScript, CSS or URL encoding may be used. Similarly, to input validation, Output Encoding leverages whitelisting specific HTML elements.

Cross-side Request Forgery (CSRF)

CSRF occurs because of the way browsers uses cookies. If the application does not use cookies for authentication, you are probably not vulnerable to this type of attack.

The CSRF attack enables the attacker to gain control of the victim’s browser. Here are the basic steps of the attack:

  1. 1. Attacker places a malicious web page on the carrier server (server the attacker controls or another server that allows him/her to place content there, wiki for example). The attacker creates the content in such a way that when loaded in a browser it will send requests to the target server
  2. 2. The victim starts an authenticated session to the target server (bank, for example)
  3. 3. The victim is lured to visiting the malicious web page in the same browser. For this attack to be successful the victim must have the session (2.) still valid when he/she visits the malicious web page.
  4. 4. When the browser requests the malicious web page from the carrier server, it loads and executes the malicious code which connects to the active browser session (2.) without the victim being aware. Perhaps the malicious code may change the victim’s password to the target server
  5. 5. Now the attacker can impersonate the victim and connect to the target server.

Possible solutions:

  • Require re-authentication before critical actions.
  • Preferred method: Insert a synchronizer token (random number) with every state changing request. This token is part of links for get requests or hidden form fields for post requests. The fact that the token is unpredictable, prevents the attacker to draft the link used in the attack.

Here are some examples of products that have built-in solutions for CSRF attacks:

OWASP CSRFGuard: https://www.owasp.org/index.php/Category:OWASP_CSRFGuard_Project

Apache Tomcat: https://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#CSRF_Prevention_Filter

Oracle Commerce: https://docs.oracle.com/cd/E69533_01/Platform.11-3/ATGWSFrameGuide/html/s0511settingserversidesecurity01.html

Whether you use a built-in or customized solution, please make sure that all HTTP requests that causes changes of state in the server is guarded against CSRF attacks.

Here are some other security frameworks for Java:

OWASP Security API: https://www.owasp.org/index.php/Category:OWASP_Enterprise_Security_API

AntiSamy (HTML Sanitization): https://www.owasp.org/index.php/Category:OWASP_AntiSamy_Project

Object Semantics Issues

Numeric Problems

Unlike lower level languages, problem with integer overflow is hard to exploit in Java because these calculations are not used to allocate memory. But these bugs can still be used to attack other system resources (disk space or network connections).

Arithmetic operations cause a number to either grow too large to be represented in the number of bits allocated to it, or too small. This could cause a positive number to become negative or a negative number to become positive, resulting in unexpected/dangerous behavior.

The following may cause an overflow and hence trouble in business logic:

int totalAmount = quantity * unitPrice;

Integer overflow can be handled and/or prevented with the following sample method (If long is not big enough use java.math.BigInteger):

final int safeMultiply(int quantity, int unitPrice) {
    long value = (long) quantity * (long) unitPrice;
    if ((value < Integer.MIN_VALUE) || (value > Integer.MAX_VALUE)) {
        throw new ArithmeticException(“Integer overflow”);
    }
    return (int) value;
}

By casting the result into a bigger value before doing the multiplication will prevent the overflow if an attacker tries to use a very large number.

But you don’t have to use the method above. Since Java 8, you can use instead “overflow safe methods” found in the math package.

Declaring Arrays Final

A word of warning: declaring arrays final will provide a false sense of security, because the final keyword will prevent changing the reference, but not the content of the array, i.e. the contents of the final array can still be replaced.

Threading Traps

Some of the issues below can cause synchronization problems or denial of service:

  • finalize() – is used to reclaim resources (files, connections, etc.). All objects that use an external resource, should have a finalize method to cleanup resources. However, you should not call this method. It will be called by the garbage collector. If you call it, the method will run twice. Furthermore, whenever you override the finalize method, you should call the parent’s finalize method: super.finalize() so it too can cleanup.
  • Call to Thread.run() will not spawn a new thread, rather it will run the code in the current thread. Instead use Thread.start() since it will start a new thread.

Command Injection and Path Traversal

In these types of attack, un-validated parameters are passed to the OS. As already mentioned in the beginning of this article, user supplied values should always be sanitized to decrease the chance for exploitation. As shown in the example below, if user input is not sanitized, it may allow the attacker to read sensitive files that he/she should not have access to. For examples:

import java.io.*;

public class Example1 {
	public static void main(String[] args)
	throws IOException {
		if(args.length != 1) {
			System.out.println("No arguments");
			System.exit(1);
		}
		Runtime runtime = Runtime.getRuntime();
		Process proc = runtime.exec("find" + " " + args[0]);
		
		InputStream is = proc.getInputStream();
		InputStreamReader isr = new InputStreamReader(is);
		BufferedReader br = new BufferedReader(isr);
		
		String line;
		while ((line = br.readLine()) != null) {
			System.out.println(line);
		}
	}
}

Crypto in Java

A discussion of crypto in Java is beyond the scope of this article. In fact, it may take several articles to fully cover the entire topic, but a couple of points should be made:

  • The crypto algorithms will eventually be broken. Therefore, write your application so the crypto libraries can be easily replaced when new versions are available
  • Use SecureRandom() instead of Random(). The latter is unsuitable for any security related purposes because it’s not cryptographically as strong as the former. Bottom line: it’s not safe. It would take 248 attempts to break a number generated with Random() compared to 2128 with SecureRandom().

Conclusion

Even though Java is a step up from lower level languages and it helps us to avoid mistakes such as buffer overflow, secure coding in Java is still a vast topic. I briefly cover the most exploited attacks and basic coding practices. You should become familiar with the rules in the Oracle Coding Standards for Java (https://wiki.sei.cmu.edu/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java) and never develop outside of these standards.

Add Your Comment