Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions addOns/ascanrulesBeta/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Maintenance changes.
- The scan rules now have new tags for the OWASP Top 10 2025, and API Top 10 2023.
- Depends on an updated version of the Common Library add-on.
- The Possible Username Enumeration scan rule now includes example alert functionality for documentation generation purposes (Issue 6119).

## [64] - 2025-12-15
### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
*/
package org.zaproxy.zap.extension.ascanrulesBeta;

import difflib.ChangeDelta;
import difflib.Chunk;
import difflib.Delta;
import difflib.DiffUtils;
import difflib.Patch;
Expand Down Expand Up @@ -656,58 +658,7 @@ public void scan() {
Arrays.asList(
longestCommonSubstringB.split("\\n"))));

int numberofDifferences = diffpatch.getDeltas().size();

StringBuilder tempDiff = new StringBuilder(250);
for (Delta<String> delta : diffpatch.getDeltas()) {
String changeType = null;
if (delta.getType() == Delta.TYPE.CHANGE) changeType = "Changed Text";
else if (delta.getType() == Delta.TYPE.DELETE)
changeType = "Deleted Text";
else if (delta.getType() == Delta.TYPE.INSERT)
changeType = "Inserted text";
else changeType = "Unknown change type [" + delta.getType() + "]";

tempDiff.append("\n(" + changeType + ")\n"); // blank line before
tempDiff.append(
"Output for Valid Username : "
+ delta.getOriginal()
+ "\n"); // no blank lines
tempDiff.append(
"\nOutput for Invalid Username: "
+ delta.getRevised()
+ "\n"); // blank line before
}
String diffAB = tempDiff.toString();
String extraInfo =
Constant.messages.getString(
"ascanbeta.usernameenumeration.alert.extrainfo",
currentHtmlParameter.getType(),
currentHtmlParameter.getName(),
currentHtmlParameter.getValue(), // original value
invalidUsername, // new value
diffAB, // the differences between the two sets of output
numberofDifferences);
String attack =
Constant.messages.getString(
"ascanbeta.usernameenumeration.alert.attack",
currentHtmlParameter.getType(),
currentHtmlParameter.getName());
String vulnname =
Constant.messages.getString("ascanbeta.usernameenumeration.name");
String vulndesc =
Constant.messages.getString("ascanbeta.usernameenumeration.desc");
String vulnsoln =
Constant.messages.getString("ascanbeta.usernameenumeration.soln");

newAlert()
.setConfidence(Alert.CONFIDENCE_LOW)
.setName(vulnname)
.setDescription(vulndesc)
.setParam(currentHtmlParameter.getName())
.setAttack(attack)
.setOtherInfo(extraInfo)
.setSolution(vulnsoln)
buildAlert(currentHtmlParameter, invalidUsername, diffpatch.getDeltas())
.setMessage(getBaseMsg())
.raise();

Expand Down Expand Up @@ -770,4 +721,52 @@ public int getWascId() {
public Map<String, String> getAlertTags() {
return ALERT_TAGS;
}

private AlertBuilder buildAlert(
HtmlParameter param, String invalidValue, List<Delta<String>> deltas) {
StringBuilder diffText = new StringBuilder(250);
for (Delta<String> delta : deltas) {
String changeType;
if (delta.getType() == Delta.TYPE.CHANGE) changeType = "Changed Text";
else if (delta.getType() == Delta.TYPE.DELETE) changeType = "Deleted Text";
else if (delta.getType() == Delta.TYPE.INSERT) changeType = "Inserted text";
else changeType = "Unknown change type [" + delta.getType() + "]";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is syntactically correct but using braces for consistency/readability would be best.

This whole loop and the text it's building should all be internationalized (ex: Use the message.properties key/values/substitution)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, added braces and moved all the diff text into Messages.properties keys.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record this was existing code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, that's on me.

@thc202 are you okay with it going forward this way?

diffText.append("\n(" + changeType + ")\n");
diffText.append("Output for Valid Username : " + delta.getOriginal() + "\n");
diffText.append("\nOutput for Invalid Username: " + delta.getRevised() + "\n");
}
return newAlert()
.setConfidence(Alert.CONFIDENCE_LOW)
.setParam(param.getName())
.setAttack(
Constant.messages.getString(
"ascanbeta.usernameenumeration.alert.attack",
param.getType(),
param.getName()))
.setOtherInfo(
Constant.messages.getString(
"ascanbeta.usernameenumeration.alert.extrainfo",
param.getType(),
param.getName(),
param.getValue(),
invalidValue,
diffText.toString(),
deltas.size()));
}

@Override
public List<Alert> getExampleAlerts() {
return List.of(
buildAlert(
new HtmlParameter(HtmlParameter.Type.form, "username", "admin"),
"invaliduser123",
List.of(
new ChangeDelta<>(
new Chunk<>(0, List.of("Welcome, admin")),
new Chunk<>(
0,
List.of("Invalid username or password")))))
.build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.mockito.MockSettings;
import org.mockito.quality.Strictness;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.core.scanner.Plugin.AttackStrength;
import org.parosproxy.paros.extension.ExtensionLoader;
import org.parosproxy.paros.model.Model;
Expand Down Expand Up @@ -165,6 +166,17 @@ void shouldScanMessageWithoutPath() throws Exception {
assertThat(alertsRaised, hasSize(0));
}

@Test
void shouldHaveExpectedExampleAlerts() {
// Given / When
List<Alert> alerts = rule.getExampleAlerts();
// Then
assertThat(alerts, hasSize(1));
Alert alert = alerts.get(0);
assertThat(alert.getRisk(), is(equalTo(Alert.RISK_INFO)));
assertThat(alert.getConfidence(), is(equalTo(Alert.CONFIDENCE_LOW)));
}

private Context contextWithLoginUrl(String path) throws HttpMalformedHeaderException {
Context context = mock(Context.class);
FormBasedAuthenticationMethod authMethod = mock(FormBasedAuthenticationMethod.class);
Expand Down
Loading