Security is one of the most complex, broad, and important aspects of software development. Software security is also frequently overlooked, or oversimplified to just a few minor adjustments at the end of the development cycle.
We can see the results in the annual list of major data security breaches, which in 2019 amounted to over 3 billion exposed records. If it can happen to Capital One, it can happen to you.
The good news is that Java is a longstanding development platform with many built-in security features. The Java Security package has undergone intensive battle testing, and is frequently updated for new security vulnerabilities.
The newer Java EE Security API, released in September 2017, addresses vulnerabilities in cloud and micro-services architectures. The Java ecosystem also includes a wide range of tools for profiling and reporting security issues.
But even with a solid development platform, it is important to stay vigilant. Application development is a complex undertaking, and vulnerabilities can hide in the background noise. You should be thinking about security at every stage of application development, from class-level language features to API endpoint authorisation.
The following ground rules offer a good foundation for building more secure Java applications.
Java security rule #1: Write clean, strong Java code
Vulnerabilities love to hide in complexity, so keep your code as simple as possible without sacrificing functionality. Using proven design principles like DRY (don't repeat yourself) will help you write code that is easier to review for problems.
Always expose as little information as possible in your code. Hiding implementation details supports code that is both maintainable and secure. These three tips will go a long way toward writing secure Java code:
- Make good use of Java's access modifiers: Knowing how to declare different access levels for classes, methods, and their attributes will go a long way to protecting your code. Everything that can be made private, should be private.
- Avoid reflection and introspection: There are some cases where such advanced techniques are merited, but for the most part you should avoid them. Using reflection eliminates strong typing, which can introduce weak points and instability to your code. Comparing class names as strings is error prone and can easily lead to namespace collision.
- Always define the smallest possible API and interface surfaces: Decouple components and make them interact across the smallest area possible. Even if one area of your application is infected by a breach, others will be safe.
Java security rule #2: Avoid serialisation
This is another coding tip, but it's important enough to be a rule of its own. Serialisation takes a remote input and transforms it into a fully endowed object. It dispenses with constructors and access modifiers, and allows for a stream of unknown data to become running code in the JVM. As a result, Java serialisation is deeply and inherently insecure.
The end of Java serialisation
If you haven't heard, Oracle has long-term plans to remove serialisation from Java. Mark Reinhold, chief architect of the Java platform group at Oracle, has said that he believes a third or more of all Java vulnerabilities involve serialisation.
As much as possible, avoid serialisation/deserialisation in your Java code. Instead, consider using a serialisation format like JSON or YAML. Never, ever expose an unprotected network endpoint that receives and acts upon a serialisation stream. This is nothing but a welcome mat for mayhem.
Java security rule #3: Never expose unencrypted credentials or PII
It's hard to believe, but this avoidable mistake causes pain year after year.
When a user enters a password into the browser, it is sent as plaintext to your server. That should be the last time it sees the light of day. You must encrypt the password via a one-way cypher before persisting it to the database, and then do it again whenever comparing against that value.
The rules for passwords apply to all personally identifiable information (PII): credit cards, social security numbers, etc. Any personal information entrusted to your application should be treated with the highest level of care.
Unencrypted credentials or PII in a database is a gaping security hole, waiting for an attacker to discover. Likewise, never write raw credentials to a log, or otherwise transmit to file or network. Instead, create a salted hash for your passwords. Be sure to do your research and use a recommended hashing algorithm.
Jumping down to Rule #4: always use a library for encryption; do not roll your own.
Java security rule #4: Use known and tested libraries
Feast your eyes on this question-and-answer about rolling your own security algorithm. The tl;dr lesson is: use known, reliable libraries and frameworks whenever possible. This applies across the spectrum, from password hashing to REST API authorisation.
Fortunately, Java and its ecosystem have your back here. For application security, Spring Security is the de facto standard. It offers a wide-range of options and the flexibility to fit with any app architecture, and it incorporate a range of security approaches.
Your first instinct in tackling security should be to do your research. Research best-practices, and then research what library will implement those practices for you. For instance, if you are looking at using JSON Web Tokens to manage authentication and authorisation, look at the Java library that encapsulates JWT, and then learn how to integrate that into Spring Security.
Even using a reliable tool, it is fairly easy to bungle authorisation and authentication. Be sure to move slowly and double check everything you do.
Java security rule #5: Be paranoid about external input
Whether it comes from a user typing into a form, a datastore, or a remote API, never trust external input.
SQL injection and cross-site scripting (XSS) are just the most commonly known attacks that can result from mishandling external input. A less known example--one of many--is the "billion laughs attack," whereby XML entity expansion can cause a Denial of Service attack.
Anytime you receive input, it should be sanity checked and sanitised. This is especially true of anything that might be presented to another tool or system for processing. For example, if something could wind up as an argument for a OS command-line: beware!
A special and well-known instance is SQL injection, which is covered in the next rule.
Java security rule #6: Always use prepared statements to handle SQL parameters
Anytime you build up an SQL statement, you risk interpolating a fragment of executable code.
Knowing this, it's a good practice to always use the java.sql.PreparedStatement class to create SQL. Similar facilities exist for NoSQL stores like MongoDB. If you are using an ORM layer, the implementation will use
PreparedStatements for you under the hood.
Java security rule #7: Don't reveal implementation via error messages
Error messages in production can be a fertile source of information for attackers. Stack traces, especially, can reveal information about the technology you are using and how you're using it. Avoid revealing stack traces to end users.
Failed-login alerts also fall into this category. It is generally accepted that an error message should be given as "Login failed" versus "Did not find that user" or "Incorrect password." Offer as little help to potentially nefarious users as possible.
Ideally, error messages should not reveal the underlying technology stack for your application. Keep that information as opaque as possible.
Java security rule #8: Keep security releases up to date
As of 2019, Oracle has implemented a new licensing scheme and release schedule for Java. Unfortunately for developers, the new release cadence does not make things easier. Nonetheless, you are responsible for frequently checking for security updates and applying them to your JRE and JDK.
Make sure you know what critical patches are available by regularly checking the Oracle homepage for security alerts. Every quarter, Oracle delivers an automated patch update for the current LTS (long-term-support) release of Java. The trouble is, that patch is only available if you are paying for a Java support licence.
Read more on the next page...