OWASP Zed Attack Proxy (ZAP) is one of my favorite tools for scanning and performing vulnerability tests on a web application. It has a simple GUI to get started, with a large capability for customization to tailor scans as needed.
Below is an excerpt of the code on the landing page.
If the token is not included with the login script as a POST parameter, the request will be rejected. Below is a sample request header, with the POST parameters included under the Form Data. To not be outright rejected, and have a custom 500 error page return, the _token must equal the value of the csrf-token.
This presented a challenge beyond ZAP’s Basic Authentication scripting capabilities. Simply getting ZAP to enter the username and password, as prompted by the application is unsuccessful as it is unable to automatically extract the token value. Attempting to spider or access pages that require authentication result in 500 or 405 errors. The application returned a custom 500 error when failing to access a given page. These error codes indicated that ZAP could not load the correct page.
ZAP scanned only a limited portion of the application, as most of the pages and features required authentication.
Enter Zest, the ZAP scripting language. This language is built around accomplishing tasks, such as fuzzing data, traversal through a website, or logging in. Simon Bennet gives a fantastic basic crash course demo on YouTube for how to work with Zest.
As the authenticated login for this website was a bit complicated, a custom script was needed. There were several different methods of constructing the Zest scripts to perform the scan. By far the easiest is to Record a new Zest Script.
Clicking the button, shown below, in the top navigation bar brings up a popup menu to record all of the actions performed.
For this script, I began the recording at the landing page of the web application. When ZAP recorded this page, it gathered the token, and learned to serve it with the login parameters. This recording extracted the token and automated the login process.
After creating these scripts with Zest, be sure to remove extra steps or page calls to simplify the repeatable process.
In my case, the login steps required only 3 steps: start at the landing page, submit the login, and redirect to the authenticated landing page. Along the way, there were dozens of pages and files accessed to help visually display the page. These page loads were unnecessary for login, so they were removed from the script.
The end result is the script containing the 3 requests shown:
Test the Zest script by pressing the Run button. This executes the script. A 200 HTTP response on the authenticated page indicates the script successfully logged in.
A custom Context is used to apply the Login Script from Zest to run the recorded tasks.
To get the best results, spider the site first using the Context to find all of the authenticated pages that are available. Then an Active Scan is performed on each page identified.
One mistake I discovered was to ensure that the Forced User Mode button is not selected.
Otherwise the token was fixated to the value used when creating the Zest script and not renewed with each test execution. This was very frustrating to overlook as it caused the script to work, but the login attempts to fail.
Another problem was that the login URL for the Zest script needed to begin at the starting homepage and not the login page to work properly. For this web application, the token was set the first time the user accesses the homepage. Therefore, the scripts needed to start at that page to obtain a new token before moving to login.
To do this, set the LoginURL to the starting page within the Context settings. In this case, I needed to use the LoginURL of the homepage, https://[myapp], instead of the login page at https://[myapp]/auth/login.