Last active
November 26, 2015 18:01
-
-
Save tfcporciuncula/8e7dfe7fa9e92ff0a90d to your computer and use it in GitHub Desktop.
A temporary CAS authentication script for ZAP that should work with SSO disabled
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The authenticate function is called whenever ZAP requires to authenticate for a Context for which this script | |
// was selected as the Authentication Method. The function should send any messages that are required to do the authentication | |
// and should return a message with the authenticated response. | |
// | |
// NOTE: Any message sent in the function should be obtained using the 'helper.prepareMessage()' method. | |
// | |
// Parameters: | |
// helper - a helper class providing useful methods: prepareMessage(), sendAndReceive(msg) | |
// paramsValues - the values of the parameters configured in the Session Properties -> Authentication panel. | |
// The paramsValues is a map, having as keys the parameters names (as returned by the getRequiredParamsNames() | |
// and getOptionalParamsNames() functions below) | |
// credentials - an object containing the credentials values, as configured in the Session Properties -> Users panel. | |
// The credential values can be obtained via calls to the getParam(paramName) method. The param names are the ones | |
// returned by the getCredentialsParamsNames() below | |
/* | |
* @author Thiago Porciúncula <thiago.porciuncula at softplan.com.br> | |
* @author Hugo Baes <hugo.junior at softplan.com.br> | |
* @author Fábio Resner <fabio.resner at softplan.com.br> | |
*/ | |
function authenticate(helper, paramsValues, credentials) { | |
print("---- CAS authentication script has started ----"); | |
// Enable Rhino behavior, in case ZAP is running on Java 8 (which uses Nashorn) | |
if (java.lang.System.getProperty("java.version").startsWith("1.8")) { | |
load("nashorn:mozilla_compat.js"); | |
} | |
// Imports | |
importClass(org.parosproxy.paros.network.HttpRequestHeader) | |
importClass(org.parosproxy.paros.network.HttpHeader) | |
importClass(org.parosproxy.paros.network.HtmlParameter) | |
importClass(org.apache.commons.httpclient.URI) | |
importClass(org.apache.commons.httpclient.params.HttpClientParams) | |
importClass(java.util.regex.Pattern) | |
var loginUri = new URI(paramsValues.get("loginUrl"), false); | |
// Perform a GET request to the login page to get the values generated by CAS on the response (login ticket, event id, etc) | |
var get = helper.prepareMessage(); | |
get.setRequestHeader(new HttpRequestHeader(HttpRequestHeader.GET, loginUri, HttpHeader.HTTP10)); | |
get.setGetParams(new java.util.TreeSet(java.util.Arrays.asList( | |
[new HtmlParameter(HtmlParameter.Type.url, "service", "https://apps1.test.domain.ca/testapp/j_spring_cas_security_check"), | |
new HtmlParameter(HtmlParameter.Type.url, "renew", "true")]))); | |
helper.sendAndReceive(get); | |
var casInputValues = getCASInputValues(get.getResponseBody().toString()); | |
// Build the request body using the credentials values and the CAS values obtained from the first request | |
requestBody = "username=" + encodeURIComponent(credentials.getParam("username")); | |
requestBody += "&password=" + encodeURIComponent(credentials.getParam("password")); | |
requestBody += "<=" + encodeURIComponent(casInputValues["lt"]); | |
requestBody += "&execution=" + encodeURIComponent(casInputValues["execution"]); | |
requestBody += "&_eventId=" + encodeURIComponent(casInputValues["_eventId"]); | |
// Add any extra post data provided | |
extraPostData = paramsValues.get("extraPostData"); | |
if (extraPostData != null && !extraPostData.trim().isEmpty()) { | |
requestBody += "&" + extraPostData.trim(); | |
} | |
// Perform a POST request to authenticate | |
print("POST request body built for the authentication:\n " + requestBody.replaceAll("&", "\n ")); | |
var post = helper.prepareMessage(); | |
post.setRequestHeader(new HttpRequestHeader(HttpRequestHeader.POST, loginUri, HttpHeader.HTTP10)); | |
post.setGetParams(new java.util.TreeSet(java.util.Arrays.asList( | |
[new HtmlParameter(HtmlParameter.Type.url, "service", "https://apps1.test.domain.ca/testapp/j_spring_cas_security_check"), | |
new HtmlParameter(HtmlParameter.Type.url, "renew", "true")]))); | |
post.setRequestBody(requestBody); | |
helper.sendAndReceive(post); | |
/* | |
* At this point we are authenticated, but we are not done yet :( | |
* | |
* We have authenticated on the CAS server (let's say http://mydomain/cas-server), so when we access | |
* the app (i.e. http://mydomain/my-app) for the first time, we will be redirected to the CAS server. | |
* Since we are authenticated, the CAS server will redirect us back to the app: | |
* | |
* http://your.domain/your-app -> http://your.domain/cas-server -> http://your.domain/your-app | |
* | |
* There are two problems here: the first is that ZAP's Spider explicitly doesn't follow redirects. | |
* So if the Spider access the app, it will get a redirect response and the page will never really | |
* be spidered. However, this redirect happens only at the first time we access the app after the | |
* authentication, so we could just access the app once before ZAP's Spider starts, right? Almost. | |
* This is a circular redirect, and the second problem is that ZAP doesn't support it. So, this | |
* strategy works, but we need a bit of code to get us through this. | |
* | |
* This script has another parameter that should hold at least one protected page for each app that | |
* might be analyzed. We then proceed to enable circular redirect for ZAP (reflection to the rescue!) | |
* and do a simple GET for each page, ensuring no redirects will happen during our analysis. | |
*/ | |
// Get the protected pages | |
var protectedPagesSeparatedByComma = paramsValues.get("protectedPages"); | |
var protectedPages = protectedPagesSeparatedByComma.split(","); | |
// Enable circular redirect | |
var client = getHttpClientFromHelper(helper); | |
client.getParams().setParameter(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, true); | |
// Perform a GET request on the protected pages to avoid redirects during the scan | |
for (index in protectedPages) { | |
var request = helper.prepareMessage(); | |
request.setRequestHeader(new HttpRequestHeader(HttpRequestHeader.GET, new URI(protectedPages[index], false), HttpHeader.HTTP10)); | |
helper.sendAndReceive(request, true); | |
} | |
// Disable circular redirect | |
client.getParams().setParameter(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, false); | |
print("---- CAS authentication script has finished ----\n"); | |
return post; | |
} | |
function getCASInputValues(response){ | |
var result = {}; | |
var regex = "<input.*name=\"(lt|execution|_eventId)\".*value=\"([^\"]*)\""; | |
var matcher = Pattern.compile(regex).matcher(response); | |
while (matcher.find()) { | |
result[matcher.group(1)] = matcher.group(2); | |
} | |
return result; | |
} | |
function getHttpClientFromHelper(helper) { | |
var httpSenderField = helper.getClass().getDeclaredField("httpSender"); | |
httpSenderField.setAccessible(true); | |
var httpSender = httpSenderField.get(helper); | |
var clientField = httpSender.getClass().getDeclaredField("client"); | |
clientField.setAccessible(true); | |
return clientField.get(httpSender); | |
} | |
// This function is called during the script loading to obtain a list of the names of the required configuration parameters, | |
// that will be shown in the Session Properties -> Authentication panel for configuration. They can be used | |
// to input dynamic data into the script, from the user interface (e.g. a login URL, name of POST parameters etc.) | |
function getRequiredParamsNames(){ | |
return ["loginUrl", "protectedPages"]; | |
// The protectedPages parameter will normally have a value similar to http://your.domain/your-app/protected/index.html | |
// If your CAS server happens to handle authentication for more than one app (which is the usual case), and you intend | |
// to scan those apps as well, make sure to provide one protected page for each app separated by comma. | |
} | |
// This function is called during the script loading to obtain a list of the names of the optional configuration parameters, | |
// that will be shown in the Session Properties -> Authentication panel for configuration. They can be used | |
// to input dynamic data into the script, from the user interface (e.g. a login URL, name of POST parameters etc.) | |
function getOptionalParamsNames(){ | |
return ["extraPostData"]; | |
} | |
// This function is called during the script loading to obtain a list of the names of the parameters that are required, | |
// as credentials, for each User configured corresponding to an Authentication using this script | |
function getCredentialsParamsNames(){ | |
return ["username", "password"]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment