Skip to content

Commit

Permalink
authhelper: try login links if authentication fails
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Bennetts <[email protected]>
  • Loading branch information
psiinon committed Feb 20, 2025
1 parent 1f966a6 commit c6dc362
Show file tree
Hide file tree
Showing 5 changed files with 383 additions and 4 deletions.
3 changes: 3 additions & 0 deletions addOns/authhelper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this add-on will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased
### Added
- If authentication fails then try to find a likely looking login link

### Changed
- Prefer form related fields in Browser Based Authentication for the selection of username field.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,33 @@ public class AuthUtils {

public static final String[] HEADERS = {HttpHeader.AUTHORIZATION};
public static final String[] JSON_IDS = {"accesstoken", "token"};
private static final String[] USERNAME_FIELD_INDICATORS = {
"email", "signinname", "uname", "user"
};
private static final List<String> USERNAME_FIELD_INDICATORS =
List.of("email", "signinname", "uname", "user", "name", "nome", "nombre");
/* A selection of the most common login label links - just European languages for now - more suggestions appreciated */
protected static List<String> LOGIN_LABELS_P1 =
List.of(
"login",
"log in",
"log-in",
"signin",
"sign in",
"sign-in",
"iniciar sesión", // Spanish: login
"acceder", // Spanish: sign in
"connexion", // French: login
"se connecter", // French: sign in
"anmeldung", // German: login
"einloggen", // German: sign in
"accesso", // Italian: login
"accedi", // Italian: sign in
"entrar", // Portuguese: sign in (login is login;)
"inloggen", // Dutch: login
"aanmelden" // Dutch: sign in
);

/* Less likely labels, but still worth trying */
protected static List<String> LOGIN_LABELS_P2 =
List.of("account", "signup", "sign up", "sign-up");

private static final int MIN_SESSION_COOKIE_LENGTH = 10;

Expand Down Expand Up @@ -253,7 +277,7 @@ private static Stream<WebElement> displayed(List<WebElement> elements) {
return elements.stream().filter(WebElement::isDisplayed);
}

static boolean attributeContains(WebElement we, String attribute, String[] strings) {
static boolean attributeContains(WebElement we, String attribute, List<String> strings) {
String att = getAttribute(we, attribute);
if (att == null) {
return false;
Expand Down Expand Up @@ -292,7 +316,58 @@ public static boolean authenticateAsUser(
String password,
int waitInSecs,
List<AuthenticationStep> steps) {

// Try with the given URL
wd.get(loginPageUrl);
boolean auth =
internalAuthenticateAsUser(
wd, context, loginPageUrl, username, password, waitInSecs, steps);

if (auth) {
return true;
}

// Try to find a login link - best set
wd.get(loginPageUrl);
List<WebElement> links = LoginLinkDetector.getLoginLinks(wd, LOGIN_LABELS_P1);
if (!links.isEmpty()) {
// Only try the first as we're only likely to get 1, and once we follow that then
// subsequent links will be invalid. This may change based on real world feedback of
// course.
links.get(0).click();
auth =
internalAuthenticateAsUser(
wd, context, loginPageUrl, username, password, waitInSecs, steps);
if (auth) {
return true;
}
}

// Trying second best set
wd.get(loginPageUrl);
links = LoginLinkDetector.getLoginLinks(wd, LOGIN_LABELS_P2);
if (!links.isEmpty()) {
links.get(0).click();
auth =
internalAuthenticateAsUser(
wd, context, loginPageUrl, username, password, waitInSecs, steps);
if (auth) {
return true;
}
}

return false;
}

private static boolean internalAuthenticateAsUser(
WebDriver wd,
Context context,
String loginPageUrl,
String username,
String password,
int waitInSecs,
List<AuthenticationStep> steps) {

sleep(50);
if (demoMode) {
sleep(2000);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.authhelper;

import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

public class LoginLinkDetector {

public static List<WebElement> getLoginLinks(WebDriver wd, List<String> loginLabels) {
// Start with all links
List<WebElement> loginLinks =
wd.findElements(By.tagName("a")).stream()
.filter(l -> elementContainsText(l, loginLabels))
.collect(Collectors.toList());
if (!loginLinks.isEmpty()) {
return loginLinks;
}
// then try buttons
return wd.findElements(By.tagName("button")).stream()
.filter(l -> elementContainsText(l, loginLabels))
.collect(Collectors.toList());
}

private static boolean elementContainsText(WebElement element, List<String> searchTexts) {
String txt = element.getText().toLowerCase(Locale.ROOT);
return searchTexts.stream().filter(s -> txt.contains(s)).findAny().isPresent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public void scanHttpResponseReceive(HttpMessage msg, int id, Source source) {
|| msg.getResponseHeader().isJson()
|| msg.getResponseHeader().isXml())) {
// An "image/svg+xml" will look like XML
// TODO ignore CSS, fonts ...
return;
}

Expand Down
Loading

0 comments on commit c6dc362

Please sign in to comment.