How to fix “ClassNotFoundException” for Burp Suite extension using Jersey

Context

I am the maintainer of a BurpSuite extension that is implementing a REST API on top of Burp Suite. The goal of this REST API is to offer basic actions (retrieve a report, trigger a scan, retrieve the list of scanned url) and is executed on a headless Burp Suite from a CICD pipeline.

From the technical point of view, the extension is implemented in Java and I’m using the JAX-RS specification in order to implement the REST-APIs and Jersey as JAX-RS implementation.

Problem

One of the REST entry points was returning a Set<OBJECT> where OBJECT is a POJO specific to the extension. When a client was calling this entry point, the following exception was thrown:

Caused by: java.lang.ClassNotFoundException: org.eclipse.persistence.internal.jaxb.many.CollectionValue
at java.base jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
at org.eclipse.persistence.internal.jaxb.JaxbClassLoader.loadClass(JaxbClassLoader.java:110)

Root Cause

A ClassNotFoundException is thrown when the JVM tries to load a class that is not available in the classpath or when there is a class loading issue. I was sure that the missing class (CollectionValue) was in the extension classpath so the root cause of the problem was a class loading issues.

In Java the classes are loaded by a Java classloader. A Java classloader is a component of the Java Virtual Machine (JVM) responsible for loading Java classes into memory at runtime. The classloader’s primary role is to locate and load class files from various sources, such as the file system, network.

Classloaders in Java typically follow a hierarchical delegation model. When a class is requested for loading, the classloader first delegates the request to its parent classloader. If the parent classloader cannot find the class, the child classloader attempts to load the class itself. This delegation continues recursively until the class is successfully loaded or all classloaders in the hierarchy have been exhausted.

The classloader hierarchy of a thread that is serving a JAX-RS call looks like this:

The classloader hierarchy of the thread that is executing the Burp Suite extension looks like this:

So, the root cause of the ClassNotFoundException is that the classloader hierarchy of the threads serving the JAX-RS calls it does not include the (Burp Suite) extension classloader and so none of the classes from the (Burp Suite) extension classpath can be loaded by the JAX-RS calls.

Solution

The solution is to create a custom classloader that will have to be injected into the classloader hierarchy of the threads serving the JAX-RS calls. This custom classloader will implement the delegation pattern and will contains the original JAX-RS classloader and the Burp Suite extension classloader.

The custom classloader will delegate all the calls to the original Jersey classloader and in the case of loadClass method (which is throwing a ClassNotFoundException) if the Jersey classloader is not finding a class then it will delegate the call to the Burp Suite extension classloader.

The custom classloader will look like this:
public class CustomClassLoader extends ClassLoader{
  private final ClassLoader burpClassLoader;
  private final ClassLoader jerseyClassLoader;
  
  public CustomClassLoader(
                            ClassLoader bcl,
                            ClassLoader jcl){
     this.burpClassLoader = bcl;
     this.jerseyClassLoader = jcl;
  }

 @Override
  public String getName(){
     return "CustomJerseyBurpClassloader";
  }
  
 @Override
  public Class<?> loadClass(String name)
      throws ClassNotFoundException {
     try {
        return this.jerseyClassLoader.loadClass(name); 
     } catch (ClassNotFoundException ex) {
         //use the Burp classloader if class cannot be load from the jersey classloader
        return this.burpClassLoader.loadClass(name); 
    }    
  } 

//all the other methods implementation will just delegate 
//to the return jerseyClassLoader
//for ex:
 @Override
  public URL getResource(String name) {
  return return this.jerseyClassLoader.getResource(name);  
  }
 .......
}  

Now, we have the custom classloader; what is missing is to replace the original Jersey classloader with the custom one for each REST call of the API. In order to do this, we will create a Jersey ContainerRequestFilter which will be called before the execution of each request.

The request filter will look like this:
public class ClassloaderSwitchFilter 
  implements ContainerRequestFilter {
  @Override
  public void filter(ContainerRequestContext requestContext) 
        throws IOException {
        Thread currentThread = Thread.currentThread();
        ClassLoader initialClassloader = 
              currentThread.getContextClassLoader();

        //custom classloader already injected
        if (initialClassloader instanceof CustomClassLoader) {
            return;
        }

        ClassLoader customClassloader =
                new CustomClassLoader(
                        CustomClassLoader.class.getClassLoader(),
                        initialClassloader);
        
        currentThread.setContextClassLoader(customClassloader);
  }
}

How to fill-in programmatically ProForma forms into Jira Issues

The goal of this ticket is to present how to programmatically fill-in ProForma form fields that are part of a Jira issue.

Context

Jira is a very popular issue tracking product. The Jira functionalities can be extended via different plugins that can be found on Jira Marketplace. One of this plug-ins is ProForma Forms that is a plug-in for building forms and checklists directly into Jira.

Problem

Most of the Jira functionalities are also accessible programmatically via a REST APIs and ProForma also offers a REST API to programmatically manipulate forms. But taking a closer look to the ProForma API, there is no entry point for filling-in the fields of a form:

ProForma API for interacting with forms into an Jira Issue

Solution

The (logical) workflow of (programmatically) add a form to an issue is to :

  1. Create the issue (using Jira API)
  2. Attach the desired form/s to the issue (using the ProForma API entry point /rest/proforma/1/issue/{issueKey}/form )
  3. Fill in the form/s fields (no API to do this)
  4. Submit the form/s (using the ProForma API entry point /rest/proforma/1/issue/{issueKey}/form/{formId}/submit)

To fill in the fields programmatically it is possible to :

  • During the form design add a default answer so the form will be pre-fill with this default answer
  • During the form design link each of the form fields to Jira fields and fill in this Jira fields during the creation of the Jira issue:

For more information about how to link ProForma fields to Jira fields you can see also this: Linking ProForma Fields to Jira Fields

Book review: Building microservices (part 1)

This is the first part of the review of the Building Microservices book.

Chapter 1: Microservicesmicroservices

This first chapter is a quick introduction to microservices, the definition, the concept genesis and the key benefits. The microservices idea have emerged from the (new) ways of crafting software today, this new ways implies the use domain-driven design, the continuous delivery, the virtualization, the infrastructure automation and the small autonomous teams.

The author is defining the microservices as “small, autonomous services that work together”.

The key benefits of the microservices are:

  • technology heterogeneity; use the right tool for the right job.
  • resilience; because the microservices have service boundaries quite well defined the failures are not cascading, it’s easy to quick find and isolate the problem(s).
  • scaling; the microservices can be deployed and run independently, so it is possible to choose which microservices need special attention to be scaled accordingly.
  • ease of deployment; microservices are independent by nature so, it can be (re)deployed punctually.
  • optimizing for replaceability; due to autonomous characteristics, the microservices can be easily replaced with new versions.

Chapter 2: The Evolutionary Architect

This chapter is about the role of the architect in the new IT landscape; for the author the qualities of an IT architect a re the following ones: should have a vision and be able to communicate it very clearly, should have empathy so he could understand the impact of his decisions over the colleagues and customers, should be able to collaborate with the others in order to define and execute the vision,  should be adaptable so he can change the vision as the changing of requirements, should be autonomous so he could find the right balance between standardizing and enabling autonomy for the team.

For me this chapter it does not feet very well in the book because all the ideas (from the chapter) could very well be applied to monolithic systems also.

Chapter 3: How to model services

The goal of this chapter is to split the services in the right way by finding the boundaries between services. In order to find the right service boundaries, it must see the problem from the model point of view.

The author introduces the notion of bounded context, notion that was coined by Eric Evans’s in Domain-Driven Design book. Any domain consists of multiple bounded contexts, and residing within each are components that do not need to be communicated outside as well as things that should be shared externally with other bounded contexts. By thinking in terms of model, it is possible to avoid the tight coupling pitfall. So, the each bounded context represents an ideal candidate for a microservice.

This cut on the bounded context is rather a vertical slice but in some situation due to technical boundaries, the cut can be done horizontally.

Chapter 4: Integration

All the ideas of this chapter are around 3 axes; inter-microservices integration, user interface integration with microservices and the COTS (Commercial Off the Shelf Software) integration with microservices.

For the inter-microservices integration different communications styles (synchronous versus asynchronous), different ways to manage (complex) business processes (orchestration versus choreography) and technologies (RPC, SOAP, REST) are very carefully explained with all the advantages and drawbacks. The author tend to prefer the asynchronous-choreographic using REST style, but he emphases that there is no ideal solution.

Then some integration issues are tackled; the service versioning problem or how to (wisely) reuse the code between microservices and/or client libraries and no fit all solution is proposed, just different options.

For the user interface integration with microservices some interesting ideas are presented, like the creation of a different backend api if your microservices are used by different ui technologies (create a backend api for the mobile application and a different backend api for the web application). Another interesting idea is to have services directly serving up UI components.

 The integration of microservices and the COTS part is around the problems that a team should solve in order to integrate with COTS; lack of control (the COTS could use a different technological stack that your microservices), difficult customization of COTS.

Chapter 5: Splitting the Monolith

The goal of this chapter is to presents some ideas about how to split a monolithic application into microservices. The first proposed step is to find portions of the code that can be treated in isolation and worked on without impacting the rest of the codebase (this portions of code are called seams, this word have been coined by Michael Feather in Working Effectively with Legacy Code). The seams are the perfect candidates for the service boundaries.

In order to easily refactor the code to create the seams the authors is advertising the Structure101 application which is an ADE (Architecture Development Environment); for alternatives to Structure 101 you can see Architecture Development Environment: Top 5 Alternatives to Structure101

 The rest of the chapter is about how to find seams in the database and into the code that is using it. The overall idea is that every microservice should have his own independent (DB) schema. Different problems will raise if this happens like the foreign key relationship problem, share of static data stored in the DB, shared tables, the transactional boundaries. Each of this problem is discussed in detail and multiple solutions are presented.

The author recognize that splitting the monolith it’s not trivial at all and it should start very small (for the DB for example a first step would be to split the schema and keep the code as it was before, the second step would be to migrate some parts of the monolithic code towards microservices). It also recognize that sometimes the splitting brings new problems (like the transactional boundaries).

Chapter 6: Deployment

This chapter presents different deployment techniques for the micro services. The first topic that is tackled is how the micro services code should be stored and how the Continuous Integration (CI) process should work; multiple options are discussed: one code repository for all micro services, and one CI server; one code repository by micro service and one CI server, build pipelines by operating system or by directly platform artifacts.

A second topic around the deployment is about the infrastructure on which the micro services are deployed. Multiple deployment scenarios are presented: all micro services deployed on same host, one micro service by host, virtualized hosts, dockerized hosts. The most important idea on this part is that all the deployment and the host creation should be automate; the automation is essential for keeping the team/s productive.