diff --git a/addOns/ascanrulesAlpha/ascanrulesAlpha.gradle.kts b/addOns/ascanrulesAlpha/ascanrulesAlpha.gradle.kts index 2d6681571ea..416f3a1f1cc 100644 --- a/addOns/ascanrulesAlpha/ascanrulesAlpha.gradle.kts +++ b/addOns/ascanrulesAlpha/ascanrulesAlpha.gradle.kts @@ -12,17 +12,22 @@ zapAddOn { register("commonlib") { version.set(">= 1.32.0 & < 2.0.0") } + register("authhelper") { + version.set("0.26.0") + } } } } } tasks.named("compileJava") { + dependsOn(":addOns:authhelper:enhance") mustRunAfter(parent!!.childProjects.get("oast")!!.tasks.named("enhance")) } dependencies { zapAddOn("commonlib") + zapAddOn("authhelper") testImplementation(project(":testutils")) } diff --git a/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/BlankTotpActiveScanRule.java b/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/BlankTotpActiveScanRule.java new file mode 100644 index 00000000000..fa1e28300e7 --- /dev/null +++ b/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/BlankTotpActiveScanRule.java @@ -0,0 +1,124 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.zap.extension.ascanrulesAlpha; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.Constant; +import org.parosproxy.paros.core.scanner.AbstractHostPlugin; +import org.parosproxy.paros.core.scanner.Alert; +import org.parosproxy.paros.core.scanner.Category; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.authhelper.internal.AuthenticationStep; + +public class BlankTotpActiveScanRule extends AbstractHostPlugin + implements CommonActiveScanRuleInfo { + private static final String MESSAGE_PREFIX = "ascanalpha.blanktotp."; + private static final Logger LOGGER = LogManager.getLogger(BlankTotpActiveScanRule.class); + private static final Map ALERT_TAGS = new HashMap<>(); + + @Override + public int getId() { + return 40048; + } + + @Override + public String getName() { + return Constant.messages.getString(MESSAGE_PREFIX + "name"); + } + + @Override + public String getDescription() { + return Constant.messages.getString(MESSAGE_PREFIX + "desc"); + } + + @Override + public int getCategory() { + return Category.MISC; + } + + @Override + public String getSolution() { + return Constant.messages.getString(MESSAGE_PREFIX + "soln"); + } + + @Override + public String getReference() { + return "N/A"; + } + + @Override + public void scan() { + try { + + // Get target URL from request + HttpMessage msg = getBaseMsg(); + TotpScanContext context = TotpScanContextHelper.resolve(msg); + if (context == null) { + return; + } + + List mutableAuthSteps = new ArrayList<>(context.authSteps); + + AuthenticationStep testStep = new AuthenticationStep(); + testStep.setType(AuthenticationStep.Type.CUSTOM_FIELD); + testStep.setXpath(context.totpStep.getXpath()); + testStep.setCssSelector(context.totpStep.getCssSelector()); + testStep.setValue(""); + + for (int i = 0; i < mutableAuthSteps.size(); i++) { + AuthenticationStep step = mutableAuthSteps.get(i); + if (step.getType() == AuthenticationStep.Type.TOTP_FIELD) { + mutableAuthSteps.set(i, testStep); // Replace the TOTP step with the test step + break; + } + } + context.browserAuthMethod.setAuthenticationSteps(mutableAuthSteps); + context.browserAuthMethod.authenticate( + context.sessionManagementMethod, context.credentials, context.user); + boolean webSessionBlankCode = context.browserAuthMethod.wasAuthTestSucessful(); + + if (webSessionBlankCode) { + buildAlert( + "Blank Passcode Vulnerability", + "The application allows authentication with a blank or empty passcode, which poses a significant security risk. Attackers can exploit this vulnerability to gain unauthorized access without providing valid credentials.", + "Enforce strict password policies that require non-empty, strong passcodes. Implement validation checks to prevent blank passcodes during authentication", + msg) + .raise(); + } + } catch (Exception e) { + LOGGER.error("Error in TOTP Page Scan Rule: {}", e.getMessage(), e); + } + } + + private AlertBuilder buildAlert( + String name, String description, String solution, HttpMessage msg) { + return newAlert() + .setConfidence(Alert.CONFIDENCE_HIGH) + .setName(name) + .setDescription(description) + .setSolution(solution) + .setMessage(msg); + } +} diff --git a/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/CaptchaTotpActiveScanRule.java b/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/CaptchaTotpActiveScanRule.java new file mode 100644 index 00000000000..8785b43dc6d --- /dev/null +++ b/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/CaptchaTotpActiveScanRule.java @@ -0,0 +1,207 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.zap.extension.ascanrulesAlpha; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.Constant; +import org.parosproxy.paros.core.scanner.AbstractHostPlugin; +import org.parosproxy.paros.core.scanner.Alert; +import org.parosproxy.paros.core.scanner.Category; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.authhelper.BrowserBasedAuthenticationMethodType.BrowserBasedAuthenticationMethod; +import org.zaproxy.addon.authhelper.internal.AuthenticationStep; +import org.zaproxy.zap.authentication.UsernamePasswordAuthenticationCredentials; +import org.zaproxy.zap.session.SessionManagementMethod; +import org.zaproxy.zap.session.WebSession; +import org.zaproxy.zap.users.User; + +public class CaptchaTotpActiveScanRule extends AbstractHostPlugin + implements CommonActiveScanRuleInfo { + private static final String MESSAGE_PREFIX = "ascanalpha.captchatotp."; + private static final Logger LOGGER = LogManager.getLogger(CaptchaTotpActiveScanRule.class); + private static final Map ALERT_TAGS = new HashMap<>(); + + @Override + public int getId() { + return 40051; + } + + @Override + public String getName() { + return Constant.messages.getString(MESSAGE_PREFIX + "name"); + } + + @Override + public String getDescription() { + return Constant.messages.getString(MESSAGE_PREFIX + "desc"); + } + + @Override + public int getCategory() { + return Category.INFO_GATHER; + } + + @Override + public String getSolution() { + return Constant.messages.getString(MESSAGE_PREFIX + "soln"); + } + + @Override + public String getReference() { + return "N/A"; + } + + @Override + public void scan() { + try { + + // Get target URL from request + HttpMessage msg = getBaseMsg(); + TotpScanContext context = TotpScanContextHelper.resolve(msg); + if (context == null) { + return; + } + + // Check if lockout or captcha mechanism is detected + boolean captchaDetected = false; + boolean lockoutDetected = false; + + // Run 10 incorrect authentications and store the responses + // Check responses for any changes or any common captcha technology + + List authSteps = new ArrayList<>(context.authSteps); + AuthenticationStep totpStep = context.totpStep; + int totpIndex = authSteps.indexOf(totpStep); + List subset = + new ArrayList<>(authSteps.subList(totpIndex + 1, authSteps.size())); + for (int i = 0; i < 9; i++) { + for (AuthenticationStep step : subset) { + authSteps.add(step); + } + } + WebSession test = + testAuthenticatSession( + context.totpStep, + "111111", + authSteps, + context.browserAuthMethod, + context.sessionManagementMethod, + context.credentials, + context.user); + + List messages = context.browserAuthMethod.getRecordedHttpMessages(); + + // Check for key captcha words in the responses + String[] captchaKeywords = { + "captcha", + "g-recaptcha", + "hcaptcha", + "data-sitekey", + "verify you are human", + "challenge-response", + "bot detection", + "recaptcha/api.js", + "hcaptcha.com/1/api.js", + "please solve the captcha", + "captcha verification", + "input type=\"hidden\" name=\"g-recaptcha-response\"" + }; + for (String keyword : captchaKeywords) { + for (HttpMessage response : messages) { + String contentType = response.getResponseHeader().getHeader("Content-Type"); + if (contentType == null || !contentType.toLowerCase().contains("text")) { + continue; + } + if (response.getResponseBody().toString().toLowerCase().contains(keyword)) { + captchaDetected = true; + return; + } + } + } + + // Check for lockout words in the responses + String[] lockoutKeywords = { + "lockout", + "locked", + "too many failed attempts", + "too many login attempts", + "reset your password", + "account disabled", + "unlock" + }; + for (String keyword : lockoutKeywords) { + for (HttpMessage response : messages) { + String contentType = response.getResponseHeader().getHeader("Content-Type"); + if (contentType == null || !contentType.toLowerCase().contains("text")) { + continue; + } + if (response.getResponseBody().toString().toLowerCase().contains(keyword)) { + lockoutDetected = true; + return; + } else if (response.getResponseHeader().getStatusCode() == 403) { + lockoutDetected = true; + return; + } + } + } + + if (!captchaDetected && !lockoutDetected) { + buildAlert( + "No Lockout or Captcha Mechanism Detected", + "\"The application does not enforce CAPTCHA or account lockout mechanisms, making it vulnerable to brute-force attacks.", + "Implement CAPTCHA verification and/or account lockout policies after multiple failed login attempts.", + msg) + .raise(); + } + } catch (Exception e) { + LOGGER.error("Error in TOTP Page Scan Rule: {}", e.getMessage(), e); + } + } + + private WebSession testAuthenticatSession( + AuthenticationStep totpStep, + String newTotpValue, + List authSteps, + BrowserBasedAuthenticationMethod browserAuthMethod, + SessionManagementMethod sessionManagementMethod, + UsernamePasswordAuthenticationCredentials credentials, + User user) { + if (totpStep.getType() == AuthenticationStep.Type.TOTP_FIELD) + totpStep.setUserProvidedTotp(newTotpValue); + else totpStep.setValue(newTotpValue); + browserAuthMethod.setAuthenticationSteps(authSteps); + return browserAuthMethod.authenticate(sessionManagementMethod, credentials, user); + } + + private AlertBuilder buildAlert( + String name, String description, String solution, HttpMessage msg) { + return newAlert() + .setConfidence(Alert.CONFIDENCE_MEDIUM) + .setName(name) + .setDescription(description) + .setSolution(solution) + .setMessage(msg); + } +} diff --git a/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/ReplayTotpActiveScanRule.java b/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/ReplayTotpActiveScanRule.java new file mode 100644 index 00000000000..903c98bd66f --- /dev/null +++ b/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/ReplayTotpActiveScanRule.java @@ -0,0 +1,141 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.zap.extension.ascanrulesAlpha; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.Constant; +import org.parosproxy.paros.core.scanner.AbstractHostPlugin; +import org.parosproxy.paros.core.scanner.Alert; +import org.parosproxy.paros.core.scanner.Category; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.authhelper.internal.AuthenticationStep; +import org.zaproxy.zap.session.WebSession; + +public class ReplayTotpActiveScanRule extends AbstractHostPlugin + implements CommonActiveScanRuleInfo { + private static final String MESSAGE_PREFIX = "ascanalpha.replaytotp."; + private static final Logger LOGGER = LogManager.getLogger(ReplayTotpActiveScanRule.class); + private static final Map ALERT_TAGS = new HashMap<>(); + + @Override + public int getId() { + return 40049; + } + + @Override + public String getName() { + return Constant.messages.getString(MESSAGE_PREFIX + "name"); + } + + @Override + public String getDescription() { + return Constant.messages.getString(MESSAGE_PREFIX + "desc"); + } + + @Override + public int getCategory() { + return Category.INFO_GATHER; + } + + @Override + public String getSolution() { + return Constant.messages.getString(MESSAGE_PREFIX + "soln"); + } + + @Override + public String getReference() { + return "N/A"; + } + + @Override + public void scan() { + try { + + // Get target URL from request + HttpMessage msg = getBaseMsg(); + + TotpScanContext context = TotpScanContextHelper.resolve(msg); + if (context == null) { + return; + } + + // If user provided TOTP secret, generate valid TOTP code and then check if it works + // multiple times + if (context.totpStep.getType() == AuthenticationStep.Type.TOTP_FIELD + && context.totpStep.getTotpSecret() != null + && !context.totpStep.getTotpSecret().isEmpty()) { + String validTotpCode = context.totpStep.getTotpCode(context.credentials).toString(); + + WebSession webSession = + context.browserAuthMethod.authenticate( + context.sessionManagementMethod, context.credentials, context.user); + if (!context.browserAuthMethod.wasAuthTestSucessful()) { + return; + } + + List testSteps = new ArrayList<>(); + for (AuthenticationStep step : context.authSteps) { + if (step.getType() == AuthenticationStep.Type.TOTP_FIELD) { + AuthenticationStep cloned = new AuthenticationStep(step); + cloned.setUserProvidedTotp(validTotpCode); + testSteps.add(cloned); + } else { + testSteps.add(step); + } + } + + context.browserAuthMethod.setAuthenticationSteps(testSteps); + context.browserAuthMethod.authenticate( + context.sessionManagementMethod, context.credentials, context.user); + boolean webSessionRedo = context.browserAuthMethod.wasAuthTestSucessful(); + // Check for passcode reuse vulnerability + if (webSessionRedo) { + buildAlert( + "TOTP Replay Attack Vulnerability", + "The application is vulnerable to replay attacks, allowing attackers to reuse previously intercepted TOTP codes to authenticate.", + "Ensure that TOTP codes are validated only once per session and are invalidated after use.", + msg) + .raise(); + } + + } else { + return; + } + + } catch (Exception e) { + LOGGER.error("Error in TOTP Page Scan Rule: {}", e.getMessage(), e); + } + } + + private AlertBuilder buildAlert( + String name, String description, String solution, HttpMessage msg) { + return newAlert() + .setConfidence(Alert.CONFIDENCE_HIGH) + .setName(name) + .setDescription(description) + .setSolution(solution) + .setMessage(msg); + } +} diff --git a/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/TotpActiveScanRule.java b/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/TotpActiveScanRule.java new file mode 100644 index 00000000000..b6969dd3efa --- /dev/null +++ b/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/TotpActiveScanRule.java @@ -0,0 +1,139 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.zap.extension.ascanrulesAlpha; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.Constant; +import org.parosproxy.paros.core.scanner.AbstractHostPlugin; +import org.parosproxy.paros.core.scanner.Alert; +import org.parosproxy.paros.core.scanner.Category; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.authhelper.internal.AuthenticationStep; + +public class TotpActiveScanRule extends AbstractHostPlugin implements CommonActiveScanRuleInfo { + private static final String MESSAGE_PREFIX = "ascanalpha.commtotp."; + private static final Logger LOGGER = LogManager.getLogger(TotpActiveScanRule.class); + private static final Map ALERT_TAGS = new HashMap<>(); + + @Override + public int getId() { + return 40050; + } + + @Override + public String getName() { + return Constant.messages.getString(MESSAGE_PREFIX + "name"); + } + + @Override + public String getDescription() { + return Constant.messages.getString(MESSAGE_PREFIX + "desc"); + } + + @Override + public int getCategory() { + return Category.INFO_GATHER; + } + + @Override + public String getSolution() { + return "N/A"; + } + + @Override + public String getReference() { + return "N/A"; + } + + @Override + public void scan() { + try { + + // Get target URL from request + HttpMessage msg = getBaseMsg(); + TotpScanContext context = TotpScanContextHelper.resolve(msg); + if (context == null) { + return; + } + + // Checks if a valid username/password combination gives access with any passcode + // meeting the format + // Uses known static backup passcodes to check if any of them work + List backupPasscodes = + List.of( + "000000", + "0000000", + "00000000", + "123456", + "1234567", + "12345678", + "888888", + "8888888", + "88888888"); + // Test passcodes (check format- RFC-6238 (6,7,8) + + for (String code : backupPasscodes) { + List testSteps = new ArrayList<>(); + for (AuthenticationStep step : context.authSteps) { + if (step.getType() == AuthenticationStep.Type.TOTP_FIELD) { + AuthenticationStep clone = new AuthenticationStep(step); + clone.setUserProvidedTotp(code); + testSteps.add(clone); + } else { + testSteps.add(step); + } + } + + context.browserAuthMethod.setAuthenticationSteps(testSteps); + context.browserAuthMethod.authenticate( + context.sessionManagementMethod, context.credentials, context.user); + boolean webSessionNew = context.browserAuthMethod.wasAuthTestSucessful(); + + if (webSessionNew) { + buildAlert( + "Passcode Authentication Bypass", + "The application allows authentication using passcodes that meet the expected format but are weak or known values. This poses a security risk as attackers could exploit predictable static backup passcodes to gain unauthorized access.", + "", + msg) + .raise(); + return; + } + } + + } catch (Exception e) { + LOGGER.error("Error in TOTP Page Scan Rule: {}", e.getMessage(), e); + } + } + + private AlertBuilder buildAlert( + String name, String description, String solution, HttpMessage msg) { + return newAlert() + .setConfidence(Alert.CONFIDENCE_HIGH) + .setName(name) + .setDescription(description) + .setSolution(solution) + .setMessage(msg); + } +} diff --git a/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/TotpScanContext.java b/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/TotpScanContext.java new file mode 100644 index 00000000000..f8b7f856f3d --- /dev/null +++ b/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/TotpScanContext.java @@ -0,0 +1,56 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.zap.extension.ascanrulesAlpha; + +import java.util.List; +import org.zaproxy.addon.authhelper.BrowserBasedAuthenticationMethodType.BrowserBasedAuthenticationMethod; +import org.zaproxy.addon.authhelper.internal.AuthenticationStep; +import org.zaproxy.zap.authentication.UsernamePasswordAuthenticationCredentials; +import org.zaproxy.zap.model.Context; +import org.zaproxy.zap.session.SessionManagementMethod; +import org.zaproxy.zap.users.User; + +// Bundles all the context information needed to perform a TOTP scan +public class TotpScanContext { + public final Context context; + public final BrowserBasedAuthenticationMethod browserAuthMethod; + public final List authSteps; + public final AuthenticationStep totpStep; + public final UsernamePasswordAuthenticationCredentials credentials; + public final SessionManagementMethod sessionManagementMethod; + public final User user; + + public TotpScanContext( + Context context, + BrowserBasedAuthenticationMethod browserAuthMethod, + List authSteps, + AuthenticationStep totpStep, + UsernamePasswordAuthenticationCredentials credentials, + SessionManagementMethod sessionManagementMethod, + User user) { + this.context = context; + this.browserAuthMethod = browserAuthMethod; + this.authSteps = authSteps; + this.totpStep = totpStep; + this.credentials = credentials; + this.sessionManagementMethod = sessionManagementMethod; + this.user = user; + } +} diff --git a/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/TotpScanContextHelper.java b/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/TotpScanContextHelper.java new file mode 100644 index 00000000000..f3a91fa358b --- /dev/null +++ b/addOns/ascanrulesAlpha/src/main/java/org/zaproxy/zap/extension/ascanrulesAlpha/TotpScanContextHelper.java @@ -0,0 +1,113 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.zap.extension.ascanrulesAlpha; + +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.control.Control; +import org.parosproxy.paros.model.Model; +import org.parosproxy.paros.model.Session; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.authhelper.BrowserBasedAuthenticationMethodType.BrowserBasedAuthenticationMethod; +import org.zaproxy.addon.authhelper.internal.AuthenticationStep; +import org.zaproxy.zap.authentication.AuthenticationMethod; +import org.zaproxy.zap.authentication.UsernamePasswordAuthenticationCredentials; +import org.zaproxy.zap.extension.users.ExtensionUserManagement; +import org.zaproxy.zap.model.Context; +import org.zaproxy.zap.session.SessionManagementMethod; +import org.zaproxy.zap.users.User; + +public class TotpScanContextHelper { + private static final Logger LOGGER = LogManager.getLogger(TotpScanContextHelper.class); + + public static TotpScanContext resolve(HttpMessage msg) { + try { + ExtensionUserManagement usersExtension = + Control.getSingleton() + .getExtensionLoader() + .getExtension(ExtensionUserManagement.class); + if (usersExtension == null) { + return null; + } + + String targetUrl = msg.getRequestHeader().getURI().toString(); + Session session = Model.getSingleton().getSession(); + Context activeContext = null; + for (Context context : session.getContexts()) { + if (context.isInContext(targetUrl)) { + activeContext = context; + break; + } + } + if (activeContext == null) { + return null; + } + + AuthenticationMethod authMethod = activeContext.getAuthenticationMethod(); + if (!(authMethod instanceof BrowserBasedAuthenticationMethod)) { + return null; + } + + BrowserBasedAuthenticationMethod browserAuthMethod = + (BrowserBasedAuthenticationMethod) authMethod; + List authSteps = browserAuthMethod.getAuthenticationSteps(); + + AuthenticationStep totpStep = null; + for (AuthenticationStep step : authSteps) { + if (step.getType() == AuthenticationStep.Type.TOTP_FIELD + || (step.getType() == AuthenticationStep.Type.CUSTOM_FIELD + && step.getDescription().toLowerCase().contains("totp"))) { + totpStep = step; + break; + } + } + if (totpStep == null) { + return null; + } + + List users = + usersExtension.getContextUserAuthManager(activeContext.getId()).getUsers(); + if (users == null || users.isEmpty()) { + return null; + } + + User user = users.get(0); + UsernamePasswordAuthenticationCredentials credentials = + (UsernamePasswordAuthenticationCredentials) user.getAuthenticationCredentials(); + + SessionManagementMethod sessionManagementMethod = + activeContext.getSessionManagementMethod(); + + return new TotpScanContext( + activeContext, + browserAuthMethod, + authSteps, + totpStep, + credentials, + sessionManagementMethod, + user); + + } catch (Exception e) { + LOGGER.error("Error resolving TOTP scan context: {}", e.getMessage(), e); + return null; + } + } +} diff --git a/addOns/ascanrulesAlpha/src/main/resources/org/zaproxy/zap/extension/ascanrulesAlpha/resources/Messages.properties b/addOns/ascanrulesAlpha/src/main/resources/org/zaproxy/zap/extension/ascanrulesAlpha/resources/Messages.properties index 39cf142e419..a1cd3d2f646 100644 --- a/addOns/ascanrulesAlpha/src/main/resources/org/zaproxy/zap/extension/ascanrulesAlpha/resources/Messages.properties +++ b/addOns/ascanrulesAlpha/src/main/resources/org/zaproxy/zap/extension/ascanrulesAlpha/resources/Messages.properties @@ -1,3 +1,14 @@ +ascanalpha.blanktotp.desc = A Blank TOTP vulnerability allows bypassing two-factor authentication by accepting an empty or missing TOTP value due to improper input validation. + +ascanalpha.blanktotp.name = Blank code TOTP Scan Rule +ascanalpha.blanktotp.soln = Enforce strict validation by rejecting empty, null, or improperly formatted TOTP inputs before proceeding with verification. +ascanalpha.captchatotp.desc = The lack of CAPTCHA or lockout mechanisms in TOTP input allows attackers to brute-force codes without restriction, increasing the risk of unauthorized access. + +ascanalpha.captchatotp.name = Captcha or Lockout TOTP Scan Rule +ascanalpha.captchatotp.soln = Implement rate limiting, CAPTCHA, and account lockout mechanisms after a defined number of failed TOTP attempts to prevent brute-force attacks. +ascanalpha.commtotp.desc = Application allows authentication using passcodes that meet the expected format but are weak or known values. This poses a security risk as attackers could exploit predictable static backup passcodes to gain unauthorized access. + +ascanalpha.commtotp.name = Common codes TOTP Scan Rule ascanalpha.desc = Alpha status active scan rules ascanalpha.examplefile.desc = Add more information about the vulnerability here. @@ -32,6 +43,10 @@ ascanalpha.mongodb.refs = https://arxiv.org/pdf/1506.04082.pdf\nhttps://owasp.or ascanalpha.mongodb.soln = Do not trust client side input and escape all data on the server side.\nAvoid to use the query input directly into the where and group clauses and upgrade all drivers at the latest available version. ascanalpha.name = Active Scan Rules - alpha +ascanalpha.replaytotp.desc = The Replay TOTP vulnerability allows an attacker to reuse a previously valid TOTP code within its time window to gain unauthorized access. + +ascanalpha.replaytotp.name = Replay TOTP Scan Rule +ascanalpha.replaytotp.soln = Implement replay protection by tracking and rejecting previously used TOTP codes within their valid time window. ascanalpha.webCacheDeception.desc = Web cache deception may be possible. It may be possible for unauthorised user to view sensitive data on this page. ascanalpha.webCacheDeception.name = Web Cache Deception diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/BrowserBasedAuthenticationMethodType.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/BrowserBasedAuthenticationMethodType.java index cb665cd7600..d22025b4e6d 100644 --- a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/BrowserBasedAuthenticationMethodType.java +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/BrowserBasedAuthenticationMethodType.java @@ -202,6 +202,11 @@ public class BrowserBasedAuthenticationMethod extends AuthenticationMethod { private String browserId = DEFAULT_BROWSER_ID; private int loginPageWait = DEFAULT_PAGE_WAIT; private List authenticationSteps = List.of(); + private boolean authTestSucessful = false; + + public boolean wasAuthTestSucessful() { + return authTestSucessful; + } public BrowserBasedAuthenticationMethod() {} @@ -214,6 +219,32 @@ public BrowserBasedAuthenticationMethod(BrowserBasedAuthenticationMethod method) method.getAuthenticationSteps().stream().map(AuthenticationStep::new).toList(); } + public List getRecordedHttpMessages() { + if (handler != null) { + List historyIds = handler.getHttpMessagesIds(); + List messages = new ArrayList<>(); + for (int historyId : historyIds) { + try { + HttpMessage msg = handler.getHistoryProvider().getHttpMessage(historyId); + if (msg != null) { + messages.add(msg); + } + } catch (Exception e) { + LOGGER.error( + "Failed to retrieve HttpMessage for History ID: " + historyId, e); + } + } + return messages; + } + return new ArrayList<>(); + } + + public void resetRecordedHttpMessages() { + if (handler != null) { + handler.resetHttpMessages(); + } + } + @Override public boolean isConfigured() { return loginPageUrl != null; @@ -302,6 +333,8 @@ private WebSession authenticateImpl( User user) { if (handler != null) { handler.resetAuthMsg(); + handler.resetHttpMessages(); + authTestSucessful = false; } if (this.loginPageWait > 0) { AuthUtils.setTimeToWaitMs(TimeUnit.SECONDS.toMillis(loginPageWait)); @@ -385,6 +418,7 @@ private WebSession authenticateImpl( // Let the user know it worked AuthenticationHelper.notifyOutputAuthSuccessful(authMsg); user.getAuthenticationState().setLastAuthFailure(""); + authTestSucessful = true; } else { diags.recordStep( authMsg, diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/internal/AuthenticationStep.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/internal/AuthenticationStep.java index dff0b433e76..1cc08782d5f 100644 --- a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/internal/AuthenticationStep.java +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/internal/AuthenticationStep.java @@ -141,6 +141,8 @@ public String toString() { @SuppressWarnings("deprecation") private String totpAlgorithm = HMACAlgorithm.SHA1.name(); + private String overideTotpValue = null; + private boolean enabled; private int order; @@ -223,7 +225,19 @@ public WebElement execute(WebDriver wd, UsernamePasswordAuthenticationCredential return element; } - private CharSequence getTotpCode(UsernamePasswordAuthenticationCredentials credentials) { + // Add a method to set own TOTP value + public void setUserProvidedTotp(String totp) { + this.overideTotpValue = totp; + } + + public CharSequence getTotpCode(UsernamePasswordAuthenticationCredentials credentials) { + if (!isEmpty(overideTotpValue)) { + return overideTotpValue; + } + if (!isEmpty(value)) { + return value; + } + CharSequence code = TotpSupport.getCode(credentials); if (code != null) { return code; diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/internal/ClientSideHandler.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/internal/ClientSideHandler.java index 5ba92e26229..5f650d9acb6 100644 --- a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/internal/ClientSideHandler.java +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/internal/ClientSideHandler.java @@ -32,6 +32,8 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.model.HistoryReference; +import org.parosproxy.paros.model.Model; import org.parosproxy.paros.network.HttpHeader; import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.network.HttpRequestHeader; @@ -59,6 +61,7 @@ public final class ClientSideHandler implements HttpMessageHandler { private UsernamePasswordAuthenticationCredentials authCreds; private AuthRequestDetails authReq; private int firstHrefId; + private List httpMessageHistoryIds = new ArrayList<>(); @Setter private HistoryProvider historyProvider = new HistoryProvider(); @@ -70,6 +73,18 @@ public ClientSideHandler(User user) { } } + public HistoryProvider getHistoryProvider() { + return historyProvider; + } + + public List getHttpMessagesIds() { + return new ArrayList<>(httpMessageHistoryIds); + } + + public void resetHttpMessages() { + httpMessageHistoryIds.clear(); + } + private boolean isPost(HttpMessage msg) { return HttpRequestHeader.POST.equals(msg.getRequestHeader().getMethod()); } @@ -81,7 +96,6 @@ private boolean containsMaybeEncodedString(String contents, String testStr) { @Override public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { - if (ctx.isFromClient()) { return; } @@ -90,6 +104,22 @@ public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { firstHrefId = msg.getHistoryRef().getHistoryId(); } + try { + // Explicitly write the message to ZAP's history database + HistoryReference hr = + new HistoryReference( + Model.getSingleton().getSession(), + HistoryReference.TYPE_AUTHENTICATION, // or TYPE_PROXIED + msg); + + // Store the history ID for later retrieval + httpMessageHistoryIds.add(hr.getHistoryId()); + LOGGER.debug("Recorded HTTP message ID: {}", hr.getHistoryId()); + + } catch (Exception e) { + LOGGER.warn("Failed to add message to history", e); + } + historyProvider.addAuthMessageToHistory(msg); if (!user.getContext().isIncluded(msg.getRequestHeader().getURI().toString())) { diff --git a/addOns/dev/dev.gradle.kts b/addOns/dev/dev.gradle.kts index 8ea69dd3e9f..d4bbd004205 100644 --- a/addOns/dev/dev.gradle.kts +++ b/addOns/dev/dev.gradle.kts @@ -1,5 +1,9 @@ description = "An add-on to help with development of ZAP." +repositories { + mavenCentral() +} + zapAddOn { addOnName.set("Dev Add-on") @@ -20,6 +24,9 @@ zapAddOn { register("network") { version.set(">=0.7.0") } + register("authhelper") { + version.set(">=0.26.0") // Or whichever version you need + } } } } @@ -28,9 +35,14 @@ zapAddOn { dependencies { zapAddOn("network") zapAddOn("commonlib") + zapAddOn("authhelper") compileOnly(libs.log4j.core) testImplementation(project(":testutils")) testImplementation(libs.log4j.core) } + +tasks.named("compileJava") { + dependsOn(":addOns:authhelper:enhance") +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/TestProxyServer.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/TestProxyServer.java index 3a72c3e5f42..8d8b95cf097 100644 --- a/addOns/dev/src/main/java/org/zaproxy/addon/dev/TestProxyServer.java +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/TestProxyServer.java @@ -3,7 +3,7 @@ * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * - * Copyright 2018 The ZAP Development Team + * Copyright 2025 The ZAP Development Team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,6 +47,12 @@ import org.zaproxy.addon.dev.auth.simpleJsonBearerJsCookie.SimpleJsonBearerJsCookieDir; import org.zaproxy.addon.dev.auth.simpleJsonCookie.SimpleJsonCookieDir; import org.zaproxy.addon.dev.auth.sso1.SSO1RootDir; +import org.zaproxy.addon.dev.auth.totp.simpleAuthTotpBlankCodeVuln.OpenApiWithBlankOtpSimpleAuthDir; +import org.zaproxy.addon.dev.auth.totp.simpleAuthTotpCaptcha.OpenApiWithCaptchaOtpSimpleAuthDir; +import org.zaproxy.addon.dev.auth.totp.simpleAuthTotpCommonBackupCodeVuln.OpenApiWithCommonOtpSimpleAuthDir; +import org.zaproxy.addon.dev.auth.totp.simpleAuthTotpLockout.OpenApiWithLockoutOtpSimpleAuthDir; +import org.zaproxy.addon.dev.auth.totp.simpleAuthTotpReplayVuln.OpenApiWithReplayOtpSimpleAuthDir; +import org.zaproxy.addon.dev.auth.totp.simpleAuthWithOTP.OpenApiWithOtpSimpleAuthDir; import org.zaproxy.addon.dev.auth.uuidLogin.UuidLoginRootDir; import org.zaproxy.addon.dev.csrf.basic.BasicCsrfDir; import org.zaproxy.addon.dev.seq.performance.PerformanceDir; @@ -99,6 +105,19 @@ public TestProxyServer(ExtensionDev extension, ExtensionNetwork extensionNetwork authDir.addDirectory(new JsonMultipleCookiesDir(this, "json-multiple-cookies")); authDir.addDirectory(new SSO1RootDir(this, "sso1")); authDir.addDirectory(new UuidLoginRootDir(this, "uuid-login")); + TestDirectory totpDir = new TestDirectory(this, "totp"); + authDir.addDirectory(totpDir); + totpDir.addDirectory(new OpenApiWithOtpSimpleAuthDir(this, "simple-auth-with-otp")); + totpDir.addDirectory( + new OpenApiWithBlankOtpSimpleAuthDir(this, "simple-auth-otp-blank-code-vuln")); + totpDir.addDirectory( + new OpenApiWithLockoutOtpSimpleAuthDir(this, "simple-auth-otp-lockout")); + totpDir.addDirectory( + new OpenApiWithCaptchaOtpSimpleAuthDir(this, "simple-auth-otp-captcha")); + totpDir.addDirectory( + new OpenApiWithReplayOtpSimpleAuthDir(this, "simple-auth-otp-replay-vuln")); + totpDir.addDirectory( + new OpenApiWithCommonOtpSimpleAuthDir(this, "simple-auth-otp-common-codes-vuln")); TestDirectory apiDir = new TestDirectory(this, "api"); TestDirectory openapiDir = new TestDirectory(this, "openapi"); diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/TestTotp.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/TestTotp.java new file mode 100644 index 00000000000..fa789fbfb3f --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/TestTotp.java @@ -0,0 +1,44 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp; + +import org.zaproxy.addon.authhelper.internal.AuthenticationStep; +import org.zaproxy.zap.authentication.UsernamePasswordAuthenticationCredentials; + +public class TestTotp { + + public static String generateCurrentCode() { + AuthenticationStep step = new AuthenticationStep(); + step.setType(AuthenticationStep.Type.TOTP_FIELD); + step.setTotpSecret("JBSWY3DPEHPK3PXP"); + step.setTotpPeriod(30); + step.setTotpDigits(6); + step.setTotpAlgorithm("SHA1"); + + UsernamePasswordAuthenticationCredentials dummyCreds = + new UsernamePasswordAuthenticationCredentials("user", "pass"); + + return step.getTotpCode(dummyCreds).toString(); + } + + public static boolean isCodeValid(String inputCode) { + return generateCurrentCode().equals(inputCode); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpBlankCodeVuln/OpenApiWithBlankOtpLoginPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpBlankCodeVuln/OpenApiWithBlankOtpLoginPage.java new file mode 100644 index 00000000000..cd0a128ee86 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpBlankCodeVuln/OpenApiWithBlankOtpLoginPage.java @@ -0,0 +1,74 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpBlankCodeVuln; + +import net.sf.json.JSONException; +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithBlankOtpLoginPage extends TestPage { + + private static final Logger LOGGER = LogManager.getLogger(OpenApiWithBlankOtpLoginPage.class); + + public OpenApiWithBlankOtpLoginPage(TestProxyServer server) { + super(server, "login"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String username = null; + String password = null; + + if (msg.getRequestHeader().hasContentType("json")) { + String postData = msg.getRequestBody().toString(); + JSONObject jsonObject; + try { + jsonObject = JSONObject.fromObject(postData); + username = jsonObject.getString("user"); + password = jsonObject.getString("password"); + } catch (JSONException e) { + LOGGER.debug("Unable to parse as JSON: {}", postData, e); + } + } + + JSONObject response = new JSONObject(); + if (getParent().isValid(username, password)) { + String token = getParent().getToken(username); + getParent().setUser(token, username); + String totp = getParent().generateAndStoreTotp(token); + response.put("result", "OK"); + response.put("accesstoken", token); + response.put("totp", totp); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + } + + @Override + public OpenApiWithBlankOtpSimpleAuthDir getParent() { + return (OpenApiWithBlankOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpBlankCodeVuln/OpenApiWithBlankOtpSimpleAuthDir.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpBlankCodeVuln/OpenApiWithBlankOtpSimpleAuthDir.java new file mode 100644 index 00000000000..1d3e2290813 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpBlankCodeVuln/OpenApiWithBlankOtpSimpleAuthDir.java @@ -0,0 +1,70 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpBlankCodeVuln; + +import java.util.HashMap; +import java.util.Map; +import org.zaproxy.addon.dev.TestAuthDirectory; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.dev.auth.totp.TestTotp; + +/** + * A directory which contains an OpenAPI spec. The spec is available unauthenticated but the + * endpoint it describes can only be accessed when a valid Authentication header is supplied. The + * login page uses one JSON request to login endpoint. The token is returned in a standard field. + */ +public class OpenApiWithBlankOtpSimpleAuthDir extends TestAuthDirectory { + private Map verifiedTokens = new HashMap<>(); + private Map tokenToUserMap = new HashMap<>(); + + public OpenApiWithBlankOtpSimpleAuthDir(TestProxyServer server, String name) { + super(server, name); + this.addPage(new OpenApiWithBlankOtpLoginPage(server)); + this.addPage(new OpenApiWithBlankOtpVerificationPage(server)); + this.addPage(new OpenApiWithBlankOtpTestApiPage(server)); + } + + public void markTokenVerified(String token) { + verifiedTokens.put(token, true); + } + + public boolean isTokenVerified(String token) { + return verifiedTokens.getOrDefault(token, false); + } + + public String generateAndStoreTotp(String token) { + String code = TestTotp.generateCurrentCode(); + return code; + } + + public boolean validateTotp(String token, String code) { + if (code == null || code.trim().isEmpty()) return true; + return TestTotp.isCodeValid(code); + } + + public void setUser(String token, String user) { + tokenToUserMap.put(token, user); + } + + @Override + public String getUser(String token) { + return tokenToUserMap.get(token); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpBlankCodeVuln/OpenApiWithBlankOtpTestApiPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpBlankCodeVuln/OpenApiWithBlankOtpTestApiPage.java new file mode 100644 index 00000000000..0da62766e08 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpBlankCodeVuln/OpenApiWithBlankOtpTestApiPage.java @@ -0,0 +1,59 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpBlankCodeVuln; + +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithBlankOtpTestApiPage extends TestPage { + + private static final Logger LOGGER = LogManager.getLogger(OpenApiWithBlankOtpTestApiPage.class); + + public OpenApiWithBlankOtpTestApiPage(TestProxyServer server) { + super(server, "test-api"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String token = msg.getRequestHeader().getHeader(HttpHeader.AUTHORIZATION); + String user = getParent().getUser(token); + LOGGER.debug("Token: {} user: {}", token, user); + + JSONObject response = new JSONObject(); + if (user != null) { + response.put("result", "Success"); + this.getServer().setJsonResponse(TestProxyServer.STATUS_OK, response, msg); + } else { + response.put("result", "FAIL"); + this.getServer().setJsonResponse(TestProxyServer.STATUS_FORBIDDEN, response, msg); + } + } + + @Override + public OpenApiWithBlankOtpSimpleAuthDir getParent() { + return (OpenApiWithBlankOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpBlankCodeVuln/OpenApiWithBlankOtpVerificationPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpBlankCodeVuln/OpenApiWithBlankOtpVerificationPage.java new file mode 100644 index 00000000000..7487e3332f0 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpBlankCodeVuln/OpenApiWithBlankOtpVerificationPage.java @@ -0,0 +1,89 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpBlankCodeVuln; + +import net.sf.json.JSONException; +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithBlankOtpVerificationPage extends TestPage { + + private static final Logger LOGGER = + LogManager.getLogger(OpenApiWithBlankOtpVerificationPage.class); + + public OpenApiWithBlankOtpVerificationPage(TestProxyServer server) { + super(server, "user"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String method = msg.getRequestHeader().getMethod(); + String token = msg.getRequestHeader().getHeader(HttpHeader.AUTHORIZATION); + String user = getParent().getUser(token); + JSONObject response = new JSONObject(); + + if ("GET".equalsIgnoreCase(method)) { + // Check token validity for GET + if (user != null && getParent().isTokenVerified(token)) { + response.put("result", "OK"); + response.put("user", user); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + return; + } + + String totp = null; + if (msg.getRequestHeader().hasContentType("json")) { + String postData = msg.getRequestBody().toString(); + JSONObject jsonObject; + try { + jsonObject = JSONObject.fromObject(postData); + totp = jsonObject.getString("code"); + } catch (JSONException e) { + LOGGER.debug("Unable to parse as JSON: {}", postData, e); + } + } + + LOGGER.debug("Token: {} user: {} TOTP: {}", token, user, totp); + + if (user != null && getParent().validateTotp(token, totp)) { + // Vulnerability: bypass TOTP check if passcode is blank + response.put("result", "OK"); + response.put("user", user); + getParent().markTokenVerified(token); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + } + + @Override + public OpenApiWithBlankOtpSimpleAuthDir getParent() { + return (OpenApiWithBlankOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCaptcha/OpenApiWithCaptchaOtpLoginPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCaptcha/OpenApiWithCaptchaOtpLoginPage.java new file mode 100644 index 00000000000..6e97af6b9e4 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCaptcha/OpenApiWithCaptchaOtpLoginPage.java @@ -0,0 +1,74 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpCaptcha; + +import net.sf.json.JSONException; +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithCaptchaOtpLoginPage extends TestPage { + + private static final Logger LOGGER = LogManager.getLogger(OpenApiWithCaptchaOtpLoginPage.class); + + public OpenApiWithCaptchaOtpLoginPage(TestProxyServer server) { + super(server, "login"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String username = null; + String password = null; + + if (msg.getRequestHeader().hasContentType("json")) { + String postData = msg.getRequestBody().toString(); + JSONObject jsonObject; + try { + jsonObject = JSONObject.fromObject(postData); + username = jsonObject.getString("user"); + password = jsonObject.getString("password"); + } catch (JSONException e) { + LOGGER.debug("Unable to parse as JSON: {}", postData, e); + } + } + + JSONObject response = new JSONObject(); + if (getParent().isValid(username, password)) { + response.put("result", "OK"); + String token = getParent().getToken(username); + getParent().setUser(token, username); + String totp = getParent().generateAndStoreTotp(token); + response.put("accesstoken", token); + response.put("totp", totp); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + } + + @Override + public OpenApiWithCaptchaOtpSimpleAuthDir getParent() { + return (OpenApiWithCaptchaOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCaptcha/OpenApiWithCaptchaOtpSimpleAuthDir.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCaptcha/OpenApiWithCaptchaOtpSimpleAuthDir.java new file mode 100644 index 00000000000..22275154b17 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCaptcha/OpenApiWithCaptchaOtpSimpleAuthDir.java @@ -0,0 +1,72 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpCaptcha; + +import java.util.HashMap; +import java.util.Map; +import org.zaproxy.addon.dev.TestAuthDirectory; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.dev.auth.totp.TestTotp; + +/** + * A directory which contains an OpenAPI spec. The spec is available unauthenticated but the + * endpoint it describes can only be accessed when a valid Authentication header is supplied. The + * login page uses one JSON request to login endpoint. The token is returned in a standard field. + */ +public class OpenApiWithCaptchaOtpSimpleAuthDir extends TestAuthDirectory { + + private Map verifiedTokens = new HashMap<>(); + private Map tokenToUserMap = new HashMap<>(); + + public OpenApiWithCaptchaOtpSimpleAuthDir(TestProxyServer server, String name) { + super(server, name); + this.addPage(new OpenApiWithCaptchaOtpLoginPage(server)); + this.addPage(new OpenApiWithCaptchaOtpVerificationPage(server)); + this.addPage(new OpenApiWithCaptchaOtpTestApiPage(server)); + } + + public void markTokenVerified(String token) { + if (token != null) { + verifiedTokens.put(token, true); + } + } + + public boolean isTokenVerified(String token) { + return verifiedTokens.getOrDefault(token, false); + } + + public String generateAndStoreTotp(String token) { + String code = TestTotp.generateCurrentCode(); + return code; + } + + public boolean validateTotp(String token, String code) { + return TestTotp.isCodeValid(code); + } + + public void setUser(String token, String user) { + tokenToUserMap.put(token, user); + } + + @Override + public String getUser(String token) { + return tokenToUserMap.get(token); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCaptcha/OpenApiWithCaptchaOtpTestApiPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCaptcha/OpenApiWithCaptchaOtpTestApiPage.java new file mode 100644 index 00000000000..ee89a314c15 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCaptcha/OpenApiWithCaptchaOtpTestApiPage.java @@ -0,0 +1,60 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpCaptcha; + +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithCaptchaOtpTestApiPage extends TestPage { + + private static final Logger LOGGER = + LogManager.getLogger(OpenApiWithCaptchaOtpTestApiPage.class); + + public OpenApiWithCaptchaOtpTestApiPage(TestProxyServer server) { + super(server, "test-api"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String token = msg.getRequestHeader().getHeader(HttpHeader.AUTHORIZATION); + String user = getParent().getUser(token); + LOGGER.debug("Token: {} user: {}", token, user); + + JSONObject response = new JSONObject(); + if (user != null) { + response.put("result", "Success"); + this.getServer().setJsonResponse(TestProxyServer.STATUS_OK, response, msg); + } else { + response.put("result", "FAIL"); + this.getServer().setJsonResponse(TestProxyServer.STATUS_FORBIDDEN, response, msg); + } + } + + @Override + public OpenApiWithCaptchaOtpSimpleAuthDir getParent() { + return (OpenApiWithCaptchaOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCaptcha/OpenApiWithCaptchaOtpVerificationPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCaptcha/OpenApiWithCaptchaOtpVerificationPage.java new file mode 100644 index 00000000000..86ad24f0ac0 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCaptcha/OpenApiWithCaptchaOtpVerificationPage.java @@ -0,0 +1,100 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpCaptcha; + +import net.sf.json.JSONException; +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithCaptchaOtpVerificationPage extends TestPage { + + private static final Logger LOGGER = + LogManager.getLogger(OpenApiWithCaptchaOtpVerificationPage.class); + + public OpenApiWithCaptchaOtpVerificationPage(TestProxyServer server) { + super(server, "user"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String method = msg.getRequestHeader().getMethod(); + String token = msg.getRequestHeader().getHeader(HttpHeader.AUTHORIZATION); + String user = getParent().getUser(token); + JSONObject response = new JSONObject(); + + if ("GET".equalsIgnoreCase(method)) { + if (user != null && getParent().isTokenVerified(token)) { + response.put("result", "OK"); + response.put("user", user); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + return; + } + + String totp = null; + String captcha = null; + + if (msg.getRequestHeader().hasContentType("json")) { + String postData = msg.getRequestBody().toString(); + JSONObject jsonObject; + try { + jsonObject = JSONObject.fromObject(postData); + totp = jsonObject.getString("code"); + captcha = jsonObject.getString("captcha"); + } catch (JSONException e) { + LOGGER.debug("Unable to parse as JSON: {}", postData, e); + } + } + + // Validate CAPTCHA (static) + boolean isCaptchaValid = "captcha123".equals(captcha); + + LOGGER.debug("Token: {} user: {} TOTP: {} CAPTCHA: {}", token, user, totp, captcha); + + if (user != null + && totp != null + && getParent().validateTotp(token, totp) + && isCaptchaValid) { + response.put("result", "OK"); + response.put("user", user); + getParent().markTokenVerified(token); + } else { + response.put("result", "FAIL"); + } + + // Always return the static captcha + response.put("newCaptcha", "captcha123"); + + this.getServer().setJsonResponse(response, msg); + } + + @Override + public OpenApiWithCaptchaOtpSimpleAuthDir getParent() { + return (OpenApiWithCaptchaOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCommonBackupCodeVuln/OpenApiWithCommonOtpLoginPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCommonBackupCodeVuln/OpenApiWithCommonOtpLoginPage.java new file mode 100644 index 00000000000..b971ba8e924 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCommonBackupCodeVuln/OpenApiWithCommonOtpLoginPage.java @@ -0,0 +1,76 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpCommonBackupCodeVuln; + +import net.sf.json.JSONException; +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithCommonOtpLoginPage extends TestPage { + + private static final Logger LOGGER = LogManager.getLogger(OpenApiWithCommonOtpLoginPage.class); + + public OpenApiWithCommonOtpLoginPage(TestProxyServer server) { + super(server, "login"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String username = null; + String password = null; + + if (msg.getRequestHeader().hasContentType("json")) { + String postData = msg.getRequestBody().toString(); + JSONObject jsonObject; + try { + jsonObject = JSONObject.fromObject(postData); + username = jsonObject.getString("user"); + password = jsonObject.getString("password"); + } catch (JSONException e) { + LOGGER.debug("Unable to parse as JSON: {}", postData, e); + } + } + + JSONObject response = new JSONObject(); + if (getParent().isValid(username, password)) { + + response.put("result", "OK"); + String token = getParent().getToken(username); + getParent().setUser(token, username); + String totp = getParent().generateAndStoreTotp(token); + response.put("accesstoken", token); + response.put("totp", totp); + + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + } + + @Override + public OpenApiWithCommonOtpSimpleAuthDir getParent() { + return (OpenApiWithCommonOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCommonBackupCodeVuln/OpenApiWithCommonOtpSimpleAuthDir.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCommonBackupCodeVuln/OpenApiWithCommonOtpSimpleAuthDir.java new file mode 100644 index 00000000000..ecd948ae21e --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCommonBackupCodeVuln/OpenApiWithCommonOtpSimpleAuthDir.java @@ -0,0 +1,119 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpCommonBackupCodeVuln; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.zaproxy.addon.dev.TestAuthDirectory; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.dev.auth.totp.TestTotp; + +/** + * A directory which contains an OpenAPI spec. The spec is available unauthenticated but the + * endpoint it describes can only be accessed when a valid Authentication header is supplied. The + * login page uses one JSON request to login endpoint. The token is returned in a standard field. + */ +public class OpenApiWithCommonOtpSimpleAuthDir extends TestAuthDirectory { + private Map verifiedTokens = new HashMap<>(); + private final Map tokenToUserMap = new HashMap<>(); + private final Map usedTotpCodes = new HashMap<>(); + private final Map lastTotpPerUser = new HashMap<>(); + + private static final Set COMMON_BACKUP_CODES = new HashSet<>(); + + static { + COMMON_BACKUP_CODES.add("000000"); + COMMON_BACKUP_CODES.add("111111"); + COMMON_BACKUP_CODES.add("222222"); + } + + private static class UsedOtpInfo { + String otpCode; + + UsedOtpInfo(String otpCode) { + this.otpCode = otpCode; + } + } + + public OpenApiWithCommonOtpSimpleAuthDir(TestProxyServer server, String name) { + super(server, name); + this.addPage(new OpenApiWithCommonOtpLoginPage(server)); + this.addPage(new OpenApiWithCommonOtpVerificationPage(server)); + this.addPage(new OpenApiWithCommonOtpTestApiPage(server)); + } + + public void markTokenVerified(String token) { + verifiedTokens.put(token, true); + } + + public boolean isTokenVerified(String token) { + return verifiedTokens.getOrDefault(token, false); + } + + public String generateAndStoreTotp(String token) { + String username = tokenToUserMap.get(token); + if (username == null) { + return "ERROR"; + } + + String newCode = TestTotp.generateCurrentCode(); + String lastCode = lastTotpPerUser.get(username); + + if (newCode.equals(lastCode)) { + lastTotpPerUser.put(username, "198755"); + return "198755"; + } + + lastTotpPerUser.put(username, newCode); + return newCode; + } + + public boolean validateTotp(String token, String code) { + String username = tokenToUserMap.get(token); + if (username == null) { + return false; + } + if (COMMON_BACKUP_CODES.contains(code)) { + return true; + } + UsedOtpInfo lastUsed = usedTotpCodes.get(username); + if (lastUsed != null && lastUsed.otpCode.equals(code)) { + return false; + } + + boolean valid = (TestTotp.isCodeValid(code) || code.equals("198755")); + if (valid) { + usedTotpCodes.put(username, new UsedOtpInfo(code)); + } + + return valid; + } + + public void setUser(String token, String user) { + tokenToUserMap.put(token, user); + } + + @Override + public String getUser(String token) { + return tokenToUserMap.get(token); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCommonBackupCodeVuln/OpenApiWithCommonOtpTestApiPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCommonBackupCodeVuln/OpenApiWithCommonOtpTestApiPage.java new file mode 100644 index 00000000000..d99947507eb --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCommonBackupCodeVuln/OpenApiWithCommonOtpTestApiPage.java @@ -0,0 +1,60 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpCommonBackupCodeVuln; + +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithCommonOtpTestApiPage extends TestPage { + + private static final Logger LOGGER = + LogManager.getLogger(OpenApiWithCommonOtpTestApiPage.class); + + public OpenApiWithCommonOtpTestApiPage(TestProxyServer server) { + super(server, "test-api"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String token = msg.getRequestHeader().getHeader(HttpHeader.AUTHORIZATION); + String user = getParent().getUser(token); + LOGGER.debug("Token: {} user: {}", token, user); + + JSONObject response = new JSONObject(); + if (user != null) { + response.put("result", "Success"); + this.getServer().setJsonResponse(TestProxyServer.STATUS_OK, response, msg); + } else { + response.put("result", "FAIL"); + this.getServer().setJsonResponse(TestProxyServer.STATUS_FORBIDDEN, response, msg); + } + } + + @Override + public OpenApiWithCommonOtpSimpleAuthDir getParent() { + return (OpenApiWithCommonOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCommonBackupCodeVuln/OpenApiWithCommonOtpVerificationPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCommonBackupCodeVuln/OpenApiWithCommonOtpVerificationPage.java new file mode 100644 index 00000000000..0518dcf2cac --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpCommonBackupCodeVuln/OpenApiWithCommonOtpVerificationPage.java @@ -0,0 +1,86 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpCommonBackupCodeVuln; + +import net.sf.json.JSONException; +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithCommonOtpVerificationPage extends TestPage { + + private static final Logger LOGGER = + LogManager.getLogger(OpenApiWithCommonOtpVerificationPage.class); + + public OpenApiWithCommonOtpVerificationPage(TestProxyServer server) { + super(server, "user"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String method = msg.getRequestHeader().getMethod(); + String token = msg.getRequestHeader().getHeader(HttpHeader.AUTHORIZATION); + String user = getParent().getUser(token); + JSONObject response = new JSONObject(); + + if ("GET".equalsIgnoreCase(method)) { + if (user != null && getParent().isTokenVerified(token)) { + response.put("result", "OK"); + response.put("user", user); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + return; + } + + String totp = null; + if (msg.getRequestHeader().hasContentType("json")) { + String postData = msg.getRequestBody().toString(); + try { + JSONObject jsonObject = JSONObject.fromObject(postData); + totp = jsonObject.getString("code"); + } catch (JSONException e) { + LOGGER.debug("Unable to parse as JSON: {}", postData, e); + } + } + + LOGGER.debug("Token: {} user: {} TOTP: {}", token, user, totp); + + if (user != null && totp != null && getParent().validateTotp(token, totp)) { + response.put("result", "OK"); + response.put("user", user); + getParent().markTokenVerified(token); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + } + + @Override + public OpenApiWithCommonOtpSimpleAuthDir getParent() { + return (OpenApiWithCommonOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpLockout/OpenApiWithLockoutOtpLoginPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpLockout/OpenApiWithLockoutOtpLoginPage.java new file mode 100644 index 00000000000..f462c54c57f --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpLockout/OpenApiWithLockoutOtpLoginPage.java @@ -0,0 +1,74 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpLockout; + +import net.sf.json.JSONException; +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithLockoutOtpLoginPage extends TestPage { + + private static final Logger LOGGER = LogManager.getLogger(OpenApiWithLockoutOtpLoginPage.class); + + public OpenApiWithLockoutOtpLoginPage(TestProxyServer server) { + super(server, "login"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String username = null; + String password = null; + + if (msg.getRequestHeader().hasContentType("json")) { + String postData = msg.getRequestBody().toString(); + JSONObject jsonObject; + try { + jsonObject = JSONObject.fromObject(postData); + username = jsonObject.getString("user"); + password = jsonObject.getString("password"); + } catch (JSONException e) { + LOGGER.debug("Unable to parse as JSON: {}", postData, e); + } + } + + JSONObject response = new JSONObject(); + if (getParent().isValid(username, password)) { + response.put("result", "OK"); + String token = getParent().getToken(username); + getParent().setUser(token, username); + String totp = getParent().generateAndStoreTotp(token); + response.put("accesstoken", token); + response.put("totp", totp); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + } + + @Override + public OpenApiWithLockoutOtpSimpleAuthDir getParent() { + return (OpenApiWithLockoutOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpLockout/OpenApiWithLockoutOtpSimpleAuthDir.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpLockout/OpenApiWithLockoutOtpSimpleAuthDir.java new file mode 100644 index 00000000000..54b4393a74f --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpLockout/OpenApiWithLockoutOtpSimpleAuthDir.java @@ -0,0 +1,81 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpLockout; + +import java.util.HashMap; +import java.util.Map; +import org.zaproxy.addon.dev.TestAuthDirectory; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.dev.auth.totp.TestTotp; + +/** + * A directory which contains an OpenAPI spec. The spec is available unauthenticated but the + * endpoint it describes can only be accessed when a valid Authentication header is supplied. The + * login page uses one JSON request to login endpoint. The token is returned in a standard field. + */ +public class OpenApiWithLockoutOtpSimpleAuthDir extends TestAuthDirectory { + + private Map verifiedTokens = new HashMap<>(); + private Map failedAttempts = new HashMap<>(); + private Map tokenToUserMap = new HashMap<>(); + private static final int MAX_ATTEMPTS = 3; + + public OpenApiWithLockoutOtpSimpleAuthDir(TestProxyServer server, String name) { + super(server, name); + this.addPage(new OpenApiWithLockoutOtpLoginPage(server)); + this.addPage(new OpenApiWithLockoutOtpVerificationPage(server)); + this.addPage(new OpenApiWithLockoutOtpTestApiPage(server)); + } + + public void markTokenVerified(String token) { + verifiedTokens.put(token, true); + } + + public boolean isTokenVerified(String token) { + return verifiedTokens.getOrDefault(token, false); + } + + public String generateAndStoreTotp(String token) { + String code = TestTotp.generateCurrentCode(); + return code; + } + + public boolean validateTotp(String token, String code) { + return TestTotp.isCodeValid(code); + } + + public void setUser(String token, String user) { + tokenToUserMap.put(token, user); + } + + @Override + public String getUser(String token) { + return tokenToUserMap.get(token); + } + + public void recordFailedAttempt(String token) { + if (token == null) return; + failedAttempts.put(token, failedAttempts.getOrDefault(token, 0) + 1); + } + + public boolean isLockedOut(String token) { + return failedAttempts.getOrDefault(token, 0) >= MAX_ATTEMPTS; + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpLockout/OpenApiWithLockoutOtpTestApiPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpLockout/OpenApiWithLockoutOtpTestApiPage.java new file mode 100644 index 00000000000..df7a3803452 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpLockout/OpenApiWithLockoutOtpTestApiPage.java @@ -0,0 +1,60 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpLockout; + +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithLockoutOtpTestApiPage extends TestPage { + + private static final Logger LOGGER = + LogManager.getLogger(OpenApiWithLockoutOtpTestApiPage.class); + + public OpenApiWithLockoutOtpTestApiPage(TestProxyServer server) { + super(server, "test-api"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String token = msg.getRequestHeader().getHeader(HttpHeader.AUTHORIZATION); + String user = getParent().getUser(token); + LOGGER.debug("Token: {} user: {}", token, user); + + JSONObject response = new JSONObject(); + if (user != null) { + response.put("result", "Success"); + this.getServer().setJsonResponse(TestProxyServer.STATUS_OK, response, msg); + } else { + response.put("result", "FAIL"); + this.getServer().setJsonResponse(TestProxyServer.STATUS_FORBIDDEN, response, msg); + } + } + + @Override + public OpenApiWithLockoutOtpSimpleAuthDir getParent() { + return (OpenApiWithLockoutOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpLockout/OpenApiWithLockoutOtpVerificationPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpLockout/OpenApiWithLockoutOtpVerificationPage.java new file mode 100644 index 00000000000..c50a303baa6 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpLockout/OpenApiWithLockoutOtpVerificationPage.java @@ -0,0 +1,92 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpLockout; + +import net.sf.json.JSONException; +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithLockoutOtpVerificationPage extends TestPage { + + private static final Logger LOGGER = + LogManager.getLogger(OpenApiWithLockoutOtpVerificationPage.class); + + public OpenApiWithLockoutOtpVerificationPage(TestProxyServer server) { + super(server, "user"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String method = msg.getRequestHeader().getMethod(); + String token = msg.getRequestHeader().getHeader(HttpHeader.AUTHORIZATION); + String user = getParent().getUser(token); + JSONObject response = new JSONObject(); + + if ("GET".equalsIgnoreCase(method)) { + if (user != null && getParent().isTokenVerified(token)) { + response.put("result", "OK"); + response.put("user", user); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + return; + } + + String totp = null; + + if (msg.getRequestHeader().hasContentType("json")) { + String postData = msg.getRequestBody().toString(); + JSONObject jsonObject; + try { + jsonObject = JSONObject.fromObject(postData); + totp = jsonObject.getString("code"); + } catch (JSONException e) { + LOGGER.debug("Unable to parse as JSON: {}", postData, e); + } + } + + LOGGER.debug("Token: {} user: {} TOTP: {}", token, user, totp); + + if (getParent().isLockedOut(token)) { + response.put("result", "LOCKED"); + } else if (user != null && totp != null && getParent().validateTotp(token, totp)) { + response.put("result", "OK"); + response.put("user", user); + getParent().markTokenVerified(token); + } else { + getParent().recordFailedAttempt(token); + response.put("result", "FAIL"); + } + + this.getServer().setJsonResponse(response, msg); + } + + @Override + public OpenApiWithLockoutOtpSimpleAuthDir getParent() { + return (OpenApiWithLockoutOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpReplayVuln/OpenApiWithReplayOtpLoginPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpReplayVuln/OpenApiWithReplayOtpLoginPage.java new file mode 100644 index 00000000000..124509169e2 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpReplayVuln/OpenApiWithReplayOtpLoginPage.java @@ -0,0 +1,76 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpReplayVuln; + +import net.sf.json.JSONException; +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithReplayOtpLoginPage extends TestPage { + + private static final Logger LOGGER = LogManager.getLogger(OpenApiWithReplayOtpLoginPage.class); + + public OpenApiWithReplayOtpLoginPage(TestProxyServer server) { + super(server, "login"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String username = null; + String password = null; + + if (msg.getRequestHeader().hasContentType("json")) { + String postData = msg.getRequestBody().toString(); + JSONObject jsonObject; + try { + jsonObject = JSONObject.fromObject(postData); + username = jsonObject.getString("user"); + password = jsonObject.getString("password"); + } catch (JSONException e) { + LOGGER.debug("Unable to parse as JSON: {}", postData, e); + } + } + + JSONObject response = new JSONObject(); + if (getParent().isValid(username, password)) { + + response.put("result", "OK"); + String token = getParent().getToken(username); + getParent().setUser(token, username); + String totp = getParent().generateAndStoreTotp(token); + response.put("accesstoken", token); + response.put("totp", totp); + + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + } + + @Override + public OpenApiWithReplayOtpSimpleAuthDir getParent() { + return (OpenApiWithReplayOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpReplayVuln/OpenApiWithReplayOtpSimpleAuthDir.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpReplayVuln/OpenApiWithReplayOtpSimpleAuthDir.java new file mode 100644 index 00000000000..09e259ab9f7 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpReplayVuln/OpenApiWithReplayOtpSimpleAuthDir.java @@ -0,0 +1,87 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpReplayVuln; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.zaproxy.addon.dev.TestAuthDirectory; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.dev.auth.totp.TestTotp; + +/** + * A directory which contains an OpenAPI spec. The spec is available unauthenticated but the + * endpoint it describes can only be accessed when a valid Authentication header is supplied. The + * login page uses one JSON request to login endpoint. The token is returned in a standard field. + */ +public class OpenApiWithReplayOtpSimpleAuthDir extends TestAuthDirectory { + private Map verifiedTokens = new HashMap<>(); + private Map tokenToUserMap = new HashMap<>(); + private Map> acceptedTotpsPerUser = new HashMap<>(); + + public OpenApiWithReplayOtpSimpleAuthDir(TestProxyServer server, String name) { + super(server, name); + this.addPage(new OpenApiWithReplayOtpLoginPage(server)); + this.addPage(new OpenApiWithReplayOtpVerificationPage(server)); + this.addPage(new OpenApiWithReplayOtpTestApiPage(server)); + } + + public void markTokenVerified(String token) { + verifiedTokens.put(token, true); + } + + public boolean isTokenVerified(String token) { + return verifiedTokens.getOrDefault(token, false); + } + + public String generateAndStoreTotp(String token) { + String user = tokenToUserMap.get(token); + if (user == null) { + return "ERROR"; + } + + String code = TestTotp.generateCurrentCode(); + + acceptedTotpsPerUser.computeIfAbsent(user, k -> new HashSet<>()).add(code); + + return code; + } + + public boolean validateTotp(String token, String code) { + String user = tokenToUserMap.get(token); + if (user == null) { + return false; + } + + Set acceptedCodes = acceptedTotpsPerUser.getOrDefault(user, Collections.emptySet()); + return acceptedCodes.contains(code); + } + + public void setUser(String token, String user) { + tokenToUserMap.put(token, user); + } + + @Override + public String getUser(String token) { + return tokenToUserMap.get(token); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpReplayVuln/OpenApiWithReplayOtpTestApiPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpReplayVuln/OpenApiWithReplayOtpTestApiPage.java new file mode 100644 index 00000000000..c7cfe0fd43e --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpReplayVuln/OpenApiWithReplayOtpTestApiPage.java @@ -0,0 +1,60 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpReplayVuln; + +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithReplayOtpTestApiPage extends TestPage { + + private static final Logger LOGGER = + LogManager.getLogger(OpenApiWithReplayOtpTestApiPage.class); + + public OpenApiWithReplayOtpTestApiPage(TestProxyServer server) { + super(server, "test-api"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String token = msg.getRequestHeader().getHeader(HttpHeader.AUTHORIZATION); + String user = getParent().getUser(token); + LOGGER.debug("Token: {} user: {}", token, user); + + JSONObject response = new JSONObject(); + if (user != null) { + response.put("result", "Success"); + this.getServer().setJsonResponse(TestProxyServer.STATUS_OK, response, msg); + } else { + response.put("result", "FAIL"); + this.getServer().setJsonResponse(TestProxyServer.STATUS_FORBIDDEN, response, msg); + } + } + + @Override + public OpenApiWithReplayOtpSimpleAuthDir getParent() { + return (OpenApiWithReplayOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpReplayVuln/OpenApiWithReplayOtpVerificationPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpReplayVuln/OpenApiWithReplayOtpVerificationPage.java new file mode 100644 index 00000000000..da8589990a7 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthTotpReplayVuln/OpenApiWithReplayOtpVerificationPage.java @@ -0,0 +1,86 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthTotpReplayVuln; + +import net.sf.json.JSONException; +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithReplayOtpVerificationPage extends TestPage { + + private static final Logger LOGGER = + LogManager.getLogger(OpenApiWithReplayOtpVerificationPage.class); + + public OpenApiWithReplayOtpVerificationPage(TestProxyServer server) { + super(server, "user"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String method = msg.getRequestHeader().getMethod(); + String token = msg.getRequestHeader().getHeader(HttpHeader.AUTHORIZATION); + String user = getParent().getUser(token); + JSONObject response = new JSONObject(); + + if ("GET".equalsIgnoreCase(method)) { + if (user != null && getParent().isTokenVerified(token)) { + response.put("result", "OK"); + response.put("user", user); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + return; + } + + String totp = null; + if (msg.getRequestHeader().hasContentType("json")) { + String postData = msg.getRequestBody().toString(); + try { + JSONObject jsonObject = JSONObject.fromObject(postData); + totp = jsonObject.getString("code"); + } catch (JSONException e) { + LOGGER.debug("Unable to parse as JSON: {}", postData, e); + } + } + + LOGGER.debug("Token: {} user: {} TOTP: {}", token, user, totp); + + if (user != null && totp != null && getParent().validateTotp(token, totp)) { + response.put("result", "OK"); + response.put("user", user); + getParent().markTokenVerified(token); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + } + + @Override + public OpenApiWithReplayOtpSimpleAuthDir getParent() { + return (OpenApiWithReplayOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthWithOTP/OpenApiWithOtpLoginPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthWithOTP/OpenApiWithOtpLoginPage.java new file mode 100644 index 00000000000..45a6a555f1b --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthWithOTP/OpenApiWithOtpLoginPage.java @@ -0,0 +1,76 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthWithOTP; + +import net.sf.json.JSONException; +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithOtpLoginPage extends TestPage { + + private static final Logger LOGGER = LogManager.getLogger(OpenApiWithOtpLoginPage.class); + + public OpenApiWithOtpLoginPage(TestProxyServer server) { + super(server, "login"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String username = null; + String password = null; + + if (msg.getRequestHeader().hasContentType("json")) { + String postData = msg.getRequestBody().toString(); + JSONObject jsonObject; + try { + jsonObject = JSONObject.fromObject(postData); + username = jsonObject.getString("user"); + password = jsonObject.getString("password"); + } catch (JSONException e) { + LOGGER.debug("Unable to parse as JSON: {}", postData, e); + } + } + + JSONObject response = new JSONObject(); + if (getParent().isValid(username, password)) { + + response.put("result", "OK"); + String token = getParent().getToken(username); + getParent().setUser(token, username); + String totp = getParent().generateAndStoreTotp(token); + response.put("accesstoken", token); + response.put("totp", totp); + + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + } + + @Override + public OpenApiWithOtpSimpleAuthDir getParent() { + return (OpenApiWithOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthWithOTP/OpenApiWithOtpSimpleAuthDir.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthWithOTP/OpenApiWithOtpSimpleAuthDir.java new file mode 100644 index 00000000000..2a788149109 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthWithOTP/OpenApiWithOtpSimpleAuthDir.java @@ -0,0 +1,107 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthWithOTP; + +import java.util.HashMap; +import java.util.Map; +import org.zaproxy.addon.dev.TestAuthDirectory; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.dev.auth.totp.TestTotp; + +/** + * A directory which contains an OpenAPI spec. The spec is available unauthenticated but the + * endpoint it describes can only be accessed when a valid Authentication header is supplied. The + * login page uses one JSON request to login endpoint. The token is returned in a standard field. + */ +public class OpenApiWithOtpSimpleAuthDir extends TestAuthDirectory { + private Map verifiedTokens = new HashMap<>(); + private final Map tokenToUserMap = new HashMap<>(); + private final Map usedTotpCodes = new HashMap<>(); + private final Map lastTotpPerUser = new HashMap<>(); + + private static class UsedOtpInfo { + String otpCode; + + UsedOtpInfo(String otpCode) { + this.otpCode = otpCode; + } + } + + public OpenApiWithOtpSimpleAuthDir(TestProxyServer server, String name) { + super(server, name); + this.addPage(new OpenApiWithOtpLoginPage(server)); + this.addPage(new OpenApiWithOtpVerificationPage(server)); + this.addPage(new OpenApiWithOtpTestApiPage(server)); + } + + public void markTokenVerified(String token) { + verifiedTokens.put(token, true); + } + + public boolean isTokenVerified(String token) { + return verifiedTokens.getOrDefault(token, false); + } + + public String generateAndStoreTotp(String token) { + String username = tokenToUserMap.get(token); + if (username == null) { + return "ERROR"; + } + + String newCode = TestTotp.generateCurrentCode(); + String lastCode = lastTotpPerUser.get(username); + + if (newCode.equals(lastCode)) { + lastTotpPerUser.put(username, "198755"); + return "198755"; + } + + lastTotpPerUser.put(username, newCode); + return newCode; + } + + public boolean validateTotp(String token, String code) { + String username = tokenToUserMap.get(token); + if (username == null) { + return false; + } + + UsedOtpInfo lastUsed = usedTotpCodes.get(username); + if (lastUsed != null && lastUsed.otpCode.equals(code)) { + return false; // Replay for same user + } + + boolean valid = (TestTotp.isCodeValid(code) || code.equals("198755")); + if (valid) { + usedTotpCodes.put(username, new UsedOtpInfo(code)); + } + + return valid; + } + + public void setUser(String token, String user) { + tokenToUserMap.put(token, user); + } + + @Override + public String getUser(String token) { + return tokenToUserMap.get(token); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthWithOTP/OpenApiWithOtpTestApiPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthWithOTP/OpenApiWithOtpTestApiPage.java new file mode 100644 index 00000000000..50974496271 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthWithOTP/OpenApiWithOtpTestApiPage.java @@ -0,0 +1,59 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthWithOTP; + +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithOtpTestApiPage extends TestPage { + + private static final Logger LOGGER = LogManager.getLogger(OpenApiWithOtpTestApiPage.class); + + public OpenApiWithOtpTestApiPage(TestProxyServer server) { + super(server, "test-api"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String token = msg.getRequestHeader().getHeader(HttpHeader.AUTHORIZATION); + String user = getParent().getUser(token); + LOGGER.debug("Token: {} user: {}", token, user); + + JSONObject response = new JSONObject(); + if (user != null) { + response.put("result", "Success"); + this.getServer().setJsonResponse(TestProxyServer.STATUS_OK, response, msg); + } else { + response.put("result", "FAIL"); + this.getServer().setJsonResponse(TestProxyServer.STATUS_FORBIDDEN, response, msg); + } + } + + @Override + public OpenApiWithOtpSimpleAuthDir getParent() { + return (OpenApiWithOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthWithOTP/OpenApiWithOtpVerificationPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthWithOTP/OpenApiWithOtpVerificationPage.java new file mode 100644 index 00000000000..35c6c535a96 --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/auth/totp/simpleAuthWithOTP/OpenApiWithOtpVerificationPage.java @@ -0,0 +1,87 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.auth.totp.simpleAuthWithOTP; + +import net.sf.json.JSONException; +import net.sf.json.JSONObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class OpenApiWithOtpVerificationPage extends TestPage { + + private static final Logger LOGGER = LogManager.getLogger(OpenApiWithOtpVerificationPage.class); + + public OpenApiWithOtpVerificationPage(TestProxyServer server) { + super(server, "user"); + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String method = msg.getRequestHeader().getMethod(); + String token = msg.getRequestHeader().getHeader(HttpHeader.AUTHORIZATION); + String user = getParent().getUser(token); + JSONObject response = new JSONObject(); + + if ("GET".equalsIgnoreCase(method)) { + // Check token validity for GET + if (user != null && getParent().isTokenVerified(token)) { + response.put("result", "OK"); + response.put("user", user); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + return; + } + + // TOTP validation + String totp = null; + if (msg.getRequestHeader().hasContentType("json")) { + String postData = msg.getRequestBody().toString(); + try { + JSONObject jsonObject = JSONObject.fromObject(postData); + totp = jsonObject.getString("code"); + } catch (JSONException e) { + LOGGER.debug("Unable to parse as JSON: {}", postData, e); + } + } + + LOGGER.debug("Token: {} user: {} TOTP: {}", token, user, totp); + + if (user != null && totp != null && getParent().validateTotp(token, totp)) { + response.put("result", "OK"); + response.put("user", user); + getParent().markTokenVerified(token); + } else { + response.put("result", "FAIL"); + } + this.getServer().setJsonResponse(response, msg); + } + + @Override + public OpenApiWithOtpSimpleAuthDir getParent() { + return (OpenApiWithOtpSimpleAuthDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/api/openapi/simple-auth-with-otp/home.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/api/openapi/simple-auth-with-otp/home.html new file mode 100644 index 00000000000..ad7386219f0 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/api/openapi/simple-auth-with-otp/home.html @@ -0,0 +1,42 @@ + + + + ZAP Test Server + + + +

Simple Home Page

+
+
+
+ + + + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/api/openapi/simple-auth-with-otp/index.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/api/openapi/simple-auth-with-otp/index.html new file mode 100644 index 00000000000..e93f8eef40a --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/api/openapi/simple-auth-with-otp/index.html @@ -0,0 +1,85 @@ + + + + ZAP Test Server + + + +
+

Simple TOTP OpenAPI Spec, with Authentication

+

Login

+ +
+ +
+ + + + + + + + + + + +
Username: +
Password: +
+
+

+ Test credentials: +

    +
  • username = test@test.com +
  • password = password123 +
+ The verification URL returns JSON with the username if valid, and a 200 response in all cases. +

+ OpenAPI spec in openapi.json - no links, available unauthenticated but API only available with a valid Authorization header. + +

+ + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/api/openapi/simple-auth-with-otp/totp.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/api/openapi/simple-auth-with-otp/totp.html new file mode 100644 index 00000000000..0fe54d68089 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/api/openapi/simple-auth-with-otp/totp.html @@ -0,0 +1,70 @@ + + + + ZAP Test Server + + + +
+

Enter TOTP Passcode

+ +
+ +
+ + + + + + + + + +
TOTP Passcode:
+
+

Your TOTP Code:

+
+ + + \ No newline at end of file diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-blank-code-vuln/home.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-blank-code-vuln/home.html new file mode 100644 index 00000000000..ad7386219f0 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-blank-code-vuln/home.html @@ -0,0 +1,42 @@ + + + + ZAP Test Server + + + +

Simple Home Page

+
+
+
+ + + + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-blank-code-vuln/index.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-blank-code-vuln/index.html new file mode 100644 index 00000000000..fb8c2027fde --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-blank-code-vuln/index.html @@ -0,0 +1,86 @@ + + + + ZAP Test Server + + + +
+

Simple TOTP OpenAPI Spec, with Authentication

+

Login

+ +
+ +
+ + + + + + + + + + + +
Username: +
Password: +
+
+

+ Test credentials: +

    +
  • username = test@test.com +
  • password = password123 +
  • TOTP secret = JBSWY3DPEHPK3PXP +
+ The verification URL returns JSON with the username if valid, and a 200 response in all cases. +

+ OpenAPI spec in openapi.json - no links, available unauthenticated but API only available with a valid Authorization header. + +

+ + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-blank-code-vuln/totp.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-blank-code-vuln/totp.html new file mode 100644 index 00000000000..934ee4a8e48 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-blank-code-vuln/totp.html @@ -0,0 +1,71 @@ + + + + ZAP Test Server + + + +
+

Enter TOTP Passcode

+ +
+ +
+ + + + + + + + + +
TOTP Passcode:
+
+

Your TOTP Code:

+
+ + + \ No newline at end of file diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-captcha/home.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-captcha/home.html new file mode 100644 index 00000000000..ad7386219f0 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-captcha/home.html @@ -0,0 +1,42 @@ + + + + ZAP Test Server + + + +

Simple Home Page

+
+
+
+ + + + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-captcha/index.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-captcha/index.html new file mode 100644 index 00000000000..fb8c2027fde --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-captcha/index.html @@ -0,0 +1,86 @@ + + + + ZAP Test Server + + + +
+

Simple TOTP OpenAPI Spec, with Authentication

+

Login

+ +
+ +
+ + + + + + + + + + + +
Username: +
Password: +
+
+

+ Test credentials: +

    +
  • username = test@test.com +
  • password = password123 +
  • TOTP secret = JBSWY3DPEHPK3PXP +
+ The verification URL returns JSON with the username if valid, and a 200 response in all cases. +

+ OpenAPI spec in openapi.json - no links, available unauthenticated but API only available with a valid Authorization header. + +

+ + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-captcha/totp.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-captcha/totp.html new file mode 100644 index 00000000000..6454db2b990 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-captcha/totp.html @@ -0,0 +1,90 @@ + + + + ZAP Test Server + + + +
+

Enter TOTP Passcode

+ +
+ +
+ + + + + + + + + + + + + + + + + +
TOTP Passcode:
CAPTCHA:
captcha123
+
+

Your TOTP Code:

+
+ + + \ No newline at end of file diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-common-codes-vuln/home.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-common-codes-vuln/home.html new file mode 100644 index 00000000000..ad7386219f0 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-common-codes-vuln/home.html @@ -0,0 +1,42 @@ + + + + ZAP Test Server + + + +

Simple Home Page

+
+
+
+ + + + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-common-codes-vuln/index.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-common-codes-vuln/index.html new file mode 100644 index 00000000000..fb8c2027fde --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-common-codes-vuln/index.html @@ -0,0 +1,86 @@ + + + + ZAP Test Server + + + +
+

Simple TOTP OpenAPI Spec, with Authentication

+

Login

+ +
+ +
+ + + + + + + + + + + +
Username: +
Password: +
+
+

+ Test credentials: +

    +
  • username = test@test.com +
  • password = password123 +
  • TOTP secret = JBSWY3DPEHPK3PXP +
+ The verification URL returns JSON with the username if valid, and a 200 response in all cases. +

+ OpenAPI spec in openapi.json - no links, available unauthenticated but API only available with a valid Authorization header. + +

+ + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-common-codes-vuln/totp.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-common-codes-vuln/totp.html new file mode 100644 index 00000000000..934ee4a8e48 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-common-codes-vuln/totp.html @@ -0,0 +1,71 @@ + + + + ZAP Test Server + + + +
+

Enter TOTP Passcode

+ +
+ +
+ + + + + + + + + +
TOTP Passcode:
+
+

Your TOTP Code:

+
+ + + \ No newline at end of file diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-lockout/home.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-lockout/home.html new file mode 100644 index 00000000000..ad7386219f0 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-lockout/home.html @@ -0,0 +1,42 @@ + + + + ZAP Test Server + + + +

Simple Home Page

+
+
+
+ + + + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-lockout/index.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-lockout/index.html new file mode 100644 index 00000000000..7a90aa0c696 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-lockout/index.html @@ -0,0 +1,101 @@ + + + + ZAP Test Server + + + +
+

Simple TOTP OpenAPI Spec, with Authentication

+

Login

+ +
+ +
+ + + + + + + + + + + + + + + +
Username: +
Password: +
+
+

+ Test credentials: +

    +
  • username = test@test.com +
  • password = password123 +
  • TOTP secret = JBSWY3DPEHPK3PXP +
+ The verification URL returns JSON with the username if valid, and a 200 response in all cases. +

+ OpenAPI spec in openapi.json - no links, available unauthenticated but API only available with a valid Authorization header. + +

+ + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-lockout/totp.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-lockout/totp.html new file mode 100644 index 00000000000..cf5b868f515 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-lockout/totp.html @@ -0,0 +1,86 @@ + + + + ZAP Test Server + + + +
+

Enter TOTP Passcode

+ +
+ +
+ + + + + + + + + +
TOTP Passcode:
+
+

Your TOTP Code:

+
+ + + \ No newline at end of file diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-replay-vuln/home.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-replay-vuln/home.html new file mode 100644 index 00000000000..ad7386219f0 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-replay-vuln/home.html @@ -0,0 +1,42 @@ + + + + ZAP Test Server + + + +

Simple Home Page

+
+
+
+ + + + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-replay-vuln/index.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-replay-vuln/index.html new file mode 100644 index 00000000000..fb8c2027fde --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-replay-vuln/index.html @@ -0,0 +1,86 @@ + + + + ZAP Test Server + + + +
+

Simple TOTP OpenAPI Spec, with Authentication

+

Login

+ +
+ +
+ + + + + + + + + + + +
Username: +
Password: +
+
+

+ Test credentials: +

    +
  • username = test@test.com +
  • password = password123 +
  • TOTP secret = JBSWY3DPEHPK3PXP +
+ The verification URL returns JSON with the username if valid, and a 200 response in all cases. +

+ OpenAPI spec in openapi.json - no links, available unauthenticated but API only available with a valid Authorization header. + +

+ + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-replay-vuln/totp.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-replay-vuln/totp.html new file mode 100644 index 00000000000..934ee4a8e48 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-otp-replay-vuln/totp.html @@ -0,0 +1,71 @@ + + + + ZAP Test Server + + + +
+

Enter TOTP Passcode

+ +
+ +
+ + + + + + + + + +
TOTP Passcode:
+
+

Your TOTP Code:

+
+ + + \ No newline at end of file diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-with-otp/home.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-with-otp/home.html new file mode 100644 index 00000000000..ad7386219f0 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-with-otp/home.html @@ -0,0 +1,42 @@ + + + + ZAP Test Server + + + +

Simple Home Page

+
+
+
+ + + + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-with-otp/index.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-with-otp/index.html new file mode 100644 index 00000000000..fb8c2027fde --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-with-otp/index.html @@ -0,0 +1,86 @@ + + + + ZAP Test Server + + + +
+

Simple TOTP OpenAPI Spec, with Authentication

+

Login

+ +
+ +
+ + + + + + + + + + + +
Username: +
Password: +
+
+

+ Test credentials: +

    +
  • username = test@test.com +
  • password = password123 +
  • TOTP secret = JBSWY3DPEHPK3PXP +
+ The verification URL returns JSON with the username if valid, and a 200 response in all cases. +

+ OpenAPI spec in openapi.json - no links, available unauthenticated but API only available with a valid Authorization header. + +

+ + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-with-otp/totp.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-with-otp/totp.html new file mode 100644 index 00000000000..934ee4a8e48 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/auth/totp/simple-auth-with-otp/totp.html @@ -0,0 +1,71 @@ + + + + ZAP Test Server + + + +
+

Enter TOTP Passcode

+ +
+ +
+ + + + + + + + + +
TOTP Passcode:
+
+

Your TOTP Code:

+
+ + + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index e68633cff62..7c30850312c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ org.gradle.caching=true -org.gradle.parallel=true \ No newline at end of file +org.gradle.parallel=true +org.gradle.jvmargs=-Xmx4g \ No newline at end of file