Skip to content

Instantly share code, notes, and snippets.

@tfcporciuncula
Last active November 26, 2015 18:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tfcporciuncula/8e7dfe7fa9e92ff0a90d to your computer and use it in GitHub Desktop.
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
// 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 += "&lt=" + 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