32 How to Write Secure Java Code

32 How to write secure Java code #

In the previous lesson, we have touched on Java security to some extent. Today, we will delve into more situations in Java development that may affect security. Many security issues have different definitions in specific contexts, although they have similar or consistent essences. This is due to the unique problems brought about by the characteristics of the Java platform. In today’s lesson, I will focus on the perspective of Java developers to discuss code security, rather than talking about general security risks.

The question I want to ask you today is, how to write secure Java code?

Typical Answer #

This question is a bit broad, but we can use a specific type of security risk, such as a Denial of Service (DoS) attack, to analyze the points that Java developers need to focus on.

DoS is a common type of network attack, also known as a “flood attack.” The most common manifestation is the use of a large number of machines to send requests, exhausting the target website’s bandwidth or other resources, rendering it unable to respond to legitimate user requests.

In my opinion, from the perspective of the Java language, more emphasis needs to be placed on program-level attacks, i.e., exploiting vulnerabilities in Java, the JVM, or the application itself to launch low-cost DoS attacks. This is something that must be considered when writing secure Java code. For example:

  • If an older version of JDK and technologies like Applet are used, it is relatively easy for attackers to create legitimate but malicious programs. For example, setting the thread priority to the highest and performing seemingly harmless but resource-consuming tasks. Fortunately, such technologies have gradually exited the stage of history, and in JDK 9 and later, the related modules have been removed.
  • Hash collision attacks mentioned in the previous lecture are a typical example, wherein the attacker can easily consume the system’s limited CPU and thread resources. From this perspective, computationally intensive tasks such as encryption, decryption, and image processing need to be safeguarded against malicious abuse to prevent attackers from consuming system resources through direct or indirect triggering methods.
  • When using Java to build services that involve file uploads or other forms of user input, it is necessary to control the upper limit of system memory or storage consumption since we cannot rely on users’ rational usage for system security. Special attention should be paid to specific attacks such as Zip bomb when dealing with decompression functionality.
  • In addition, there are many types of resources that need to be explicitly released in Java programs, such as file descriptors, database connections, and even reentrant locks. In any case, the successful release of resources should be ensured. Otherwise, even if the program can run normally most of the time, it may be exploited by attackers to deplete certain types of resources. This can also be a potential source of DoS attacks.

Therefore, it can be seen that implementing secure Java code requires consideration of potential security impacts from functional design to implementation details.

Analysis of Examination Points #

Regarding today’s question, using the typical DoS attack as a starting point, I focused on the Java development and introduced the precautions for Java application design and implementation. Later, I will also introduce more comprehensive practices.

In fact, security issues are actually software defects. There is no one-size-fits-all secret recipe for software security. It is both inseparable from risk analysis in design and architecture, and from security practices such as coding and testing in various stages. For interviewers, when examining security issues, in addition to testing specific security domain knowledge, they are more interested in the interviewee’s basic Java programming skills and knowledge accumulation.

Therefore, I will gradually discuss Java secure programming in the following sections. There are no black technologies, only standardized development standards. Many security issues are actually a matter of attitude, depending on whether you take it seriously.

  • I will use some typical code snippets as the starting point to analyze some easily overlooked security risks and introduce hot topics in security issues, such as Java serialization and deserialization.
  • From the perspective of the software lifecycle, I will explore the common security strategies or tools in different stages such as design, development, testing, and deployment.

Knowledge Expansion #

First, let’s take a look at a seemingly insignificant conditional statement. Is there any potential problem here?

// a, b, c are all int type values
if (a + b < c) {
// ...
}

You might wonder, this is just a common conditional statement, what could be the security risk?

The hidden danger here is that numeric types need to be protected against overflow. Otherwise, this not only may lead to logical errors, but in certain situations, it could also result in serious security vulnerabilities.

In terms of language features, Java and the JVM provide many fundamental improvements. Compared to traditional languages ​​like C and C++, Java has much better handling of issues such as array out of bounds, and it natively avoids attacks like buffer overflow, thereby enhancing software security. However, this does not mean that problems are completely eliminated. Java programs may call native code, which is JNI technology, and incorrect numeric values ​​may lead to data out-of-bounds issues at the C/C++ level, which is very dangerous.

Therefore, for the above conditional statement, it is necessary to check the range of its numeric values. For example, you can write a structure similar to the following:

if (a < c - b)

Let’s look at another example. Please take a look at the following exception handling code:

try {
// business code
} catch (Exception e) {
throw new RuntimeException(hostname + port + " doesn't respond");
}

This code includes sensitive information in the exception message. Imagine if it is a web application and the exception is not properly wrapped, there is a high possibility of exposing internal information to end users. The ancient saying “the more you speak, the more likely you will make mistakes” is meaningful. Although it was not originally referring to software security, minimizing the exposure of information is also one of the basic principles of ensuring security. Even if we do not consider certain information to have security risks, my suggestion is to not expose it if it is not necessary.

Such exposure can also occur through other means. For example, a famous programming technology website once exposed all usernames and passwords. This information was stored in plaintext and the transmission process was not necessarily encrypted. In similar cases, exposure is just a matter of time.

For systems with particularly high security standards, it may even require sensitive information to be immediately destroyed in memory after being used, to prevent detection, or to avoid accidental exposure during core dump.

Third, Java provides innovative features such as serialization, which are widely used in remote invocation and other aspects, but they also bring complex security issues. Serialization is still a frequent scene for security issues even today.

Regarding serialization, the usual recommendation is:

  • Sensitive information should not be serialized! In coding, it is recommended to use the transient keyword to protect it.
  • In deserialization, it is recommended to implement the same security checks and data validation as the object construction process in readObject.

In addition, in JDK 9, Java introduced a filtering mechanism to ensure that data in the deserialization process must undergo basic validation before it can be used. The principle is to use blacklists and whitelists to restrict secure or insecure types, and you can customize them and flexibly configure them through environment variables. For more specific usage, you can refer to ObjectInputFilter.

From the introduction above, you may have noticed that many security issues stem from very basic programming details, such as immutable objects, encapsulation, etc., all of which involve security considerations. From a practical perspective, it is necessary for everyone to understand and master these principles, but it is not very realistic. Are there any engineering practices that can help us identify security risks?

Development and testing phase

In actual development, there are various features that may not be fully considered. I suggest that there is no need to implement everything from scratch. Instead, try to use widely validated tools and libraries, whether they come from the JDK itself or from third-party organizations like Apache. Continuous code security improvement through community feedback.

Applying code style conventions during development is an effective way to avoid security issues. I especially recommend Alibaba’s “Alibaba Java Development Manual” and its accompanying tools, which systematically introduce industry practices in Java and other areas to software development in China, effectively improving code quality.

Of course, everything has its cost, and conventions will increase development costs and may affect the pace of iterations. Therefore, for teams at different stages and with different requirements, it is possible to adapt the conventions according to their own circumstances.

In actual development processes, the OpenJDK team applies several practices from different perspectives:

  • In the early design phase, security experts assess the risks of new features.
  • During the development process, especially during code reviews, the OpenJDK’s own customized code conventions are applied.
  • Multiple static analysis tools like FindBugs, Parfait, etc., are used to help detect potential security risks early and adopt a zero-tolerance attitude towards corresponding issues, requiring them to be resolved.
  • OpenJDK even treats any warnings (such as compilation warnings) as errors and reflects them in the CI process.
  • At critical stages such as code check-ins, hook mechanisms are used to invoke rule checking tools to prevent non-compliant code from entering the OpenJDK code base.

Regarding the selection of static analysis tools, the principle we choose is “good enough.” No tool can discover all problems, so while ensuring functionality, the more significant impact is the efficiency of analysis, or in other words, the noise level of code analysis. No matter how comprehensive the analysis is, if there are too many false positives, useful information will be covered by noise, which is also not conducive to subsequent automated processing and hinders issue investigation.

The above practices are to ensure the stringent quality requirements of the JDK as a fundamental platform. In actual products, you need to consider what level of requirements is reasonable.

Deployment phase

The JDK itself is also software and inevitably has implementation flaws. The security vulnerability patches we often see for JDK updates are actually to fix these vulnerabilities. I recently noticed that a certain company’s backend was exposed to serialization-related vulnerabilities due to the use of a lower version of the JDK. Similar situations can mostly be solved through deployment if it is not a security-sensitive product.

For security-sensitive products, it is recommended to pay attention to the JDK’s cryptographic roadmap as the same standards should apply to other languages and platforms. Many algorithms that were once considered highly secure have been cracked. Timely upgrading of foundational software is a necessary condition for security.

Attack and defense are asymmetric. As long as there is a severe vulnerability, it is enough for attackers. Therefore, one should not rely on a black-box deployment to ensure system security, as it cannot guarantee complete security. Attackers can use software design guesses and various methods to discover vulnerabilities.

Today, I used DoS and other typical attack methods as examples to analyze their specific manifestations on the Java platform. I also provided more details on secure coding to help you understand the universality of security issues. Finally, I introduced security practices in the software development cycle, hoping to be helpful to your work.

Practice Exercise #

Do you have a clear understanding of the topic we discussed today? Have you encountered any specific security issues in Java development? And how did you solve them?

Please write your thoughts on this question in the comments section. I will select the comments that have been carefully considered and give you a learning reward coupon. I welcome you to discuss with me.

Don’t forget that I will be a guest on “Geek Live” at 8:30 tonight, where we will talk about everything related to Java interviews. Just click on “Geek Live” in the “Geek Time” App to join the live session. See you there tonight.

Are your friends also preparing for interviews? You can “invite your friends to read” and share today’s question with them. Perhaps you can help them too.