First steps in writing a custom OWASP ZAP extension

OWASP ZAP is a very popular attack proxy typically used in Web Application penetration tests. Think “Open Source BurpSuite“, and that’s ZAP in a nutshell. It has become my go-to tool for penetration tests, and it definitely is a fantastic piece of software that ticks all my boxes – except one.

The problem : Note taking

ZAP helpfully allows you to add notes to each request made through the proxy. This is easily done through the “History” pane by right-clicking on a request, and clicking on “Note…”

1

This allows you to annotate specific requests where you’ve found something interesting or need to remember for your pentest report, etc. The problem is that ZAP currently doesn’t offer a way of seeing all the notes you’ve taken at once. All it does is show “note icons” in the History pane, indicating a note is present for that particular request:

2

It would be useful to be able to see a summary or overview of all these notes – especially when you’re firing up ZAP first thing in the morning and need to remember what you were up to the day (or worse… the month) before during pen-testing.

One solution: a basic “All In One Notes” extension

ZAP is written in Java, with a GUI based on Java Swing. My objective is to extend this GUI and include an “All In One Notes” pane that shows a tabular summary of all notes present in the session. Later revisions of this extension should be a lot more interactive, allowing for navigating to the corresponding request and so on, but for now a simple text pane would suffice. This is the result:

3

Much easier to see what’s going on with all your notes!


Developing the extension

– Clone the “alpha” branch of the “zap-extensions” github repository

We need some scaffolding to get us started with the extension, so go ahead and download the alpha branch of the zap-extensions github repository here:

https://github.com/zaproxy/zap-extensions/tree/alpha

Once you’ve downloaded and extracted the repository in some directory, navigate to the directory and the following subpath:

zap-extensions-alpha > src > org > zaproxy > zap > extension

There you should see a folder called “simpleexample“. Copy that folder and give it an appropriate name that reflects your new extension. In this example we created a new folder called “AllInOneNotes“.


– Import the code into your favorite IDE

In my case, the IDE of choice is IntelliJ. So fire up IntelliJ and select:

File > New Project > Project From Existing Sources

Step through the import wizard, in my case everything was left as per defaults.


– Import the ANT build.xml file

Unfortunately the ZAP team chose to go with ANT rather than maven, but no big deal. In IntelliJ, on the right hand side, click on “Ant Build” and click on the “+” sign to add a valid XML. The build.xml file should be under:

zap-extensions-alpha > build > build.xml

Once you import the XML, IntelliJ should display all the ANT build targets:

4.png

In order to get a deploy target for your custom extension, you need to add a deploy target, so open the build.xml file and search for

deploy-simpleexample

This is the target entry for the “simpleexample” extension. We need a similar target so copy/paste a new entry right underneath that should look like so:

<target name="deploy-simpleexample" description="deploy the simple example add-on">
    <build-deploy-addon name="simpleexample" />
</target>

<!-- New Entry for custom extension -->
<target name="deploy-allinonenotes" description="deploy the all in one notes add-on">
    <build-deploy-addon name="AllInOneNotes" />
</target>

You should now see a new deploy target in the IntelliJ ANT build as per the above screenshot.


Code the extension appropriately

The main file you’d need to edit is under “src>org>zaproxy>zap>extension”, find your custom extension directory.  A full code listing can be found at the end of this article.

A few notes and tips:

  • Make sure to edit “src > org > zaproxy > zap > extension > ExtensionName > resources > Messages.properties“.
  • Also ensure you edit the “ZapAddOn.xml” file appropriately
  • At the moment the extension does not reload notes automatically when they are changed / added / deleted. You need to do this manually within ZAP via the Tools Menu > All In One Notes: Reload

5.png


Build and use the extension

This stage is straightforward. Click on the deploy target for the ANT build. In my case it’s deploy-allinonenotes as discussed above. This will create a new file under the directory

zap-extensions-alpha > build > zap-exts

If all goes well you should see a new .ZAP file here. In ZAP, you can now load this new extension via “File > Load Add-On File”

That’s it! You can find a copy of the ZAP file for this extension here


Code Listing

/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.zap.extension.AllInOneNotes;
import java.awt.CardLayout;
import java.awt.Font;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.*;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.db.DatabaseException;
import org.parosproxy.paros.extension.AbstractPanel;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.extension.ExtensionHookView;
import org.parosproxy.paros.extension.history.ExtensionHistory;
import org.parosproxy.paros.model.HistoryReference;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.zaproxy.zap.utils.FontUtils;
import org.zaproxy.zap.view.ZapMenuItem;
public class ExtensionAllInOneNotes extends ExtensionAdaptor {
private ZapMenuItem menuExample;
// The name is public so that other extensions can access it
public static final String NAME = "ExtensionAllInOneNotes";
// The i18n prefix, by default the package name – defined in one place to make it easier
// to copy and change this example
protected static final String PREFIX = "allInOneNotes";
/**
* Relative path (from add-on package) to load add-on resources.
*
* @see Class#getResource(String)
*/
private static final String RESOURCES = "resources";
private static final ImageIcon ICON = new ImageIcon(
ExtensionAllInOneNotes.class.getResource(RESOURCES + "/notepad.png"));
private AbstractPanel statusPanel;
private static final Logger LOGGER = Logger.getLogger(ExtensionAllInOneNotes.class);
private static ExtensionHookView hookView;
public ExtensionAllInOneNotes() {
super(NAME);
setI18nPrefix(PREFIX);
}
@Override
public void hook(ExtensionHook extensionHook) {
super.hook(extensionHook);
// As long as we're not running as a daemon
if (getView() != null) {
extensionHook.getHookMenu().addToolsMenuItem(getMenuExample());
hookView = extensionHook.getHookView();
hookView.addStatusPanel(getStatusPanel());
}
}
@Override
public boolean canUnload() {
// The extension can be dynamically unloaded, all resources used/added can be freed/removed from core.
return true;
}
@Override
public void unload() {
super.unload();
// In this example it's not necessary to override the method, as there's nothing to unload
// manually, the components added through the class ExtensionHook (in hook(ExtensionHook))
// are automatically removed by the base unload() method.
// If you use/add other components through other methods you might need to free/remove them
// here (if the extension declares that can be unloaded, see above method).
}
private AbstractPanel getStatusPanel() {
ExtensionHistory extHist = (ExtensionHistory) org.parosproxy.paros.control.Control.getSingleton().
getExtensionLoader().getExtension(ExtensionHistory.NAME);
JTextPane pane = new JTextPane();
pane.setEditable(false);
StringBuilder sb = new StringBuilder();
sb.append("<html><body style='overflow:auto;'><table border='1'><tr>\n" +
" <th>Request ID</th>\n" +
" <th>Note</th> \n" +
" </tr>");
if (extHist != null) {
int LastHistoryId = extHist.getLastHistoryId();
int i=0;
while (i++ < LastHistoryId) {
HistoryReference hr = extHist.getHistoryReference(i);
if (hr != null) {
if (hr.hasNote()) {
try {
String note = hr.getHttpMessage().getNote();
sb.append("<tr><td>"+String.valueOf(i)+"</td><td>"+note+"</td></tr>");
} catch (HttpMalformedHeaderException e) {
} catch (DatabaseException e) {
e.printStackTrace();
}
}
}
}
}
sb.append("</table></body></html>");
// Obtain (and set) a font with the size defined in the options
pane.setFont(FontUtils.getFont("Dialog", Font.PLAIN));
pane.setContentType("text/html");
pane.setText(sb.toString());
JScrollPane scrollPane = new JScrollPane(pane);
if (statusPanel != null) {
statusPanel.removeAll();
statusPanel.add(scrollPane);
} else {
statusPanel = new AbstractPanel();
statusPanel.setLayout(new CardLayout());
statusPanel.setName(Constant.messages.getString(PREFIX + ".panel.title"));
statusPanel.setIcon(ICON);
statusPanel.add(scrollPane);
}
return statusPanel;
}
private ZapMenuItem getMenuExample() {
if (menuExample == null) {
menuExample = new ZapMenuItem(PREFIX + ".topmenu.tools.reload");
menuExample.addActionListener( ae > {
hookView.addStatusPanel(getStatusPanel());
});
}
return menuExample;
}
@Override
public String getAuthor() {
return "David Vassallo";
}
@Override
public String getDescription() {
return Constant.messages.getString(PREFIX + ".desc");
}
@Override
public URL getURL() {
try {
return new URL(Constant.ZAP_EXTENSIONS_PAGE);
} catch (MalformedURLException e) {
return null;
}
}
}