Skip to content

Commit 2274f74

Browse files
authored
Merge pull request #297 from psiinon/js-sel-auth
JuiceShop selenium auth example
2 parents 5705788 + fe211e4 commit 2274f74

File tree

12 files changed

+734
-0
lines changed

12 files changed

+734
-0
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
This is part of a set of scripts which allow you to authenticate to Juice Shop using Selenium.
3+
4+
These scripts will currently only run in Oracle Nashorn and not Graal.js
5+
which means you need to run ZAP using Java 11.
6+
7+
---
8+
9+
This script handles authentication for requests that originate from ZAP,
10+
e.g. from the traditional spider or the active scanner.
11+
12+
It launches a browser to authenticate to Juice Shop - this is not strictly
13+
necessary but this is a demonstration of what to do if you need authenticate
14+
via a browser.
15+
16+
It also starts and uses a new proxy on a different port.
17+
If this is not done then the script will hang as it will try to authenticate
18+
again using the script which is already running.
19+
20+
The proxy can be stopped via the JuiceShopReset script.
21+
*/
22+
23+
var By = Java.type('org.openqa.selenium.By');
24+
var Cookie = Java.type("org.openqa.selenium.Cookie");
25+
var HttpRequestHeader = Java.type('org.parosproxy.paros.network.HttpRequestHeader');
26+
var HttpResponseHeader = Java.type("org.parosproxy.paros.network.HttpResponseHeader");
27+
var HttpHeader = Java.type('org.parosproxy.paros.network.HttpHeader');
28+
var ScriptVars = Java.type("org.zaproxy.zap.extension.script.ScriptVars");
29+
var System = Java.type("java.lang.System");
30+
var Thread = Java.type('java.lang.Thread');
31+
var URI = Java.type('org.apache.commons.httpclient.URI');
32+
33+
var extensionNetwork = control.getExtensionLoader().getExtension("ExtensionNetwork");
34+
35+
var juiceshopAddr = "http://localhost:3000/";
36+
var proxyAddress = "127.0.0.1";
37+
var proxyPort = 9092;
38+
39+
var count = 0;
40+
var limit = 2;
41+
42+
function logger() {
43+
print('[' + this['zap.script.name'] + '] ' + arguments[0]);
44+
}
45+
46+
function messageHandler(ctx, msg) {
47+
if (ctx.isFromClient()) {
48+
return;
49+
}
50+
var url = msg.getRequestHeader().getURI().toString();
51+
//logger("messageHandler " + url);
52+
if (url === juiceshopAddr + "rest/user/login" && msg.getRequestHeader().getMethod() === "POST") {
53+
var json = JSON.parse(msg.getResponseBody().toString());
54+
var token = json.authentication.token;
55+
logger("Saving Juice Shop token");
56+
// save the authentication token
57+
ScriptVars.setGlobalVar("juiceshop.token", token);
58+
}
59+
}
60+
61+
function authenticate(helper, _paramsValues, _credentials) {
62+
// Remove an existing token (if present) - in theory it may now be invalid
63+
ScriptVars.setGlobalVar("juiceshop.token", null);
64+
var proxy = ScriptVars.getGlobalCustomVar("auth-proxy");
65+
if (!proxy) {
66+
// We need to start a new proxy so that the request doesn't trigger another login sequence
67+
logger("Starting proxy");
68+
var proxy = extensionNetwork.createHttpProxy(5, messageHandler);
69+
proxy.start(proxyAddress, proxyPort);
70+
// Store the proxy in a global script var
71+
ScriptVars.setGlobalCustomVar("auth-proxy", proxy);
72+
}
73+
74+
logger("Launching browser to authenticate to Juice Shop");
75+
var extSel = control.getSingleton().
76+
getExtensionLoader().getExtension(
77+
org.zaproxy.zap.extension.selenium.ExtensionSelenium.class);
78+
79+
// Change to "firefox" (or "chrome") to see the browsers being launched
80+
var wd = extSel.getWebDriver(5, "firefox-headless", proxyAddress, proxyPort);
81+
logger("Got webdriver");
82+
83+
// Initial request will display a popup that is difficult to get rid of
84+
wd.get(juiceshopAddr);
85+
wd.manage().addCookie(new Cookie('cookieconsent_status', 'dismiss'));
86+
wd.manage().addCookie(new Cookie('welcomebanner_status', 'dismiss'));
87+
Thread.sleep(1000);
88+
// This request will get the login page without the pesky popup
89+
logger("Requesting login page");
90+
wd.get(juiceshopAddr + "#/login");
91+
Thread.sleep(1000);
92+
93+
// These are standard selenium methods for filling out fields
94+
// You will need to change them to support different apps
95+
wd.findElement(By.id("email")).sendKeys(System.getenv("JS_USER"));
96+
wd.findElement(By.id("password")).sendKeys(System.getenv("JS_PWD"));
97+
wd.findElement(By.id("loginButton")).click();
98+
logger("Submitting form");
99+
100+
Thread.sleep(500);
101+
wd.quit();
102+
103+
Thread.sleep(500);
104+
logger("Checking verification URL for Juice Shop");
105+
token = ScriptVars.getGlobalVar("juiceshop.token");
106+
107+
// This is the verification URL
108+
var requestUri = new URI(juiceshopAddr + "rest/user/whoami", false);
109+
var requestMethod = HttpRequestHeader.GET;
110+
var requestHeader = new HttpRequestHeader(requestMethod, requestUri, HttpHeader.HTTP11);
111+
// The auth token and cookie will be added by the httpsender script
112+
var msg = helper.prepareMessage();
113+
msg.setRequestHeader(requestHeader);
114+
helper.sendAndReceive(msg);
115+
116+
return msg;
117+
}
118+
119+
function getRequiredParamsNames() {
120+
return [];
121+
}
122+
123+
function getOptionalParamsNames() {
124+
return [];
125+
}
126+
127+
function getCredentialsParamsNames() {
128+
return ["username", "password"];
129+
}
130+
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
This is part of a set of scripts which allow you to authenticate to Juice Shop using Selenium.
3+
4+
These scripts will currently only run in Oracle Nashorn and not Graal.js
5+
which means you need to run ZAP using Java 11.
6+
7+
---
8+
9+
This script injects the authentication token into the verification request.
10+
Without this token the verification request would always fail.
11+
12+
It also records stats which allow us to test that the authentication is working
13+
in all cases.
14+
15+
*/
16+
17+
18+
function logger() {
19+
print('[' + this['zap.script.name'] + '] ' + arguments[0]);
20+
}
21+
22+
function isStaticUrl(url) {
23+
if (url.indexOf('.xml') !== -1) {
24+
return true;
25+
}
26+
27+
if (url.indexOf('.css') !== -1) {
28+
return true;
29+
}
30+
31+
if (url.indexOf('.gif') !== -1) {
32+
return true;
33+
}
34+
35+
if (url.indexOf('.js') !== -1) {
36+
return true;
37+
}
38+
39+
if (url.indexOf('.txt') !== -1) {
40+
return true;
41+
}
42+
43+
if (url.indexOf('.htm') !== -1) {
44+
return true;
45+
}
46+
return false;
47+
}
48+
49+
var HttpSender = Java.type('org.parosproxy.paros.network.HttpSender');
50+
var ScriptVars = Java.type('org.zaproxy.zap.extension.script.ScriptVars');
51+
var Stats = Java.type('org.zaproxy.zap.utils.Stats');
52+
53+
var juiceshopAddr = "http://localhost:3000/";
54+
55+
function sendingRequest(msg, initiator, _helper) {
56+
var headers = msg.getRequestHeader();
57+
var url = headers.getURI().toString();
58+
59+
if (!url.startsWith(juiceshopAddr)) { return true; }
60+
if (isStaticUrl(url)) { return true; }
61+
62+
var token = ScriptVars.getGlobalVar("juiceshop.token");
63+
64+
if (token) {
65+
Stats.incCounter("stats.juiceshop.globaltoken.present");
66+
} else {
67+
Stats.incCounter("stats.juiceshop.globaltoken.absent");
68+
}
69+
70+
if (initiator === HttpSender.AUTHENTICATION_INITIATOR) {
71+
if (url.startsWith(juiceshopAddr + "rest/user/whoami")) {
72+
// Need to add these for the verification request
73+
logger(url + " adding token to authentication request");
74+
msg.getRequestHeader().setHeader('Authorization', 'Bearer ' + token);
75+
msg.getRequestHeader().setHeader('Cookie', 'token=' + token);
76+
}
77+
} else if (initiator === HttpSender.AJAX_SPIDER_INITIATOR) {
78+
var header = msg.getRequestHeader();
79+
var auth = header.getHeader("Authorization");
80+
var cookie = header.getHeader("Cookie");
81+
// Record stats to give us some confidence that the AJAX spider is authenticated
82+
if (auth) {
83+
Stats.incCounter("stats.juiceshop.authtoken.present");
84+
} else {
85+
Stats.incCounter("stats.juiceshop.authtoken.absent");
86+
}
87+
if (cookie && cookie.indexOf("token=") > -1) {
88+
Stats.incCounter("stats.juiceshop.cookie.present");
89+
} else {
90+
Stats.incCounter("stats.juiceshop.cookie.absent");
91+
}
92+
}
93+
94+
return true;
95+
}
96+
97+
function responseReceived(_msg, _initiator, _helper) {
98+
return true;
99+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
This is part of a set of scripts which allow you to authenticate to Juice Shop using Selenium.
3+
4+
These scripts will currently only run in Oracle Nashorn and not Graal.js
5+
which means you need to run ZAP using Java 11.
6+
7+
---
8+
9+
This script stops the proxy used for authentication and removes all of the script variables.
10+
It is not needed for automation but can be useful for manual testing.
11+
*/
12+
13+
function logger() {
14+
print('[' + this['zap.script.name'] + '] ' + arguments[0]);
15+
}
16+
17+
var ScriptVars = Java.type("org.zaproxy.zap.extension.script.ScriptVars");
18+
19+
var proxy = ScriptVars.getGlobalCustomVar("auth-proxy");
20+
21+
if (proxy) {
22+
logger("Found auth proxy - stopping");
23+
proxy.stop();
24+
ScriptVars.setGlobalCustomVar("auth-proxy", null);
25+
}
26+
27+
var token = ScriptVars.getGlobalVar("juiceshop.token");
28+
if (token) {
29+
logger("Found token - removing");
30+
ScriptVars.setGlobalVar("juiceshop.token", null);
31+
32+
}
33+
34+
// Reset the state for all users
35+
var extUser = control.getSingleton().
36+
getExtensionLoader().getExtension(
37+
org.zaproxy.zap.extension.users.ExtensionUserManagement.class);
38+
var session = model.getSession();
39+
var contexts = session.getContexts();
40+
for (i in contexts) {
41+
var users = extUser.getContextUserAuthManager(contexts[i].getId()).getUsers();
42+
for (j in users) {
43+
logger("Resetting user " + users[j]);
44+
users[j].getAuthenticationState().setLastPollResult(false);
45+
}
46+
}
47+
48+
logger("Reset complete.");
49+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
This is part of a set of scripts which allow you to authenticate to Juice Shop using Selenium.
3+
4+
These scripts will currently only run in Oracle Nashorn and not Graal.js
5+
which means you need to run ZAP using Java 11.
6+
7+
---
8+
9+
This script logs in to Juice Shop when a browser is launched.
10+
This will happen when:
11+
* The authentication script runs
12+
* The AJAX Spider launches a browser
13+
* The DOM XSS scan rule runs
14+
15+
This is needed as Juice Shop maintains client side state about authentication -
16+
if the UI does not know it is authenticated then it will not be able to
17+
perform any authenticated actions.
18+
19+
*/
20+
21+
function logger() {
22+
print('[' + this['zap.script.name'] + '] ' + arguments[0]);
23+
}
24+
25+
var ArrayList = Java.type("java.util.ArrayList");
26+
var By = Java.type('org.openqa.selenium.By');
27+
var ScriptVars = Java.type("org.zaproxy.zap.extension.script.ScriptVars");
28+
var System = Java.type("java.lang.System");
29+
30+
var juiceshopAddr = "http://localhost:3000/";
31+
32+
function browserLaunched(utils) {
33+
var wd = utils.getWebDriver();
34+
wd.get(juiceshopAddr);
35+
// This request will get the login page without the pesky popup
36+
wd.get(juiceshopAddr + "#/login");
37+
// These are standard selenium methods for filling out fields
38+
// You will need to change them to support different apps
39+
wd.findElement(By.id("email")).sendKeys(System.getenv("JS_USER"));
40+
wd.findElement(By.id("password")).sendKeys(System.getenv("JS_PWD"));
41+
wd.findElement(By.id("loginButton")).click();
42+
}
43+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
This is part of a set of scripts which allow you to authenticate to Juice Shop using Selenium.
3+
4+
These scripts will currently only run in Oracle Nashorn and not Graal.js
5+
which means you need to run ZAP using Java 11.
6+
7+
---
8+
9+
This script injects the authentication token into authenticated requests
10+
from ZAP.
11+
12+
It is used by the traditional spider and the active scan rules
13+
(apart from the DOM XSS one which uses a browser).
14+
It is not used by the AJAX Spider as that need the client side state to be set.
15+
16+
*/
17+
18+
function logger() {
19+
print('[' + this['zap.script.name'] + '] ' + arguments[0]);
20+
}
21+
22+
var COOKIE_TYPE = org.parosproxy.paros.network.HtmlParameter.Type.cookie;
23+
var HtmlParameter = Java.type('org.parosproxy.paros.network.HtmlParameter');
24+
var ScriptVars = Java.type('org.zaproxy.zap.extension.script.ScriptVars');
25+
var Stats = Java.type('org.zaproxy.zap.utils.Stats');
26+
27+
function extractWebSession(_sessionWrapper) {
28+
// Handled in the auth script
29+
}
30+
31+
function clearWebSessionIdentifiers(sessionWrapper) {
32+
var headers = sessionWrapper.getHttpMessage().getRequestHeader();
33+
headers.setHeader("Authorization", null);
34+
ScriptVars.setGlobalVar("juiceshop.token", null);
35+
}
36+
37+
function processMessageToMatchSession(sessionWrapper) {
38+
var token = ScriptVars.getGlobalVar("juiceshop.token");
39+
if (token === null) {
40+
logger('no token');
41+
return;
42+
}
43+
var cookie = new HtmlParameter(COOKIE_TYPE, "token", token);
44+
// add the saved authentication token as an Authentication header and a cookie
45+
var msg = sessionWrapper.getHttpMessage();
46+
msg.getRequestHeader().setHeader("Authorization", "Bearer " + token);
47+
var cookies = msg.getRequestHeader().getCookieParams();
48+
cookies.add(cookie);
49+
msg.getRequestHeader().setCookieParams(cookies);
50+
Stats.incCounter("stats.juiceshop.tokens.added");
51+
}
52+
53+
function getRequiredParamsNames() {
54+
return [];
55+
}
56+
57+
function getOptionalParamsNames() {
58+
return [];
59+
}

0 commit comments

Comments
 (0)