Extension Example 3: HTTP Proxy JWT Decoder

We will learn how to create a practical plugin that decodes JWT tokens present in the request header under the "Proxy" tab.

In this chapter, we will create a “Proxy” tab extender plugin that will decode the JWT token present in the request header and output its decoded value on the go. To achieve this functionality, we need to implement the IMessageEditorTabFactory interface and register it as we normally do.

Note: IMessageEditorTabFactory implementation function returns an IMessageEditorTab instance which tells Burp what a new tab under Burp Proxy will look like.

Implement IMessageEditorTab Interface

The IMessageEditorTab interface will tell Burp what to do in the new tab that is returned by the IMessageEditorTabFactory.

Below is an example of how the Tab class looks abstractly.

package burp;

import java.awt.*;

public class JWTDecodeTab implements IMessageEditorTab {

    private boolean editable;
    private ITextEditor txtInput;
    private byte[] currentMessage;
    private IBurpExtenderCallbacks callbacks;

    public JWTDecodeTab(IMessageEditorController controller, boolean editable) {
    }

    @Override public String getTabCaption() {
        return null;
    }

    @Override public Component getUiComponent() {
        return null;
    }

    @Override public boolean isEnabled(byte[] content, boolean isRequest) {
        return false;
    }

    @Override public void setMessage(byte[] content, boolean isRequest) {

    }

    @Override public byte[] getMessage() {
        return new byte[0];
    }

    @Override public boolean isModified() {
        return false;
    }

    @Override public byte[] getSelectedData() {
        return new byte[0];
    }
}

Fill up constructor

public JWTDecodeTab(IMessageEditorController controller, boolean editable, IBurpExtenderCallbacks callbacks) {
     this.editable = editable;
     this.callbacks = callbacks;

     /*
      Create an instance of Burp's text editor, to display our JWT decode data.
      */
     txtInput = this.callbacks.createTextEditor();
     txtInput.setEditable(editable);
 }

Other functions

/*
    This will set the name for this tab under Proxy.
 */
    @Override public String getTabCaption() {
        return "JWT Decode";
    }
    /*
    This will return the UI component to be displayed under Tab since we just want to display the text editor
    (editable=false), we will return it. This has been created in Constructor.
     */
    @Override public Component getUiComponent() {
        return txtArea.getComponent();
    }

    /*
    This function will code the logic which will enable or disable this Tab. So since this is the JWT decode tab plugin
    this should enable when there is JWT token present in there.

    To keep this simple I am assuming that, JWT token is present as part of the parameter name `jwtToken`.
    This logic can be complex and process for all the parameters and can check if there is any field with data JWT
    deserializable or not.

    We may see this use case later.
     */
    @Override public boolean isEnabled(byte[] content, boolean isRequest) {
        return isRequest && null != helpers.getRequestParameter(content, "jwtToken");
    }
    @Override public byte[] getMessage() {
        return currentMessage;
    }

    @Override public boolean isModified() {
        return txtArea.isTextModified();
    }

    @Override public byte[] getSelectedData() {
        return txtArea.getSelectedText();
    }

Important Decode function

    @Override public void setMessage(byte[] content, boolean isRequest) {
        /*
        If no data present in the parameter
        */
        if (content == null) {
            /*
            clear our display, which is textArea
             */
            txtArea.setText(null);
            txtArea.setEditable(false);
        }
        else{
            /*
            Get the parameter value and decode it.
             */
            String jwtToken = helpers.getRequestParameter(content, "jwtToken").getValue();

            /*Since JWT token is <base64(alg)>.<base64(data)>.<base64(signature)> in simple terms, so we can use
            normal base64 decode functionality present in helpers to take this out data in plain text fmt.

            Steps :
                - split on '.', '.'(Period) is regex in Java which points to all chars so make sure you use patterns.
                    https://stackoverflow.com/a/3481842
                - decode the first two parts as the third part is just a signature.
             */
            List<String> jwtTokenParts = Arrays.asList(jwtToken.split(Pattern.quote(".")));
            String decodedJwtToken =
                    helpers.bytesToString(helpers.base64Decode(jwtTokenParts.get(0))) +"\r\n" +
                    helpers.bytesToString(helpers.base64Decode(jwtTokenParts.get(1)));

            callbacks.issueAlert("Decoded JWT token " + decodedJwtToken);
            /*
            Set this data in the text field under the tab.
             */
            txtArea.setText(helpers.stringToBytes(decodedJwtToken));
            txtArea.setEditable(editable);
        }
        currentMessage = content;
    }

Building and Loading

Lastly, let’s take a look at the main BurpExtender.java class, which brings everything to life.

Get hands-on with 1200+ tech skills courses.