True Confessions: Java Microservices – Part 1

Microservices have taken the development community by storm over the past several years. Adoption of microservices further enables continuous delivery with the ability to evolve the underlying technology stack overtime. At Adversa Labs, we assist our customers in the automated security testing of their Java based microservices as a part of our private early access program. This is Part I in a series of posts detailing the true confessions of testing Java microservices in the field.

Our first story is centered around a Java development team within an e-commerce company. This team is responsible for building out an order processing system with all the goodies you might expect: shopping cart, checkout, order history, etc. This order processing system was originally a monolithic application containing code similar to the following:

public List search(String emailAddress, String order) {
  return entityManager
    .createNativeQuery("SELECT * FROM User WHERE email_address = ? ORDER BY id " + order)              
    .setParameter(1, emailAddress)
    .getResultList();
}

This code attempts to query a database using the Java Persistence API, correctly making use of variable binding for the emailAddress parameter.  The order variable, however, is dynamically concatenated when building the query on line #3. A malicious hacker that is able to influence the value of the order parameter would be able to carry out a devastating SQL Injection attack. A manual code review for all uses of the search method confirmed that the order parameter values were always either a string literal (ex: “DESC”) or were retrieved from a trusted source (ex: properties file). As such, this code smell was never addressed.

The same developers recently migrated this portion of the monolithic application into a microservice, exposing the search function similar to the following Spring Boot code:

@GetMapping(value = "/search")
public @ResponseBody List search(
  @RequestParam("emailAddress") String emailAddress,
  @RequestParam("order") String order
) {
  LOGGER.info("searching by email address: " + emailAddress + " with " + order + " order");

  List users = entityManager
    .createNativeQuery("SELECT * FROM User WHERE email_address = ? ORDER BY id " + order)
    .setParameter(1, emailAddress)
    .getResultList();

  LOGGER.info("search for " + emailAddress + " yielded " + users.size() + " results");
  return users;
}

This code effectively exposes the search function as a REST API at “/search” where the emailAddress and order parameters are retrieved from the HTTP request. With this migration, the previously unexploitable code smell has become a full blown, remotely exploitable SQL Injection vulnerability.

There are numerous ways in which this particular vulnerability could be exploited. The goal of the malicious hacker is to ensure their attack produces valid SQL code. Consider when a malicious hacker provides an order value of (excluding quotes) “DESC; DROP TABLE User;–“. The resulting query would completely delete all users in the Users table. More potential attack payloads can be found in the Exploit Database.

The fix required the developers to implement positive input validation against the order parameter. Rather than accepting an arbitrary String value, the developers create an enum with two possible values: ASC and DESC. Any attempts to submit an order value not defined in the corresponding enum would fail. Note that use of parameterized queries, also referred to as variable binding, was not an option as such binding capabilities are limited to parameters in the WHERE clause.

SQL Injection is a classic and fairly well-known vulnerability that appears to be surfacing more frequently in microservices and is something you can test for during your build process. In our experience this is in part due to the decomposition of legacy monolithic applications into microservices. Perhaps this is another form of technical debt that has to be paid during such a migration.

Stay tuned for Part 2 of our series where we discuss how one simple request against one simple microservice led to the complete compromise of an entire set of microservices. Until next time!