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 write a (Java) Burp Suite Professional extension for Tabnabbing attack

Context and goal

The goal of this ticket is to explain how to create an extension for the Burp Suite Professional taking as implementation example the “Reverse Tabnabbing” attack.

“Reverse Tabnabbing” is an attack where an (evil) page linked from the (victim) target page is able to rewrite that page, such as by replacing it with a phishing site. The cause of this attack is the capacity of a new opened page to act on parent page’s content or location.

For more details about the attack himself you can check the OWASP Reverse Tabnabbing.

The attack vectors are the HTML links and JavaScript window.open function so to mitigate the vulnerability you have to add the attribute value: rel="noopener noreferrer" to all the HTML links and for JavaScriptadd add the values noopener,noreferrer in the windowFeatures parameter of the window.openfunction. For more details about the mitigation please check the OWASP HTML Security Check.

Basic steps for (any Burp) extension writing

The first step is to add to create an empty (Java) project and add into your classpath the Burp Extensibility API (the javadoc of the API can be found here). If you are using Maven then the easiest way is to add this dependency into your pom.xml file:

<dependency>
    <groupId>net.portswigger.burp.extender</groupId>
    <artifactId>burp-extender-api</artifactId>
    <version>LATEST</version>
</dependency>

Then the extension should contain  a class called BurpExtender (into a package called burp) that should implement the IBurpExtender interface.

The IBurpExtender  interface have only a single method (registerExtenderCallbacks) that is invoked by burp when the extension is loaded.

For more details about basics of extension writing you can read Writing your first Burp Suite extension from the PortSwigger website.

Extend the (Burp) scanner capabilities

In order to find the Tabnabbing vulnerability we must scan/parse the HTML responses (coming from the server), so the extension must extend the Burp scanner capabilities.

The interface that must be extended is IScannerCheck interface. The BurpExtender class (from the previous paragraph) must register the custom scanner, so the BurpExtender code will look something like this (where ScannerCheck is the class that extends the IScannerCheck interface):

public class BurpExtender implements IBurpExtender {

    @Override
    public void registerExtenderCallbacks(
            final IBurpExtenderCallbacks iBurpExtenderCallbacks) {

        // set our extension name
        iBurpExtenderCallbacks.setExtensionName("(Reverse) Tabnabbing checks.");

        // register the custom scanner
        iBurpExtenderCallbacks.registerScannerCheck(
                new ScannerCheck(iBurpExtenderCallbacks.getHelpers()));
    }
}

Let’s look closer to the methods offered by the IScannerCheck interface:

  • consolidateDuplicateIssues – this method is called by Burp engine to decide whether the issues found for the same url are duplicates.
  • doActiveScan – this method is called by the scanner for each insertion point scanned. In the context of Tabnabbing extension this method will not be implemented.
  • doPassiveScan – this method is invoked for each request/response pair that is scanned.  The extension will implement this method to find the Tabnabbing vulnerability. The complete signature of the method is the following one: List<IScanIssue> doPassiveScan(IHttpRequestResponse baseRequestResponse). The method receives as parameter an IHttpRequestResponse instance which contains all the information about the HTTP request and HTTP response. In the context of the Tabnabbing extension we will need to check the HTTP response.

Parse the HTTP response and check for Tabnabbing vulnerability

As seen in the previous chapter the Burp runtime gives access to the HTTP requests and responses. In our case we will need to access the HTTP response using the method IHttpRequestResponse#getResponse. This method returns a byte array (byte[]) representing the HTTP response as HTML.

In order to find the Tabnabbing vulnerability we must parse the HTML represented by the HTML response. Unfortunately, there is nothing in the API offered by Burp for parsing HTML.

The most efficient solution that I found to parse HTML was to create few classes and interfaces that are implementing the observer pattern (see the next class diagram ):

 

The most important elements are :

The following sequence diagram try to explains how the classes are interacting  together in order to find the Tabnabbing vulnerability.

Final words

If you want to download the code or try the extension you can find all you need on github repository: tabnabbing-burp-extension.

If you are interested about some metrics about the code you can the sonarcloud.io: tabnnabing project.

 

 

How to create and customize a Docker image for Burp Suite Professional Edition

This ticket explains how to create and customize a Docker image for the Burp Suite Professional Edition. The main difference with a creation of an image for the Burp Suite Free Edition is that you will need to register a valid license during the image creation.

    • Create a Dockerfile for the initial image. You will need to have the burpsuite_pro_Vx.y.z jar file; the jar should be in the bin folder that is on the same level as the Dockerfile. The Docker file looks like this:
    FROM openjdk:8u121-jre-alpine
    RUN apk --update add openssl ca-certificates ttf-dejavu && \
        rm -f /var/cache/apk/* && \
        mkdir -p /opt/burp /work && \ 
        adduser -D -s /bin/sh user user && \
        chown -R user /work

    ADD bin/* /opt/burp/
    RUN chown -R user /home/user/.*
    USER user
    WORKDIR /work
    EXPOSE 8080
  • Build the image:
    docker build -t burppro .
  • Run the image. It will be needed to run the Burp in the UI mode in order to register the license and (eventually) to customize the application (like installing extensions); unfortunately it is not possible to install extensions directly from the command line, so you will have to do it manually.
    docker run -ti \
      -e DISPLAY=$DISPLAY \
      -v /tmp/.X11-unix:/tmp/.X11-unix\
    burppro \
       java -jar /opt/burp/burpsuite_pro.jar
  • Once you’ve finished the customization, commit the new image in order to save the changes made on the initial image.
    docker commit <burppro_container_id> burppro_with_license_with_extension
  • Run the new image (in headless mode).
    docker run -p8080:8080 -ti \
    burppro_with_license_with_extension \
      java -jar -Djava.awt.headless=true /opt/burp/burpsuite_pro.jar