Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use TOTP from credentials #6289

Merged
merged 1 commit into from
Mar 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion addOns/authhelper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ 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

### Changed
- Use TOTP data defined under user credentials for Client Script and Browser Based Authentication, when available.

## [0.24.0] - 2025-03-21
### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.util.List;
import java.util.Optional;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
Expand All @@ -46,6 +48,7 @@
import org.zaproxy.addon.authhelper.BrowserBasedAuthenticationMethodType.BrowserBasedAuthenticationMethod;
import org.zaproxy.addon.authhelper.internal.AuthenticationStep;
import org.zaproxy.addon.authhelper.internal.StepsPanel;
import org.zaproxy.addon.commonlib.internal.TotpSupport;
import org.zaproxy.addon.pscan.ExtensionPassiveScan2;
import org.zaproxy.zap.ZAP;
import org.zaproxy.zap.authentication.AuthenticationMethod;
Expand Down Expand Up @@ -297,7 +300,9 @@ private void authenticate() {
// Set up user
User user = new User(context.getId(), username);
UsernamePasswordAuthenticationCredentials upCreds =
new UsernamePasswordAuthenticationCredentials(username, password);
TotpSupport.createUsernamePasswordAuthenticationCredentials(
am, username, password);
setTotp(stepsPanel.getSteps(), upCreds);
user.setAuthenticationCredentials(upCreds);
user.setEnabled(true);
Control.getSingleton()
Expand Down Expand Up @@ -440,6 +445,30 @@ public void counterInc(String site, String key) {
}
}

private void setTotp(
List<AuthenticationStep> steps, UsernamePasswordAuthenticationCredentials credentials) {
if (!TotpSupport.isTotpInCore()) {
return;
}

Optional<AuthenticationStep> optStep =
steps.stream()
.filter(e -> e.getType() == AuthenticationStep.Type.TOTP_FIELD)
.findFirst();
if (optStep.isEmpty()) {
return;
}

AuthenticationStep totpStep = optStep.get();
TotpSupport.TotpData totpData =
new TotpSupport.TotpData(
totpStep.getTotpSecret(),
totpStep.getTotpPeriod(),
totpStep.getTotpDigits(),
totpStep.getTotpAlgorithm());
TotpSupport.setTotpData(totpData, credentials);
}

private void reloadAuthenticationMethod(AuthenticationMethod am) throws ConfigurationException {
// OK, this does look weird, but it is the easiest way to actually get
// the session management data loaded :/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,16 +397,14 @@ static boolean authenticateAsUserImpl(
List<AuthenticationStep> steps) {

UsernamePasswordAuthenticationCredentials credentials = getCredentials(user);
String username = credentials.getUsername();
String password = credentials.getPassword();

Context context = user.getContext();

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

if (auth) {
return true;
Expand All @@ -430,14 +428,7 @@ static boolean authenticateAsUserImpl(
sleep(AUTH_PAGE_SLEEP_IN_MSECS);
auth =
internalAuthenticateAsUser(
diags,
wd,
context,
loginPageUrl,
username,
password,
waitInSecs,
steps);
diags, wd, context, loginPageUrl, credentials, waitInSecs, steps);
if (auth) {
return true;
}
Expand All @@ -460,8 +451,7 @@ private static boolean internalAuthenticateAsUser(
WebDriver wd,
Context context,
String loginPageUrl,
String username,
String password,
UsernamePasswordAuthenticationCredentials credentials,
int waitInSecs,
List<AuthenticationStep> steps) {

Expand All @@ -472,6 +462,9 @@ private static boolean internalAuthenticateAsUser(
sleep(DEMO_SLEEP_IN_MSECS);
}

String username = credentials.getUsername();
String password = credentials.getPassword();

WebElement userField = null;
WebElement pwdField = null;
boolean userAdded = false;
Expand All @@ -488,7 +481,7 @@ private static boolean internalAuthenticateAsUser(
break;
}

WebElement element = step.execute(wd, username, password);
WebElement element = step.execute(wd, credentials);
diags.recordStep(wd, step.getDescription(), element);

switch (step.getType()) {
Expand Down Expand Up @@ -552,7 +545,7 @@ private static boolean internalAuthenticateAsUser(
continue;
}

step.execute(wd, username, password);
step.execute(wd, credentials);
diags.recordStep(wd, step.getDescription());

sleep(demoMode ? DEMO_SLEEP_IN_MSECS : TIME_TO_SLEEP_IN_MSECS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public void importData(Configuration config, SessionManagementMethod sessionMeth

@Override
public ApiDynamicActionImplementor getSetMethodForContextApiAction() {
return new ApiDynamicActionImplementor(API_METHOD_NAME, null, null) {
return new ApiDynamicActionImplementor(API_METHOD_NAME, (String[]) null, (String[]) null) {

@Override
public void handleAction(JSONObject params) throws ApiException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import org.zaproxy.addon.authhelper.internal.AuthenticationStep.ValidationResult;
import org.zaproxy.addon.authhelper.internal.ClientSideHandler;
import org.zaproxy.addon.authhelper.internal.StepsPanel;
import org.zaproxy.addon.commonlib.internal.TotpSupport;
import org.zaproxy.addon.network.ExtensionNetwork;
import org.zaproxy.addon.network.internal.client.apachev5.HttpSenderContextApache;
import org.zaproxy.addon.network.server.HttpServerConfig;
Expand Down Expand Up @@ -224,7 +225,7 @@ protected AuthenticationMethod duplicate() {

@Override
public AuthenticationCredentials createAuthenticationCredentials() {
return new UsernamePasswordAuthenticationCredentials();
return TotpSupport.createUsernamePasswordAuthenticationCredentials();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.parosproxy.paros.network.HttpSender;
import org.parosproxy.paros.view.View;
import org.zaproxy.addon.authhelper.internal.ClientSideHandler;
import org.zaproxy.addon.commonlib.internal.TotpSupport;
import org.zaproxy.addon.network.server.HttpMessageHandler;
import org.zaproxy.zap.authentication.AbstractAuthenticationMethodOptionsPanel;
import org.zaproxy.zap.authentication.AuthenticationCredentials;
Expand Down Expand Up @@ -246,7 +247,7 @@ public boolean validateCreationOfAuthenticationCredentials() {

@Override
public AuthenticationCredentials createAuthenticationCredentials() {
return new GenericAuthenticationCredentials(this.credentialsParamNames);
return TotpSupport.createGenericAuthenticationCredentials(credentialsParamNames);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,19 @@
*/
package org.zaproxy.addon.authhelper;

import com.bastiaanjansen.otp.HMACAlgorithm;
import com.bastiaanjansen.otp.TOTPGenerator;
import java.awt.EventQueue;
import java.awt.event.KeyEvent;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import javax.swing.ImageIcon;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -47,6 +52,9 @@
import org.parosproxy.paros.network.HttpMessage;
import org.parosproxy.paros.view.View;
import org.zaproxy.addon.authhelper.internal.db.TableJdo;
import org.zaproxy.addon.commonlib.internal.TotpSupport;
import org.zaproxy.addon.commonlib.internal.TotpSupport.TotpData;
import org.zaproxy.addon.commonlib.internal.TotpSupport.TotpGenerator;
import org.zaproxy.addon.pscan.ExtensionPassiveScan2;
import org.zaproxy.zap.authentication.FormBasedAuthenticationMethodType;
import org.zaproxy.zap.authentication.JsonBasedAuthenticationMethodType;
Expand Down Expand Up @@ -113,6 +121,35 @@ public List<Class<? extends Extension>> getDependencies() {
return EXTENSION_DEPENDENCIES;
}

@Override
public void init() {
List<String> supportedAlgorithms =
Stream.of(HMACAlgorithm.values()).map(HMACAlgorithm::name).toList();

TotpSupport.setTotpGenerator(
new TotpGenerator() {

@Override
public List<String> getSupportedAlgorithms() {
return supportedAlgorithms;
}

@Override
public String generate(TotpData data, Instant when) {
return new TOTPGenerator.Builder(data.secret())
.withHOTPGenerator(
builder ->
builder.withAlgorithm(
HMACAlgorithm.valueOf(
data.algorithm()))
.withPasswordLength(data.digits()))
.withPeriod(Duration.ofSeconds(data.period()))
.build()
.at(when);
}
});
}

public AuthhelperParam getParam() {
if (param == null) {
param = new AuthhelperParam();
Expand Down Expand Up @@ -149,6 +186,8 @@ public void unload() {
AuthUtils.disableBrowserAuthentication();
BrowserBasedAuthenticationMethodType.stopProxies();
AuthUtils.clean();

TotpSupport.setTotpGenerator(null);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.parosproxy.paros.Constant;
import org.zaproxy.addon.commonlib.internal.TotpSupport;
import org.zaproxy.zap.authentication.UsernamePasswordAuthenticationCredentials;
import org.zaproxy.zap.utils.EnableableInterface;
import org.zaproxy.zap.utils.Orderable;

Expand Down Expand Up @@ -177,7 +179,7 @@ public void setOrder(int order) {
this.order = order;
}

public WebElement execute(WebDriver wd, String username, String password) {
public WebElement execute(WebDriver wd, UsernamePasswordAuthenticationCredentials credentials) {
By by = createtBy();

WebElement element =
Expand All @@ -198,19 +200,19 @@ public WebElement execute(WebDriver wd, String username, String password) {
break;

case PASSWORD:
element.sendKeys(password);
element.sendKeys(credentials.getPassword());
break;

case RETURN:
element.sendKeys(Keys.RETURN);
break;

case TOTP_FIELD:
element.sendKeys(getTotpCode());
element.sendKeys(getTotpCode(credentials));
break;

case USERNAME:
element.sendKeys(username);
element.sendKeys(credentials.getUsername());
break;

default:
Expand All @@ -220,7 +222,13 @@ public WebElement execute(WebDriver wd, String username, String password) {
return element;
}

private CharSequence getTotpCode() {
private CharSequence getTotpCode(UsernamePasswordAuthenticationCredentials credentials) {
CharSequence code = TotpSupport.getCode(credentials);
if (code != null) {
return code;
}

// Fallback to data from the step for now.
return new TOTPGenerator.Builder(totpSecret)
.withHOTPGenerator(
builder ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import org.parosproxy.paros.Constant;
import org.zaproxy.addon.commonlib.internal.TotpSupport;
import org.zaproxy.zap.utils.ZapNumberSpinner;
import org.zaproxy.zap.utils.ZapTextField;
import org.zaproxy.zap.view.AbstractFormDialog;
Expand Down Expand Up @@ -177,6 +178,17 @@ protected JPanel getFieldsPanel() {
}

public void setEnableable(boolean enableable) {
if (TotpSupport.isTotpInCore()) {
totpSecretLabel.setVisible(enableable);
getTotpSecretTextField().setVisible(enableable);
totpPeriodLabel.setVisible(enableable);
getTotpPeriodNumberSpinner().setVisible(enableable);
totpDigitsLabel.setVisible(enableable);
getTotpDigitsNumberSpinner().setVisible(enableable);
totpAlgorithmLabel.setVisible(enableable);
getTotpAlgorithmComboBox().setVisible(enableable);
}

getEnabledLabel().setVisible(enableable);
getEnabledCheckBox().setVisible(enableable);
if (!enableable) {
Expand Down Expand Up @@ -278,6 +290,10 @@ private AuthenticationStep createStep() {
newStep.setValue(getValueTextField().getText());
newStep.setTimeout(getTimeoutNumberSpinner().getValue());

if (TotpSupport.isTotpInCore() && !getTotpSecretTextField().isVisible()) {
// Secret is read from the user credentials.
getTotpSecretTextField().setText("UserCredentials");
}
newStep.setTotpSecret(getTotpSecretTextField().getText());
newStep.setTotpPeriod(getTotpPeriodNumberSpinner().getValue());
newStep.setTotpDigits(getTotpDigitsNumberSpinner().getValue());
Expand Down
1 change: 1 addition & 0 deletions addOns/automation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
### Added
- Document how the TOTP data is defined for a user.
- Use TOTP data defined under user credentials when creating or setting up a context.

### Changed
- Progress and log messages with regard to setting scan rule threshold or strength no longer include commas in scan rule ID numbers.
Expand Down
Loading