Skip to content

Commit a5c4259

Browse files
authored
feat: add Ory Kratos authentication scripts (#447)
- Add script to authenticate via Ory Kratos native flow - Add script to authenticate via Ory Kratos browser flow Signed-off-by: Edouard Maleix <ed@getlarge.eu>
1 parent 4e19cd7 commit a5c4259

File tree

3 files changed

+316
-0
lines changed

3 files changed

+316
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010
- passive/JavaDisclosure.js - Passive scan for Java error messages leaks
1111
- httpsender/RsaEncryptPayloadForZap.py - A script that encrypts requests using RSA
1212
- selenium/FillOTPInMFA.js - A script that fills the OTP in MFA
13+
- authentication/KratosApiAuthentication.js - A script to authenticate with Kratos using the API flow
14+
- authentication/KratosBrowserAuthentication.js - A script to authenticate with Kratos using the browser flow
1315

1416
### Changed
1517
- Use Prettier to format all JavaScript scripts.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* This script is used to authenticate a user using Ory Kratos self-service API for clients without browsers.
3+
*
4+
* Authentication Verification can be configured with the following rule:
5+
* - Verification Strategy: Poll the Specified URL
6+
* - Regex Pattern used to identify Logged In message: active.*
7+
* - URL to poll: <Kratos Base URL>/sessions/whoami
8+
*
9+
* ZAP must be configured to use HTTP header-based session management with the value: {%json:session_token%}
10+
*
11+
* @author Edouard Maleix <ed@getlarge.eu>
12+
* @see https://www.ory.sh/docs/kratos/self-service/flows/user-login#login-for-api-clients-and-clients-without-browsers
13+
*/
14+
15+
const HttpRequestHeader = Java.type(
16+
"org.parosproxy.paros.network.HttpRequestHeader"
17+
);
18+
const HttpHeader = Java.type("org.parosproxy.paros.network.HttpHeader");
19+
const URI = Java.type("org.apache.commons.httpclient.URI");
20+
const AuthenticationHelper = Java.type(
21+
"org.zaproxy.zap.authentication.AuthenticationHelper"
22+
);
23+
24+
/**
25+
* @typedef {Object} AuthHelper
26+
* @property {function(): Object} prepareMessage - Prepares an HTTP message.
27+
* @property {function(Object, boolean=): void} sendAndReceive - Sends the HTTP message and receives the response.
28+
*/
29+
30+
/**
31+
* @typedef {Object} ParamsValues
32+
* @property {function(string): string} get - Gets the value of a parameter.
33+
* @property {function(string): void} set - Sets the value of a parameter.
34+
*/
35+
36+
/**
37+
* @typedef {Object} Credentials
38+
* @property {function(string): string} getParam - Gets the value of a parameter. The param names are the ones returned by the getCredentialsParamsNames() below
39+
*/
40+
41+
/**
42+
* @param {AuthHelper} helper - The authentication helper object provided by ZAP.
43+
* @param {ParamsValues} paramsValues - The map of parameter values configured in the Session Properties - Authentication panel.
44+
* @param {Credentials} credentials - an object containing the credentials values, as configured in the Session Properties - Users panel.
45+
* @returns {Object} The HTTP message used to perform the authentication.
46+
*/
47+
function authenticate(helper, paramsValues, credentials) {
48+
print("Authenticating via Ory Kratos...");
49+
50+
// Step 1: Initialize the login flow
51+
const kratosBaseUri = paramsValues.get("Kratos Base URL");
52+
const initLoginUri = new URI(kratosBaseUri + "/self-service/login/api", true);
53+
const initLoginMsg = helper.prepareMessage();
54+
initLoginMsg.setRequestHeader(
55+
new HttpRequestHeader(
56+
HttpRequestHeader.GET,
57+
initLoginUri,
58+
HttpHeader.HTTP11
59+
)
60+
);
61+
print("Sending GET request to " + initLoginUri);
62+
helper.sendAndReceive(initLoginMsg);
63+
print(
64+
"Received response status code: " +
65+
initLoginMsg.getResponseHeader().getStatusCode()
66+
);
67+
AuthenticationHelper.addAuthMessageToHistory(initLoginMsg);
68+
69+
// Step 2: Submit login credentials
70+
const actionUrl = JSON.parse(initLoginMsg.getResponseBody().toString()).ui
71+
.action;
72+
const loginUri = new URI(actionUrl, false);
73+
const loginMsg = helper.prepareMessage();
74+
const requestBody = JSON.stringify({
75+
method: "password",
76+
identifier: credentials.getParam("username"),
77+
password: credentials.getParam("password"),
78+
});
79+
loginMsg.setRequestBody(requestBody);
80+
81+
const requestHeader = new HttpRequestHeader(
82+
HttpRequestHeader.POST,
83+
loginUri,
84+
HttpHeader.HTTP11
85+
);
86+
loginMsg.setRequestHeader(requestHeader);
87+
88+
// Build the POST request header
89+
loginMsg
90+
.getRequestHeader()
91+
.setHeader(HttpHeader.CONTENT_TYPE, "application/json");
92+
loginMsg
93+
.getRequestHeader()
94+
.setContentLength(loginMsg.getRequestBody().length());
95+
96+
print("Sending POST request to " + loginUri);
97+
helper.sendAndReceive(loginMsg, false);
98+
print(
99+
"Received response status code: " +
100+
loginMsg.getResponseHeader().getStatusCode()
101+
);
102+
AuthenticationHelper.addAuthMessageToHistory(loginMsg);
103+
104+
return loginMsg;
105+
}
106+
107+
/**
108+
* Returns the required parameter names.
109+
*
110+
* @returns {Array<string>} An array of required parameter names.
111+
*/
112+
function getRequiredParamsNames() {
113+
return ["Kratos Base URL"];
114+
}
115+
116+
/**
117+
* Returns the optional parameter names.
118+
*
119+
* @returns {Array<string>} An array of optional parameter names.
120+
*/
121+
function getOptionalParamsNames() {
122+
return [];
123+
}
124+
125+
/**
126+
* Returns the credentials parameter names.
127+
*
128+
* @returns {Array<string>} An array of credentials parameter names.
129+
*/
130+
function getCredentialsParamsNames() {
131+
return ["username", "password"];
132+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* @description This script is used to authenticate a user using Ory Kratos self-service API for browser.
3+
*
4+
* Authentication Verification can be configured with the following rule:
5+
* - Verification Strategy: Poll the Specified URL
6+
* - Regex Pattern used to identify Logged In message: active.*
7+
* - URL to poll: <Kratos Base URL>/sessions/whoami
8+
*
9+
* ZAP must be configured to use cookie-based session management.
10+
*
11+
* @author Edouard Maleix <ed@getlarge.eu>
12+
* @see https://www.ory.sh/docs/kratos/self-service/flows/user-login#login-for-server-side-browser-clients
13+
*/
14+
15+
const HttpRequestHeader = Java.type(
16+
"org.parosproxy.paros.network.HttpRequestHeader"
17+
);
18+
const HttpHeader = Java.type("org.parosproxy.paros.network.HttpHeader");
19+
const URI = Java.type("org.apache.commons.httpclient.URI");
20+
const AuthenticationHelper = Java.type(
21+
"org.zaproxy.zap.authentication.AuthenticationHelper"
22+
);
23+
const Source = Java.type("net.htmlparser.jericho.Source");
24+
25+
/**
26+
* @typedef {Object} AuthHelper
27+
* @property {function(): Object} prepareMessage - Prepares an HTTP message.
28+
* @property {function(Object, boolean=): void} sendAndReceive - Sends the HTTP message and receives the response.
29+
*/
30+
31+
/**
32+
* @typedef {Object} ParamsValues
33+
* @property {function(string): string} get - Gets the value of a parameter.
34+
* @property {function(string): void} set - Sets the value of a parameter.
35+
*/
36+
37+
/**
38+
* @typedef {Object} Credentials
39+
* @property {function(string): string} getParam - Gets the value of a parameter. The param names are the ones returned by the getCredentialsParamsNames() below
40+
*/
41+
42+
/**
43+
* @param {AuthHelper} helper - The authentication helper object provided by ZAP.
44+
* @param {ParamsValues} paramsValues - The map of parameter values configured in the Session Properties - Authentication panel.
45+
* @param {Credentials} credentials - an object containing the credentials values, as configured in the Session Properties - Users panel.
46+
* @returns {Object} The HTTP message used to perform the authentication.
47+
*/
48+
function authenticate(helper, paramsValues, credentials) {
49+
print("Authenticating via Ory Kratos...");
50+
51+
// Step 1: Initialize the login flow
52+
const kratosBaseUri = paramsValues.get("Kratos Base URL");
53+
const initLoginUri = new URI(
54+
kratosBaseUri + "/self-service/login/browser",
55+
true
56+
);
57+
const initLoginMsg = helper.prepareMessage();
58+
initLoginMsg.setRequestHeader(
59+
new HttpRequestHeader(
60+
HttpRequestHeader.GET,
61+
initLoginUri,
62+
HttpHeader.HTTP11
63+
)
64+
);
65+
print("Sending GET request to " + initLoginUri);
66+
helper.sendAndReceive(initLoginMsg, true);
67+
print(
68+
"Received response status code: " +
69+
initLoginMsg.getResponseHeader().getStatusCode()
70+
);
71+
AuthenticationHelper.addAuthMessageToHistory(initLoginMsg);
72+
73+
// Step 2: Submit login credentials
74+
const actionUrl = getActionurl(initLoginMsg);
75+
const loginUri = new URI(actionUrl, false);
76+
const loginMsg = helper.prepareMessage();
77+
const csrf_token = getCsrfToken(initLoginMsg);
78+
const requestBody =
79+
"identifier=" +
80+
encodeURIComponent(credentials.getParam("username")) +
81+
"&password=" +
82+
encodeURIComponent(credentials.getParam("password")) +
83+
"&method=password" +
84+
"&csrf_token=" +
85+
encodeURIComponent(csrf_token);
86+
loginMsg.setRequestBody(requestBody);
87+
const requestHeader = new HttpRequestHeader(
88+
HttpRequestHeader.POST,
89+
loginUri,
90+
HttpHeader.HTTP11
91+
);
92+
loginMsg.setRequestHeader(requestHeader);
93+
94+
loginMsg
95+
.getRequestHeader()
96+
.setHeader(HttpHeader.CONTENT_TYPE, "application/x-www-form-urlencoded");
97+
loginMsg
98+
.getRequestHeader()
99+
.setContentLength(loginMsg.getRequestBody().length());
100+
101+
print("Sending POST request to " + loginUri);
102+
//! disable redirect to get the cookies from Kratos
103+
helper.sendAndReceive(loginMsg, false);
104+
print(
105+
"Received response status code: " +
106+
loginMsg.getResponseHeader().getStatusCode()
107+
);
108+
AuthenticationHelper.addAuthMessageToHistory(loginMsg);
109+
110+
return loginMsg;
111+
}
112+
113+
/**
114+
*
115+
* @param {*} request
116+
* @returns
117+
*/
118+
function getActionurl(request) {
119+
let iterator;
120+
let element;
121+
let actionUrl;
122+
const pageSource =
123+
request.getResponseHeader().toString() +
124+
request.getResponseBody().toString();
125+
const src = new Source(pageSource);
126+
const elements = src.getAllElements("form");
127+
128+
for (iterator = elements.iterator(); iterator.hasNext(); ) {
129+
element = iterator.next();
130+
actionUrl = element.getAttributeValue("action");
131+
break;
132+
}
133+
134+
return actionUrl;
135+
}
136+
137+
function getCsrfToken(request) {
138+
let iterator;
139+
let element;
140+
let loginToken;
141+
const pageSource =
142+
request.getResponseHeader().toString() +
143+
request.getResponseBody().toString();
144+
const src = new Source(pageSource);
145+
const elements = src.getAllElements("input");
146+
147+
for (iterator = elements.iterator(); iterator.hasNext(); ) {
148+
element = iterator.next();
149+
if (element.getAttributeValue("name") == "csrf_token") {
150+
loginToken = element.getAttributeValue("value");
151+
break;
152+
}
153+
}
154+
155+
return loginToken;
156+
}
157+
/**
158+
* Returns the required parameter names.
159+
*
160+
* @returns {Array<string>} An array of required parameter names.
161+
*/
162+
function getRequiredParamsNames() {
163+
return ["Kratos Base URL"];
164+
}
165+
166+
/**
167+
* Returns the optional parameter names.
168+
*
169+
* @returns {Array<string>} An array of optional parameter names.
170+
*/
171+
function getOptionalParamsNames() {
172+
return [];
173+
}
174+
175+
/**
176+
* Returns the credentials parameter names.
177+
*
178+
* @returns {Array<string>} An array of credentials parameter names.
179+
*/
180+
function getCredentialsParamsNames() {
181+
return ["username", "password"];
182+
}

0 commit comments

Comments
 (0)