One of the most common types of attack an application can suffer is what we call SQL injections. Since SQL injection attacks are both common and potentially devastating, it’s essential you not only are aware of them but also know how to defend your applications.
That’s what this post is about: helping you protect your Java apps against SQL injections. A few other security vulnerabilities are included in the mix as well. We’ve already published a similar post, targeting SQL injections in Python. Today’s post is about the Java language.
Does that sound good? Then let’s get started.
An introduction to SQL injections
SQL injections—also known as SQLi—happen when an attacker successfully tampers with the input of a web application, gaining the ability to execute arbitrary SQL queries on that application.
The way the attack generally works is by exploiting the escape characters that programming languages use to enclose strings. Attackers come up with clever ways to use form fields or URL parameters to input text that alters the SQL code the application generates; that way, attackers can inject—hence the name—additional SQL code that allows them to perform unauthorized actions on the targeted database.
What can a SQL injection do?
A successful SQL injection attack can give its author the power to change data in the target database. If the target application uses a database string connection whose user has write privileges to the database, the SQLi attack can inflict devastating damage. The attacker could delete large amounts of data or even drop the tables themselves.
Alternatively, the attacker could gain only read access to the database. If that doesn’t sound as bad as the previous scenario, think again. An attacker gaining access to unauthorized information would be extremely problematic. That could lead to sensitive data being exposed, including not only business-sensitive information, such as financial information or industrial secrets, but also personal information about customers.
In a world where privacy regulations such as GDPR are more and more common—and severe—data breaches are one of the most dangerous consequences of SQL injections.
SQL injections: Java isn’t immune to them
The Java language has been around for decades. Java developers have a rich ecosystem at their disposal, complete with robust application frameworks and tried-and-true ORMs.
Unfortunately, frameworks and ORMs aren’t enough to shield a language from SQLi attacks. Take the example of Ruby, for instance. Despite counting with Rails as a stable development framework, 70% of security threats to Ruby apps are still SQL injections.
An innocent example
Let’s see an example. Consider the following excerpt of Java code:
String sql = "select "
+ "id, title, excerpt, body"
+ "from Posts where slug = '"
This example might belong to a CMS, for instance. The code assembles a SQL query by concatenating a value inputted by the user somehow—probably a URL parameter. What’s the problem with this code?
The problem is performing a concatenation with a value provided by the user. A recurring theme you’ll see across most of these tips is this: all input is evil until proven otherwise. Let’s suppose the base URL for this web app is https://example.com/posts. If we go and add my-first-java-project to the URL—making it example.com/posts/my-first-java-project—the resulting SQL code will be:
select id, title, excerpt, body from Posts where slug = 'my-first-java-project'
Nothing seems wrong with that. At least not yet.
Enter the attacker
Now let’s say a less well-intentioned user made the URL look like the following:
It might be hard to understand due to URL encoding, but in the example above, the actual parameter being passed is ‘whatever’ or ‘1’=’1′. So, the resulting SQL code is:
select id, title, excerpt, body from Posts where slug = 'whatever' or '1'='1'
Now the attacker has successfully injected an unauthorized code. The query’s where clause now has an additional conditional that tests whether ‘1’ is equal to ‘1’. Since that’s always true, we’re now effectively retrieving all posts. That doesn’t sound like too much of a problem. But imagine that, instead of posts, we were talking about sensitive customer information. With a little bit of SQL injection, someone could get access to information from different people, which is a huge problem, as we’ve mentioned.
Things can get worse. Imagine the attacker passes the following as a parameter:
That would result in the following SQL query:
select id, title, excerpt, body from Posts where slug = 'whatever ';DROP TABLE 'Posts''
Now, instead of just looking at some information they shouldn’t be allowed to see, our hypothetical attacker has dropped an entire table, with all the data it contained!
Prevention techniques against Java SQL injection
The post up until now might have painted somewhat of a bleak picture. There’s no reason to despair, though. Despite SQL injection attacks being common and potentially devastating, they’re avoidable. The vulnerabilities that SQL injection attacks exploit originate from coding mistakes. So, learning how to avoid these mistakes is your first and most important line of defense against this type of attack.
Use parameterized queries
The first and most important step you can take against SQL injection in Java is to use parameterized queries instead of concatenating values. What does that look like in practice? Here’s an example, based on the query we’ve shown in the previous section:
String sql = "select id, title, excerpt, body from Posts where slug = ?";
As you can see, we’ve replaced the value we want to concatenate with a placeholder, in the form of a question mark. The next step is to create a prepared statement and bind the parameter’s value to it:
Connection connection = dataSource.getConnection();
PreparedStatement p = c.prepareStatement(sql);
We don’t see the rest of the code for the sake of brevity, but after that, you’d only have to execute the code and retrieve the results. By using parameterized queries, we can assemble queries with user-submitted values in a safe way.
Allow list input validation
This approach isn’t an alternative to using prepared statements/parameterized queries, but rather a complement to them.
Whitelisting input validation consists of restricting inputs to a pre-compiled list of known valid values and blocking all of the rest. That includes things like using regular expressions to validate certain types of information, verifying that numeric parameters fall into expected ranges, and checking that parameters meet the expected data type.
You should do this type of validation for all kinds of user input: URL parameters, form fields, content from imported files, and so on.
Execute queries with the least possible privilege
This last item is more of a palliative measure, but it’s essential to have, nonetheless. If everything else fails and an attacker succeeds in their SQLi attempt, it’s essential that your app uses a connection string whose user has the least possible privilege.
Take our hypothetical CMS app as an example. In that specific area of the application, the only database privileges the app needs are reading ones. So, the recommended route there would be to use a connection string that only has reading privileges so that if an attacker is able to inject some unauthorized code, at least they won’t be able to alter or delete data.
Use Java Persistence
Another option to prevent SQL injection could be using JPQL (Java Persistence Query Language). Several implementations of the Java Persistence API exist, the most popular being Spring Data JPA and Hibernate. They provide an extra data layer for applications, which helps to limit an attacker’s ability to use SQL injections.
Java security vulnerabilities in general
SQL injections are one of the most common types of attacks on web applications. They’re relatively easy to perform—against vulnerable targets, that is—but the payoff is immense. So that’s why they happen so frequently.
However, there are many more security threats that Java developers should be aware of. These include the following:
- Malicious jars
- XSS injections
- Java LDAP injections
- XPath injections
- SecurityManager vulnerabilities
It would be out of the scope of this post to give detailed advice on each of these security threats. Instead, we’ll give general tips you can adopt to ensure your Java apps get more secure, as a whole. Here they are:
- Identify third-party vulnerabilities. Modern applications typically have many dependencies on third-party libraries and tools. You must be able to quickly identify vulnerabilities on those and react accordingly. Luckily, this is getting easier and easier due to automated tools, such as GitHub’s Dependabot.
- Implement security early in the SDLC. Ensure you implement secure coding practices and the necessary tooling or processes to support those
- Use application security monitoring and protection tools. Protecting your app in production is key. Tools like Sqreen will give you visibility into your application security with real-time security monitoring and protection, capable of preventing exploits and notifying you about attacks.
SQL injections: Java isn’t immune to them, but you can learn to protect yourself
SQL injections aren’t incredibly sophisticated attacks. However, since they allow attackers to inject malicious SQL code into a web application, they can cause catastrophic damage.
In today’s post, we’ve covered some prevention techniques you can use to avoid falling prey to SQL injections in your Java apps. Just because they’re common attacks, that doesn’t mean they’re unavoidable. With the right mixture of best practices and tools, you’ll certainly be able to protect your Java apps against SQL injectiom attacks and other security threats.
This post was written by Carlos Schults. Carlos is a .NET software developer with experience in both desktop and web development, and he’s now trying his hand at mobile. He has a passion for writing clean and concise code, and he’s interested in practices that help you improve app health, such as code review, automated testing, and continuous build.