This post covers Java security related topics as presented at Oracle University Online (Java SE 11 training) and as required for Java Oracle Certified Professional exam. We’ll go through the different kind of security threats (DOS, SQL, JavaScript and XML injections) and how to mitigate them with Java built-in capabilities.
This article covers vanilla Java security features as per the requirements of the OCP certification. Modern framework like Spring Boot offer others ways of handling those security threats.
This article is part of a series on advanced Java concepts, also corresponding to topics to study for upgrading from level I certification to level II certification (OCP level). The new Oracle Java certification framework (as from Java 11) only offers one certification covering both levels, so you’ll want to go through the level I (“OCA”) topics as well.
Types of security threats in software
- Denial Of Service (DOS) attacks: unchecked/unrestricted resources utilization.
- Sensitive data leaks: lack of encryption or information reduction.
- Code corruption: lack of encapsulation and immutability.
- Code injections: lack of input value validation and sanitation.
Denial Of Service (DOS)
When an application suffers from DOS, legitimate users are unable to use the application because of an excessive resources consumption.
The cause can be that a file or construct grows too large, or a connection is overwhelmed with bogus requests (DDOS).
Preventing DOS:
- Use permissions to restrict access to code that consumes vulnerable resources
- Validate all inputs
- Release resources in all cases (threads, database…)
- Monitor excessive resource consumption to identify DOS attacks, but note that new methods of attacks can appear anytime…
Java capabilities to prevent DOS
Security policies
Security policies are managed in a descriptor file and allow to restrict access to code and resources. It can include general security settings (authorizations), references to certificate keystore files, references to security policies files.
In the file java.security, we set policy file(s) named java.policy:
policy.url.1=file:${java.home}/conf/security/java.policy
policy.url.2=file:/somepath/java.policy
The structure of a java.policy file looks like:
grant signedBy "Jane, John", codeBase "file/some.jar" {
permission java.net.SocketPermission "localhost:7777", "listen";
permission java.io.FilePermission "/someFile", "read, write";
};
More documentation and examples are available on Oracle’s doc website.
Permissions
Permissions are configured in the java.policy file. They are part of the Java security API (java.security.Permission). When trying to make actions dependent of permissions, an AccessControlException is thrown if access is denied.
SocketPermission s = new SocketPermission("localhost:7777", "accept,connect,listen");
FilePermission f = new FilePermission("/someFile", "read, write");
// checking against configured policies in java.policy
try {
AccessController.checkPermission(s);
AcsessController.checkPermission(f);
} catch (AccessControlException e) {
//...
}
Privileged code
The method doPrivileged executes the specified PrivilegedAction with privileges enabled. The checkPermission method stops checking policies when elevated access (privileges) is given.
String file = "/someFile"; // marked in the java.policy file
String text = AccessController.doPrivileged(
new PrivilegedAction<String> () {
public String run() {
// do whatever with the file
}
}
);
// lambda version
String text = AccessController.doPrivileged(
(PrivilegedAction<String>) () -> {
// do whatever with the file
}
);
PrivilegedAction is a functional interface.
Secure IO and file systems
Relative paths can be dangerous because they are subject to diversion. A good practice is to remove redundant elements from path and convert it to canonical with method normalize or toRealPath(LinkOption).
Monitor
Expected sizes of files and lengths of streams should be monitored and limited according to expected figures. Some mechanisms should terminate operations that process excessive amount of data and time out lengthy operations to release resources.
Deserialize cautiously
Deserialization creates an object by passing normal constructor behavior, it creates an object that may sidestep security checks. Untrusted data shouldn’t be deserialized or should pass validation tests first.
Protect the code of you Java application
Here are some practices to reduce risks of misuse of your code in Java, from which a lot of advises can probably apply to most OOP languages.
- Make use of encapsulation, use most restrictive access possible, at best use modules to beat Reflection.
- Use inheritance wisely, beware of changes and new methods which could be added to superclasses in future releases. Choose what can be overridden. Make use of composition instead of inheritance when the latter is not necessary.
- Make members and classes final to prevent unwanted inheritance and overrides when they’re not meant to be inherited. Non-final, non-private classes can be overridden by malicious attackers.
- Use factory methods in which a validation is performed before invoking methods from constructors. That way, malicious attackers will be limited when trying to construct new objects (as a reminder, the factory design pattern prevents from creating objects making a direct call to the constructor: it’s not possible to create the object with the “new …” statement).
- Don’t invoke overridable methods from constructors.
- Protect byte-code against tampering and dangerous behaviors (reverse-engineering). Some environment may generate or modify byte-code. Don’t use arguments “-Xverify” or “-noverify”, which disable byte-code verification, when executing command lines. Basically, protect your byte-code files.
Protect sensitive data
Some values are particularly sensitive and require extra care, like logins, addresses, bank information… to prevent fraud and identity theft:
- scramble sensitive data
- clean it out of memory ASAP
- remove it from logs
- do not serialize them or write them to log files
- override error messages which could contain sensitive information
- encrypt or hash sensitive values
// hash information with MessageDigest (hash is a one-way process, data cannot be decoded but can be tested for equality)
String value = "any sensitive information";
MessageDigest md = MessageDigest.getInstance("SHA-256"); // can throw NoSuchAlgorithmException (checked exception)
byte[] digest = md.digest(value.getBytes());
String hash = (new BigInteger(1, digest)).toString(16);
// encrypt and decrypt values with the javax.crypto API
SecretKey key = KeyGenerator.getInstance("AES").generateKey();
// can throw NoSuchAlgorithmException (checked exception)
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); // can throw NoSuchPaddingException (checked exception)
cipher.init(Cipher.ENCRYPT_MODE, key); // can throw InvalidKeyException (checked exception)
byte [] value = "valueToEncrypt".getBytes();
byte [] encryptedValue = cipher.doFinal(value); // can throw BadPaddingException, IllegalBlockSizeException (checked exceptions)
AlgorithmParameters params = cipher.getParameters();
System.out.println(new String(encryptedValue)); // outputs unreadable value
cipher.init(Cipher.DECRYPT_MODE, key, params); // can throw InvalidAlgorithmParameterException (checked exception)
byte [] decryptedValue = cipher.doFinal(encryptedValue);
System.out.println(new String(decryptedValue)); // outputs "valueToEncrypt"
Injections
Injections involve the execution of malicious code or scripts by exploiting vulnerabilities in an application. Although modern frontend frameworks have built-in capabilities that prevent those attacks by default, the backend application should always handle them additionally.
SQL injections
Basic statements allow to concatenate parameters (characters string) which implies a risk of SQL injection as param. For this reason, prepared statement should be used instead of basic statements.
Another way of preventing injections is to sanitize the basic statement with the enquoteLiteral(parameter)
. The parameter is then single quoted and every single quote becomes a double quote. As the database drivers may have to provide their own implementation, it is less consistent than using prepared statements capability of JDBC.
See the dedicated article about JDBC for more information on statements.
JavaScript injections
JavaScript injections can happen from web UI applications. Arbitrary code which contains JS script is passed as parameter, cookie, header, url value, input…
For that reason, all values processed by the browser should be validated and sanitized
XML injections
XML injections is malicious code inserted in XML files. To prevent it, instruct the XML parser to stop processing documents when unsafe constructs are detected.