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

[WIP] Totp Active Scan Rules #6255

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions addOns/ascanrulesAlpha/ascanrulesAlpha.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ zapAddOn {
register("commonlib") {
version.set(">= 1.22.0 & < 2.0.0")
}
register("authhelper") {
version.set("0.23.0")
}
}
}
}
Expand All @@ -23,6 +26,7 @@ tasks.named("compileJava") {

dependencies {
zapAddOn("commonlib")
zapAddOn("authhelper")

testImplementation(project(":testutils"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package org.zaproxy.zap.extension.ascanrulesAlpha;

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.control.Control;
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.model.Model;
import org.parosproxy.paros.model.Session;
import org.zaproxy.zap.model.Context;
import org.zaproxy.zap.session.SessionManagementMethod;
import org.zaproxy.zap.session.WebSession;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.zap.users.User;
import org.zaproxy.zap.authentication.AuthenticationMethod;
import org.zaproxy.zap.authentication.UsernamePasswordAuthenticationCredentials;
import org.zaproxy.zap.extension.users.ExtensionUserManagement;
import org.zaproxy.addon.authhelper.BrowserBasedAuthenticationMethodType.BrowserBasedAuthenticationMethod;
import org.zaproxy.addon.authhelper.internal.AuthenticationStep;


public class BlankTotpActiveScanRule extends AbstractHostPlugin implements CommonActiveScanRuleInfo{
private static final Logger LOGGER = LogManager.getLogger(BlankTotpActiveScanRule.class);
private static final Map<String, String> ALERT_TAGS = new HashMap<>();

@Override
public int getId() {
return 40048;
}
@Override
public String getName() {
return "Blank code TOTP Scan Rule";
}
@Override
public String getDescription() {
return "TOTP Page found";
}
@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 {
ExtensionUserManagement usersExtension =
Control.getSingleton()
.getExtensionLoader()
.getExtension(ExtensionUserManagement.class);

// Get target URL from request
HttpMessage msg = getBaseMsg();
String targetUrl = msg.getRequestHeader().getURI().toString();

// Find session context that matches the target URL
Context activeContext = null;
Session session = Model.getSingleton().getSession();
for (Context context : session.getContexts()) {
if (context.isInContext(targetUrl)) {
activeContext = context;
break;
}
}
BrowserBasedAuthenticationMethod browserAuthMethod = null;
List<AuthenticationStep> authSteps = null;
AuthenticationStep totpStep = null;
// Check if the context is found
if (activeContext != null) {
AuthenticationMethod authMethod = activeContext.getAuthenticationMethod();
// Check if the authentication method is browser based
if (authMethod instanceof BrowserBasedAuthenticationMethod) {
browserAuthMethod = (BrowserBasedAuthenticationMethod) authMethod;
// Check if the authentication method has TOTP step
authSteps = browserAuthMethod.getAuthenticationSteps();
boolean totpFound = false;
for (AuthenticationStep step : authSteps) {
// Checks for TOTP_field type step or currently also allows for
// custom field b/c of the way TOTP_field step currently implemented
if (step.getType() == AuthenticationStep.Type.TOTP_FIELD || (step.getType() == AuthenticationStep.Type.CUSTOM_FIELD && step.getDescription().toLowerCase().contains("totp"))) {
totpFound = true;
totpStep = step;
break;
}
}
if (!totpFound) {
return;
}

}
else{
//LOGGER.error("Authentication Method is not browser based.");
return;
}
}
else {
//LOGGER.error("No context found for target URL: " + targetUrl);
return;
}

//Start vulnerability testing if TOTP step is found
//LOGGER.error("TOTP authentication is enabled, proceeding with tests.");

// Get user credentials(username,password) & user from the context to run browser based web session
List<User> users = null;
if (usersExtension == null) {
//LOGGER.error("Users extension not found.");
return;
}
users = usersExtension.getContextUserAuthManager(activeContext.getId()).getUsers();
if (users == null || users.isEmpty()) {
//LOGGER.error("No users found in the context.");
return;
}
User user = users.get(0);
UsernamePasswordAuthenticationCredentials credentials = (UsernamePasswordAuthenticationCredentials) user.getAuthenticationCredentials();
SessionManagementMethod sessionManagementMethod = activeContext.getSessionManagementMethod();

//Check if user provided valid code & check if initial authentication works with normal passcode
if(totpStep.getValue() != null || !totpStep.getValue().isEmpty()){
WebSession webSession = browserAuthMethod.authenticate(sessionManagementMethod, credentials, user);
if (webSession == null) {
//LOGGER.error("Normal Authentication unsuccessful. TOTP not configured correctly.");
return;
}
}

// Check for blank passcode vulnerability
WebSession webSessionBlankCode = testAuthenticatSession(totpStep, "", authSteps, browserAuthMethod, sessionManagementMethod, credentials, user);
if (webSessionBlankCode != null) {
//LOGGER.error("Authentication successful with blank passcode.Vulernaibility found.");
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 WebSession testAuthenticatSession(AuthenticationStep totpStep, String newTotpValue, List<AuthenticationStep> authSteps , BrowserBasedAuthenticationMethod browserAuthMethod, SessionManagementMethod sessionManagementMethod, UsernamePasswordAuthenticationCredentials credentials, User user){
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_HIGH)
.setName(name)
.setDescription(description)
.setSolution(solution)
.setMessage(msg);
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package org.zaproxy.zap.extension.ascanrulesAlpha;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.control.Control;
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.model.Model;
import org.parosproxy.paros.model.Session;
import org.zaproxy.zap.model.Context;
import org.zaproxy.zap.session.SessionManagementMethod;
import org.zaproxy.zap.session.WebSession;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.zap.users.User;
import org.zaproxy.zap.authentication.AuthenticationMethod;
import org.zaproxy.zap.authentication.UsernamePasswordAuthenticationCredentials;
import org.zaproxy.zap.extension.users.ExtensionUserManagement;
import org.zaproxy.addon.authhelper.BrowserBasedAuthenticationMethodType.BrowserBasedAuthenticationMethod;
import org.zaproxy.addon.authhelper.internal.AuthenticationStep;
public class CaptchaTotpActiveScanRule extends AbstractHostPlugin implements CommonActiveScanRuleInfo{
private static final Logger LOGGER = LogManager.getLogger(BlankTotpActiveScanRule.class);
private static final Map<String, String> ALERT_TAGS = new HashMap<>();

@Override
public int getId() {
return 40051;
}
@Override
public String getName() {
return "Blank code TOTP Scan Rule";
}
@Override
public String getDescription() {
return "TOTP Page found";
}
@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 {
ExtensionUserManagement usersExtension =
Control.getSingleton()
.getExtensionLoader()
.getExtension(ExtensionUserManagement.class);

// Get target URL from request
HttpMessage msg = getBaseMsg();
String targetUrl = msg.getRequestHeader().getURI().toString();

// Find session context that matches the target URL
Context activeContext = null;
Session session = Model.getSingleton().getSession();
for (Context context : session.getContexts()) {
if (context.isInContext(targetUrl)) {
activeContext = context;
break;
}
}
BrowserBasedAuthenticationMethod browserAuthMethod = null;
List<AuthenticationStep> authSteps = null;
AuthenticationStep totpStep = null;
// Check if the context is found
if (activeContext != null) {
AuthenticationMethod authMethod = activeContext.getAuthenticationMethod();
// Check if the authentication method is browser based
if (authMethod instanceof BrowserBasedAuthenticationMethod) {
browserAuthMethod = (BrowserBasedAuthenticationMethod) authMethod;
// Check if the authentication method has TOTP step
authSteps = browserAuthMethod.getAuthenticationSteps();
boolean totpFound = false;
for (AuthenticationStep step : authSteps) {
// Checks for TOTP_field type step or currently also allows for
// custom field b/c of the way TOTP_field step currently implemented
if (step.getType() == AuthenticationStep.Type.TOTP_FIELD || (step.getType() == AuthenticationStep.Type.CUSTOM_FIELD && step.getDescription().toLowerCase().contains("totp"))) {
totpFound = true;
totpStep = step;
break;
}
}
if (!totpFound) {
return;
}

}
else{
//LOGGER.error("Authentication Method is not browser based.");
return;
}
}
else {
//LOGGER.error("No context found for target URL: " + targetUrl);
return;
}

//Start vulnerability testing if TOTP step is found
//LOGGER.error("TOTP authentication is enabled, proceeding with tests.");

// Get user credentials(username,password) & user from the context to run browser based web session
List<User> users = null;
if (usersExtension == null) {
//LOGGER.error("Users extension not found.");
return;
}
users = usersExtension.getContextUserAuthManager(activeContext.getId()).getUsers();
if (users == null || users.isEmpty()) {
//LOGGER.error("No users found in the context.");
return;
}
User user = users.get(0);
UsernamePasswordAuthenticationCredentials credentials = (UsernamePasswordAuthenticationCredentials) user.getAuthenticationCredentials();
SessionManagementMethod sessionManagementMethod = activeContext.getSessionManagementMethod();

//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<HttpMessage> httpResponses = new ArrayList<>();
for (int i = 0; i < 10; i++) {
WebSession testSession = testAuthenticatSession(totpStep, "111111", authSteps, browserAuthMethod, sessionManagementMethod, credentials, user);
//Add the response to the httpResponses list

}
for (HttpMessage response : httpResponses) {
// Check for changes to the response's indicating a potential lockout/captcha mechanism
}
for (HttpMessage response : httpResponses) {
// Check for captcha mechanism
// Check for lockout mechanism
}
if (!captchaDetected && !lockoutDetected) {
//LOGGER.error("Authentication successful with blank passcode.Vulernaibility found.");
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<AuthenticationStep> authSteps , BrowserBasedAuthenticationMethod browserAuthMethod, SessionManagementMethod sessionManagementMethod, UsernamePasswordAuthenticationCredentials credentials, User user){
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);
}
}




Loading
Loading