diff --git a/addOns/authhelper/CHANGELOG.md b/addOns/authhelper/CHANGELOG.md index 7ecdf000d4d..5fbf1bbe83f 100644 --- a/addOns/authhelper/CHANGELOG.md +++ b/addOns/authhelper/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Update minimum ZAP version to 2.16.0. - Depend on Passive Scanner add-on (Issue 7959). - Address deprecation warnings with newer Selenium version (4.27). +- Optionally depend on the Client Integration add-on to provide Browser Based Authentication to the Client Spider. ## [0.16.0] - 2024-11-06 ### Fixed diff --git a/addOns/authhelper/authhelper.gradle.kts b/addOns/authhelper/authhelper.gradle.kts index 467a7ba9f0f..660c09253d5 100644 --- a/addOns/authhelper/authhelper.gradle.kts +++ b/addOns/authhelper/authhelper.gradle.kts @@ -22,6 +22,18 @@ zapAddOn { } } } + register("org.zaproxy.addon.authhelper.client.ExtensionAuthhelperClient") { + classnames { + allowed.set(listOf("org.zaproxy.addon.authhelper.client")) + } + dependencies { + addOns { + register("client") { + version.set(">=0.10.0") + } + } + } + } } dependencies { addOns { @@ -56,6 +68,7 @@ dependencies { zapAddOn("pscan") zapAddOn("selenium") zapAddOn("spiderAjax") + zapAddOn("client") testImplementation(project(":testutils")) } diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/client/BrowserBasedAuthHandler.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/client/BrowserBasedAuthHandler.java new file mode 100644 index 00000000000..02748de8822 --- /dev/null +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/client/BrowserBasedAuthHandler.java @@ -0,0 +1,43 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2024 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.authhelper.client; + +import org.zaproxy.addon.authhelper.AuthUtils; +import org.zaproxy.addon.authhelper.BrowserBasedAuthenticationMethodType; +import org.zaproxy.addon.client.spider.AuthenticationHandler; +import org.zaproxy.zap.model.Context; +import org.zaproxy.zap.users.User; + +public class BrowserBasedAuthHandler implements AuthenticationHandler { + + @Override + public void enableAuthentication(User user) { + Context context = user.getContext(); + if (context.getAuthenticationMethod() + instanceof BrowserBasedAuthenticationMethodType.BrowserBasedAuthenticationMethod) { + AuthUtils.enableBrowserAuthentication(context, user.getName()); + } + } + + @Override + public void disableAuthentication(User user) { + AuthUtils.disableBrowserAuthentication(); + } +} diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/client/ExtensionAuthhelperClient.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/client/ExtensionAuthhelperClient.java new file mode 100644 index 00000000000..a7d0f964fcb --- /dev/null +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/client/ExtensionAuthhelperClient.java @@ -0,0 +1,85 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2024 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.authhelper.client; + +import java.util.List; +import org.parosproxy.paros.Constant; +import org.parosproxy.paros.control.Control; +import org.parosproxy.paros.extension.Extension; +import org.parosproxy.paros.extension.ExtensionAdaptor; +import org.parosproxy.paros.extension.ExtensionHook; +import org.zaproxy.addon.client.ExtensionClientIntegration; + +public class ExtensionAuthhelperClient extends ExtensionAdaptor { + + public static final String NAME = "ExtensionAuthhelperClient"; + + private static final List> DEPENDENCIES = + List.of(ExtensionClientIntegration.class); + + private BrowserBasedAuthHandler authHandler; + + public ExtensionAuthhelperClient() { + super(NAME); + } + + @Override + public boolean supportsDb(String type) { + return true; + } + + @Override + public void hook(ExtensionHook extensionHook) { + super.hook(extensionHook); + authHandler = new BrowserBasedAuthHandler(); + getClientExtension().addAuthenticationHandler(authHandler); + } + + private static ExtensionClientIntegration getClientExtension() { + return Control.getSingleton() + .getExtensionLoader() + .getExtension(ExtensionClientIntegration.class); + } + + @Override + public boolean canUnload() { + return true; + } + + @Override + public void unload() { + getClientExtension().removeAuthenticationHandler(authHandler); + } + + @Override + public List> getDependencies() { + return DEPENDENCIES; + } + + @Override + public String getDescription() { + return Constant.messages.getString("authhelper.client.desc"); + } + + @Override + public String getUIName() { + return Constant.messages.getString("authhelper.client.name"); + } +} diff --git a/addOns/authhelper/src/main/resources/org/zaproxy/addon/authhelper/resources/Messages.properties b/addOns/authhelper/src/main/resources/org/zaproxy/addon/authhelper/resources/Messages.properties index 08de51cb040..e49a1235cd9 100644 --- a/addOns/authhelper/src/main/resources/org/zaproxy/addon/authhelper/resources/Messages.properties +++ b/addOns/authhelper/src/main/resources/org/zaproxy/addon/authhelper/resources/Messages.properties @@ -44,6 +44,9 @@ authhelper.auth.test.dialog.tab.test = Test authhelper.auth.test.dialog.title = Authentication Tester +authhelper.client.desc = Enables browser based authentication when performing an authenticated Client Spider scan. +authhelper.client.name = Client Spider Browser Based Authentication Support + authhelper.desc = Authentication Helper authhelper.name = Authentication Helper diff --git a/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/client/ExtensionAuthhelperClientUnitTest.java b/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/client/ExtensionAuthhelperClientUnitTest.java new file mode 100644 index 00000000000..7192f45797d --- /dev/null +++ b/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/client/ExtensionAuthhelperClientUnitTest.java @@ -0,0 +1,58 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2024 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.authhelper.client; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; +import org.zaproxy.addon.authhelper.ExtensionAuthhelper; +import org.zaproxy.zap.testutils.TestUtils; + +class ExtensionAuthhelperClientUnitTest extends TestUtils { + + @Test + void shouldHaveName() { + // Given + ExtensionAuthhelperClient cl = new ExtensionAuthhelperClient(); + mockMessages(new ExtensionAuthhelper()); + // When / Then + assertThat(cl.getName(), is(equalTo("ExtensionAuthhelperClient"))); + } + + @Test + void shouldHaveUiName() { + // Given + ExtensionAuthhelperClient cl = new ExtensionAuthhelperClient(); + mockMessages(new ExtensionAuthhelper()); + // When / Then + assertThat( + cl.getUIName(), is(equalTo("Client Spider Browser Based Authentication Support"))); + } + + @Test + void shouldBeUnloadable() { + // Given + ExtensionAuthhelperClient cl = new ExtensionAuthhelperClient(); + // When / Then + assertThat(cl.canUnload(), is(equalTo(true))); + } +} diff --git a/addOns/client/CHANGELOG.md b/addOns/client/CHANGELOG.md index 03c9515af8f..9859f631194 100644 --- a/addOns/client/CHANGELOG.md +++ b/addOns/client/CHANGELOG.md @@ -7,6 +7,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Update minimum ZAP version to 2.16.0. +### Added +- Added support for Browser Based Authentication when installed in conjunction with thee Auth Helper add-on. + ## [0.9.0] - 2024-11-29 ### Changed - Update minimum ZAP version to 2.15.0. diff --git a/addOns/client/src/main/java/org/zaproxy/addon/client/ExtensionClientIntegration.java b/addOns/client/src/main/java/org/zaproxy/addon/client/ExtensionClientIntegration.java index 31a4fab280a..147cf51db54 100644 --- a/addOns/client/src/main/java/org/zaproxy/addon/client/ExtensionClientIntegration.java +++ b/addOns/client/src/main/java/org/zaproxy/addon/client/ExtensionClientIntegration.java @@ -60,6 +60,7 @@ import org.zaproxy.addon.client.pscan.ClientPassiveScanController; import org.zaproxy.addon.client.pscan.ClientPassiveScanHelper; import org.zaproxy.addon.client.pscan.OptionsPassiveScan; +import org.zaproxy.addon.client.spider.AuthenticationHandler; import org.zaproxy.addon.client.spider.ClientSpider; import org.zaproxy.addon.client.spider.ClientSpiderDialog; import org.zaproxy.addon.client.spider.PopupMenuSpider; @@ -83,6 +84,7 @@ import org.zaproxy.zap.extension.selenium.ExtensionSelenium; import org.zaproxy.zap.extension.selenium.ProfileManager; import org.zaproxy.zap.model.ScanEventPublisher; +import org.zaproxy.zap.users.User; import org.zaproxy.zap.utils.DisplayUtils; import org.zaproxy.zap.view.ZapMenuItem; @@ -126,6 +128,8 @@ public class ExtensionClientIntegration extends ExtensionAdaptor { private ClientSpiderDialog spiderDialog; private ZapMenuItem menuItemCustomScan; + private List authHandlers = new ArrayList<>(); + public ExtensionClientIntegration() { super(NAME); } @@ -516,6 +520,18 @@ public String getDescription() { return Constant.messages.getString(PREFIX + ".desc"); } + public void addAuthenticationHandler(AuthenticationHandler handler) { + authHandlers.add(handler); + } + + public void removeAuthenticationHandler(AuthenticationHandler handler) { + authHandlers.remove(handler); + } + + public List getAuthenticationHandlers() { + return Collections.unmodifiableList(authHandlers); + } + private class SessionChangeListener implements SessionChangedListener { @Override @@ -574,7 +590,7 @@ protected static boolean isApiUrl(String url) { * @return an id which can be used to reference the specific scan. */ public int runSpider(String url) { - return this.runSpider(url, this.getClientParam()); + return this.runSpider(url, this.getClientParam(), null); } /** @@ -582,17 +598,29 @@ public int runSpider(String url) { * * @param url The inital URL to request * @param options Custom options. + * @param user the user to be used for authentication. * @return an id which can be used to reference the specific scan. */ - public int runSpider(String url, ClientOptions options) { + public int runSpider(String url, ClientOptions options, User user) { synchronized (spiders) { - ClientSpider cs = new ClientSpider(this, url, options, spiders.size()); + ClientSpider cs = new ClientSpider(this, url, options, spiders.size(), user); spiders.add(cs); cs.start(); return spiders.indexOf(cs); } } + /** + * Run the client spider with the specified options + * + * @param url The initial URL to request + * @param options Custom options. + * @return an id which can be used to reference the specific scan. + */ + public int runSpider(String url, ClientOptions options) { + return this.runSpider(url, options, null); + } + public ClientSpider getSpider(int id) { return this.spiders.get(id); } diff --git a/addOns/client/src/main/java/org/zaproxy/addon/client/spider/AuthenticationHandler.java b/addOns/client/src/main/java/org/zaproxy/addon/client/spider/AuthenticationHandler.java new file mode 100644 index 00000000000..080775da9db --- /dev/null +++ b/addOns/client/src/main/java/org/zaproxy/addon/client/spider/AuthenticationHandler.java @@ -0,0 +1,39 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2024 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.client.spider; + +import org.zaproxy.zap.users.User; + +public interface AuthenticationHandler { + + /** + * * Enables authentication handling for the given user, if the handler applies. + * + * @param user the user to authenticate for + */ + void enableAuthentication(User user); + + /** + * Disables authentication handling for the given user, if the handler applies. + * + * @param user the user + */ + void disableAuthentication(User user); +} diff --git a/addOns/client/src/main/java/org/zaproxy/addon/client/spider/ClientSpider.java b/addOns/client/src/main/java/org/zaproxy/addon/client/spider/ClientSpider.java index 8a0fc88e96d..c4f9439b5db 100644 --- a/addOns/client/src/main/java/org/zaproxy/addon/client/spider/ClientSpider.java +++ b/addOns/client/src/main/java/org/zaproxy/addon/client/spider/ClientSpider.java @@ -43,6 +43,7 @@ import org.zaproxy.zap.eventBus.Event; import org.zaproxy.zap.eventBus.EventConsumer; import org.zaproxy.zap.extension.selenium.ExtensionSelenium; +import org.zaproxy.zap.users.User; public class ClientSpider implements EventConsumer { @@ -74,6 +75,7 @@ public class ClientSpider implements EventConsumer { private ClientOptions options; private String targetUrl; + private User user; private ExtensionClientIntegration extClient; private ExtensionSelenium extSelenium; @@ -92,11 +94,21 @@ public class ClientSpider implements EventConsumer { private int tasksTotalCount; public ClientSpider( - ExtensionClientIntegration extClient, String targetUrl, ClientOptions options, int id) { + ExtensionClientIntegration extClient, + String targetUrl, + ClientOptions options, + int id, + User user) { this.extClient = extClient; this.targetUrl = targetUrl; this.options = options; this.id = id; + this.user = user; + if (user != null) { + this.extClient + .getAuthenticationHandlers() + .forEach(handler -> handler.enableAuthentication(user)); + } ZAP.getEventBus().registerConsumer(this, ClientMap.class.getCanonicalName()); extSelenium = @@ -299,6 +311,11 @@ private void finished() { long timeTaken = System.currentTimeMillis() - startTime; tempLogProgress( "Spider finished " + DurationFormatUtils.formatDuration(timeTaken, "HH:MM:SS")); + if (this.user != null) { + extClient + .getAuthenticationHandlers() + .forEach(handler -> handler.disableAuthentication(user)); + } synchronized (this.webDriverPool) { for (WebDriver wd : this.webDriverPool) { wd.quit(); diff --git a/addOns/client/src/main/java/org/zaproxy/addon/client/spider/ClientSpiderDialog.java b/addOns/client/src/main/java/org/zaproxy/addon/client/spider/ClientSpiderDialog.java index e758e044ac7..0a63ad78026 100644 --- a/addOns/client/src/main/java/org/zaproxy/addon/client/spider/ClientSpiderDialog.java +++ b/addOns/client/src/main/java/org/zaproxy/addon/client/spider/ClientSpiderDialog.java @@ -36,12 +36,17 @@ import org.apache.logging.log4j.Logger; import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; +import org.parosproxy.paros.model.Model; +import org.parosproxy.paros.model.Session; import org.parosproxy.paros.model.SiteNode; import org.parosproxy.paros.view.View; import org.zaproxy.addon.client.ClientOptions; import org.zaproxy.addon.client.ExtensionClientIntegration; import org.zaproxy.zap.extension.selenium.ExtensionSelenium; import org.zaproxy.zap.extension.selenium.ProvidedBrowserUI; +import org.zaproxy.zap.extension.users.ExtensionUserManagement; +import org.zaproxy.zap.model.Context; +import org.zaproxy.zap.users.User; import org.zaproxy.zap.utils.ZapTextField; import org.zaproxy.zap.view.LayoutHelper; import org.zaproxy.zap.view.NodeSelectDialog; @@ -55,6 +60,8 @@ public class ClientSpiderDialog extends StandardFieldsDialog { }; private static final String FIELD_START = "client.scandialog.label.start"; + private static final String FIELD_CONTEXT = "client.scandialog.label.context"; + private static final String FIELD_USER = "client.scandialog.label.user"; private static final String FIELD_SUBTREE_ONLY = "client.scandialog.label.spiderSubtreeOnly"; private static final String FIELD_BROWSER = "client.scandialog.label.browser"; private static final String FIELD_ADVANCED = "client.scandialog.label.adv"; @@ -80,10 +87,16 @@ public class ClientSpiderDialog extends StandardFieldsDialog { private ZapTextField urlStartField; private boolean subtreeOnlyPreviousCheckedState; + private ExtensionUserManagement extUserMgmt; + public ClientSpiderDialog(ExtensionClientIntegration ext, Frame owner, Dimension dim) { super(owner, "client.scandialog.title", dim, LABELS); this.extension = ext; params = this.extension.getClientParam(); + this.extUserMgmt = + Control.getSingleton() + .getExtensionLoader() + .getExtension(ExtensionUserManagement.class); } public void init(SiteNode node) { @@ -115,6 +128,18 @@ private void init(SiteNode node, String url) { this.addUrlSelectField(0, FIELD_START, url, true, false); } + this.addComboField(0, FIELD_CONTEXT, new String[] {}, ""); + List ctxNames = new ArrayList<>(); + ctxNames.add(""); + Session session = Model.getSingleton().getSession(); + session.getContexts().forEach(context -> ctxNames.add(context.getName())); + + this.setComboFields(FIELD_CONTEXT, ctxNames, ""); + this.addComboField(0, FIELD_USER, new String[] {}, ""); + if (extUserMgmt != null) { + this.addFieldListener(FIELD_CONTEXT, e -> setUsers()); + } + this.addCheckBoxField(0, FIELD_SUBTREE_ONLY, subtreeOnlyPreviousCheckedState); this.addComboField(0, FIELD_BROWSER, new ArrayList(), null); @@ -154,6 +179,44 @@ private void init(SiteNode node, String url) { this.updateBrowsers(); } + private Context getSelectedContext() { + String ctxName = this.getStringValue(FIELD_CONTEXT); + if (this.extUserMgmt != null && !this.isEmptyField(FIELD_CONTEXT)) { + Session session = Model.getSingleton().getSession(); + return session.getContext(ctxName); + } + return null; + } + + private User getSelectedUser() { + Context context = this.getSelectedContext(); + if (context != null && extUserMgmt != null) { + String userName = this.getStringValue(FIELD_USER); + List users = + this.extUserMgmt.getContextUserAuthManager(context.getId()).getUsers(); + for (User user : users) { + if (userName.equals(user.getName())) { + return user; + } + } + } + return null; + } + + private void setUsers() { + Context context = this.getSelectedContext(); + List userNames = new ArrayList<>(); + if (context != null && extUserMgmt != null) { + List users = extUserMgmt.getContextUserAuthManager(context.getId()).getUsers(); + userNames.add(""); + for (User user : users) { + userNames.add(user.getName()); + } + } + this.setComboFields(FIELD_USER, userNames, ""); + this.getField(FIELD_USER).setEnabled(userNames.size() > 1); + } + /* Tweaked version of super.addNodeSelectField to cope with a URL that might not be in the sites tree */ public void addUrlSelectField( int tabIndex, @@ -305,7 +368,8 @@ public void save() { subtreeOnlyPreviousCheckedState = getBoolValue(FIELD_SUBTREE_ONLY); - this.extension.runSpider(getStartUrl(), params); + User user = this.getSelectedUser(); + this.extension.runSpider(getStartUrl(), params, user); } /** diff --git a/addOns/client/src/main/resources/org/zaproxy/addon/client/resources/Messages.properties b/addOns/client/src/main/resources/org/zaproxy/addon/client/resources/Messages.properties index 64362fa65b7..61c827672ef 100644 --- a/addOns/client/src/main/resources/org/zaproxy/addon/client/resources/Messages.properties +++ b/addOns/client/src/main/resources/org/zaproxy/addon/client/resources/Messages.properties @@ -78,8 +78,10 @@ client.scandialog.button.reset = Reset client.scandialog.button.scan = Start Scan client.scandialog.label.adv = Show Advanced Options: client.scandialog.label.browser = Browser: +client.scandialog.label.context = Context: client.scandialog.label.spiderSubtreeOnly = Spider Subtree Only: client.scandialog.label.start = Starting Point: +client.scandialog.label.user = User: client.scandialog.nostart.error = You must select a valid starting point\nincluding the protocol e.g. https://www.example.com client.scandialog.notSafe.error = Client Spider scans are not allowed in 'Safe' mode. client.scandialog.startProtectedMode.error = The starting point is not in scope and the mode is 'Protected'.