Common Security Pitfalls in Spring Web Applications

Introduction

Cloudflight recently started a meetup collaboration with SBA Research. The first talk provided by us was held online on March 25th, 2020 and demonstrated some common security pitfalls in web applications developed with the Spring framework. In case you missed it, we will cover some of the content presented at the meetup talk in this blog post.

Spring is an open-source application framework for the Java platform. In several years of penetration testing, we discovered some common pitfalls when using Spring. We are not talking about bugs in the framework here, but some possibly unexpected default configurations, “hidden” features and framework internals unknown to beginners.

Content Negotiation

First, we have a look at a quite common problem. Given a REST API, we want to restrict the fields of an object that are sent to the client. Let’s make it obvious that the field to be hidden is important by calling it “secret”:

public class State {
    private Integer id;
    private String secret ;
    // Constructors, getters, setters, other fields, ...
}

Following method in a REST controller will handle the requests:

@RequestMapping(value="/state/{id}", method=RequestMethod.GET)
@ResponseBody
public State get(@PathVariable("id") Integer id) {
    return repository.get(id);
}

In order to send it to the client as XML, we add the following annotations to the “State” class:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class State {
    private Integer id;
    private String secret ;
    // Constructors, getters, setters, other fields, ...
}

Of course, we do not want “secret” to be send to the client, so we add the @XmlTransient annotation to the field:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class State {
    private Integer id;
    @XmlTransient
    private String secret ;
    // Constructors, getters, setters, other fields, ...
}

In fact, this solution works perfectly when the client requests XML. But… what if this is not the case?

Spring MVC also supports the serialization as JSON by default. There are several (configurable) ways on how to request a different content type from a Spring REST controller, one is the Accept HTTP header. Setting it to application/json will happily return the “State” object as JSON. As the @XmlTransient annotation to hide the “secret” field is specific to the XML format, it is not active when requesting data in the JSON format and the “secret” field will be sent to the client.

One possibility to improve the situation is to add another annotation like @JsonIgnore for JSON to the field. However, this is not really a reliable solution. If the underlying serialization library is replaced, the annotation might be ignored by the changed implementation. A safer but verbose way to prevent this issue is to create a separate class without the “secret” field, which is then used to send the object to the client. Finally, it is possible to restrict the content type to the desired one either per controller class, per controller method or globally for the whole application.

Unprotected Dependency Endpoints

There are several awesome libraries which support the development and monitoring of applications. For example, JavaMelody only requires a single dependency to be added to the project to be up and running. It then provides a nice web-based overview of the state of the application (e.g. resource usage, HTTP sessions, processes on the server, …).

However, with this simplicity, it is tempting to overlook the security of the automatically exposed endpoints. These libraries may leak sensitive information or provide dangerous debug functionality without the developers being aware of that. Exact attack scenarios naturally depend on the corresponding library and its functionality.

To avoid exposing endpoints unintentionally, regularly check your direct but also transitive dependencies. Sadly, this is not really feasible for large projects with potentially huge dependency graphs. The Spring Boot Actuator provides a REST endpoint listing all URL mappings (/actuator/mappings). You might be surprised to not see the /monitoring endpoint listed here. Because JavaMelody is implemented as a filter, it is not listed in the URL mappings. We can add a custom Actuator endpoint that lists all filters:

@Component
@Endpoint(id="filters")
public class FilterEndpoint {
    private ServletContext servletContext;

    @Autowired
    public FilterEndpoint(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    @ReadOperation
    public List<String> getFilters() {
        return new ArrayList<>(servletContext.getFilterRegistrations().keySet());
    }
}

After activating the endpoint in the application properties, it serves the names of all filters under /actuator/filters.

Another very simple possibility is to check the route mappings and filters that are printed by Spring Boot at startup.

Note that although the integration of Spring Security automatically requires authentication for all endpoints by default, applications may allow users to register accounts themselves and thus become authenticated rather easily.

Conclusion

In this blog entry, we had a look at two common security pitfalls in Spring web applications.

  • Spring MVC is configured to be able to serialize objects in REST controllers to XML and JSON by default. Be cautious when using format-specific annotations in serialized objects.
  • Dependencies might expose additional endpoints without the developers being aware of that. Make sure you have an overview of your dependencies and routes. Tools like the Spring Boot Actuator can be of great help here.
  • Finally, we want to emphasize that Spring is an amazing framework for application development. Don’t let a few pitfalls stop you from digging deeper into it.

That’s it, see you at the next meetup!