ZAP Extension - Send To Postman
I wrote a little extension for OWASP-ZAP. Here’s a bit about how and why.
For a long time now, I’ve been thinking about APIs. In my day-to-day I do a lot of web app testing, and as you can imagine there’s often an API of some kind. I always tested them as best I can, but I also always wondered if there were ways that the process could be more automated and efficient.
Turns out I was not the only one thinking about this because earlier this year Corey Ball (AKA @hapi_hacker released his book Hacking APIs via No Starch Press. This was an insta-buy for me and I read it straight through as soon as it was delivered. There are several cool tricks in it, especially if you’re relatively new to testing APIs. Several of those tricks make use of Postman, which is a tool for designing and testing APIs.
Next month (maybe) I’ll go into more detail on a new technique I came up with after reading the book. For now, suffice it to say that there are some fun things that can be done by combining ZAP and Postman. But in order do anything you first need to have all of the API endpoints that you want to test in a collection in Postman. If you don’t have access to a saved Postman collection or a Swagger/OpenAPI doc, then you’re going to have to get them in there yourself.
One way to do this is by using Postman’s “Capture” mode. Basically it sets Postman up to listen as a proxy. You can point your browser to Postman and explore a website and all of the HTTP requests will appear in Postman’s history tab. From there you can locate API-related requests and add them into a collection for further analysis.
It’s pretty nice but it just doesn’t fit into my workflow. I work in ZAP primarily. I could make the above technique work by chaining ZAP to Postman or vice versa, but it’s inefficient - I would end up with identical histories in each tool, and I would have to review the history twice - once in Postman to identify API calls and once in ZAP to identify anything else of interest.
What I really wanted was a way to send API endpoints from ZAP directly to Postman. Ideally I wanted to be able to highlight one or more items in the ZAP history pane, right-click and say “Send to Postman”. Seemed like a good excuse to get a little better at writing scripts for ZAP and being able to interpret its massive Javadoc.
A few months ago a work colleague mused about writing a ZAP script that would do a drive-by test for unsafe deserialization. Mostly just to see if I could do it, I did it. Briefly, it uses ZAP’s Jython scripting support to allow me to call ysoserial on the fly using os.system()
and dynamically generate unique serialized payloads so that I have some hope of figuring which one succeeded if any were to actually succeed. I doubt I’ll ever find any actual bugs with this script but it was a good learning exercise. This script is not currently publicly available but that may change in the future.
As soon as I finished reading Hacking APIs I wanted to have this “Send to Postman” script and I wanted it fast. I needed a way to replay a request from ZAP, including any headers and body content, through another proxy. This is a pretty easy thing to do with curl
and in fact ZAP comes out of the box with a Copy As Curl Command extension that basically does everything I need. I just had to modify the curl
command to specify Postman’s IP/port as proxy and then have it run the command directly just like I did in the deserialization script. Easy-peasy. And it worked but it was janky. You can see it here.
First off, if I tried to run it on multiple items, it would only do one of them and ignore the rest. Second, I set it up as a “Targeted” script. ZAP has a bunch of different script types and the type that you choose influences certain things about when you can invoke it and how it gets invoked. A “Targeted” script can be invoked by right-clicking something choosing “Invoke with script…” and then choosing a script. I chose this mainly because it easily achieved the goal of being able to right-click and send to Postman. There are ways to hook the UI directly and I’ve done it in Javascript but never in Python, so this seemed easier. Unfortunately in addition to not being able to work with multiple items (maybe there is a way but I don’t know it), it seems that “Targeted” scripts also always bring the Script Console to the foreground even if the script gives no output. So every time I sent a request to Postman, ZAP would jump to the Script Console and hide my Request/Response tabs. It wasn’t awful but it just felt unpolished. I put it on github but never shared it with anyone.
But, it had been on my mind ever since to rewrite it in a better way. My plan was to write it as an “Extender” script, since there are few restrictions on those scripts. For example with an “Extender” script I can add an item the context menu that pops up when you right-click the History pane (so, one less click to use it). In fact I already had Javascript code to do just that; it only needed to be ported to Python. Which was not difficult per se but it took me a while to figure out. Then I planned to replace the hacky process of building a curl
command and sending it via os.system()
with something that uses requests
. This turned out to be difficult because Jython does not yet support Python 3, so I would need to somehow get the Python 2.7 requests
wheel onto my system for Jython to access. I’m sure this can be done but then it’s much harder for me to share the script with the world. Back to the drawing board.
After some searching through the ZAP Google Groups, I finally came upon this post which has a Javascript snippet for sending a request using native ZAP APIs. Further spelunking through the ZAP Javadoc revealed that Model.getSingleton().getOptionsParam().getConnectionParam()
essentially returns a hook into ZAP’s “Connection” settings pane, where one can configure the upstream proxy/proxychain setting! With that last step in place, my script was complete - now I can send requests to Postman natively from a right-click context menu. Best of all, enabling multi-select in this way is as simple as setting a flag when you instantiate the pop-up menu item. ZAP automagically just runs performAction() on every selected item. This is a little bit inefficient because it sets and then unsets the Postman proxy settings on every single request, but it should be fine in practice.
So there you have it. Download the extension and load it into the Extender folder of your Scripts tree. Set your Postman IP and Port in the script and save it (if you are using Postman’s defaults you should not need to change anything). Don’t forget to activate it and optionally set it to load when ZAP starts.
Then you can explore your target and send interesting API endpoints to Postman as you go just by right-clicking them and choosing “Send To Postman”. You can even configure Postman to put the requests directly into a collection to save yourself another step.
Happy hunting!