Skip to content
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
16 changes: 16 additions & 0 deletions zap/src/main/java/org/parosproxy/paros/core/scanner/Alert.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
// ZAP: 2023/09/12 Add NUMBER_RISKS convenience constant.
// ZAP: 2023/11/14 When setting CWE also add a CWE alert tag with an appropriate URL.
// ZAP: 2025/10/01 Added support for nodeName.
// ZAP: 2025/10/16 Added support for systemic alerts.
package org.parosproxy.paros.core.scanner;

import java.net.URL;
Expand Down Expand Up @@ -199,6 +200,8 @@ public static Source getSource(int id) {
private static final String CWE_KEY = "CWE-";
private static final String CWE_URL_BASE = "https://cwe.mitre.org/data/definitions/";

private static final String SYSTEMIC_KEY = "SYSTEMIC";

private int alertId = -1; // ZAP: Changed default alertId
private int historyId;
private int pluginId = -1;
Expand Down Expand Up @@ -231,6 +234,7 @@ public static Source getSource(int id) {
private String alertRef = "";
private String nodeName;
private Map<String, String> tags = Collections.emptyMap();
private Boolean systemic;

public Alert(int pluginId) {
this.pluginId = pluginId;
Expand Down Expand Up @@ -1090,6 +1094,18 @@ public void setNodeName(String nodeName) {
this.nodeName = nodeName;
}

/**
* Returns true if the alert has the "SYSTEMIC" tag
*
* @since 2.17.0
*/
public boolean isSystemic() {
if (systemic == null) {
systemic = this.getTags().containsKey(SYSTEMIC_KEY);
}
return systemic;
}

/**
* Returns a new alert builder.
*
Expand Down
12 changes: 9 additions & 3 deletions zap/src/main/java/org/zaproxy/zap/extension/alert/AlertNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class AlertNode extends DefaultMutableTreeNode {
private String nodeName = null;
private int risk = -1;
private Alert alert;
private boolean systemic;

public AlertNode(int risk, String nodeName) {
this(risk, nodeName, null);
Expand Down Expand Up @@ -143,9 +144,6 @@ public int findIndex(AlertNode aChild) {

@Override
public String toString() {
if (this.getChildCount() > 1) {
return nodeName + " (" + this.getChildCount() + ")";
}
return nodeName;
}

Expand All @@ -161,6 +159,14 @@ public int getRisk() {
return risk;
}

public boolean isSystemic() {
return systemic;
}

public void setSystemic(boolean systemic) {
this.systemic = systemic;
}

private static class AlertNodeComparatorWrapper implements Comparator<TreeNode> {

private final Comparator<AlertNode> comparator;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ public void setLinkWithSitesTreeSelection(boolean enabled) {
*/
private AlertTreeModel getLinkWithSitesTreeModel() {
if (linkWithSitesTreeModel == null) {
linkWithSitesTreeModel = new AlertTreeModel();
linkWithSitesTreeModel = new AlertTreeModel(this.extension);
}
return linkWithSitesTreeModel;
}
Expand Down
21 changes: 21 additions & 0 deletions zap/src/main/java/org/zaproxy/zap/extension/alert/AlertParam.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,21 @@ public class AlertParam extends AbstractParam {

private static final String PARAM_OVERRIDES_FILENAME = PARAM_BASE_KEY + ".overridesFilename";

private static final String PARAM_SYSTEMIC_LIMIT = PARAM_BASE_KEY + ".systemicLimit";

private static final int DEFAULT_MAXIMUM_INSTANCES = 20;

private static final int DEFAULT_SYSTEMIC_LIMIT = 0;

/**
* The number of maximum instances of each vulnerability included in a report.
*
* <p>Default is {@value #DEFAULT_MAXIMUM_INSTANCES}.
*/
private int maximumInstances = DEFAULT_MAXIMUM_INSTANCES;

private int systemicLimit = DEFAULT_SYSTEMIC_LIMIT;

private boolean mergeRelatedIssues = true;

private String overridesFilename;
Expand All @@ -58,6 +64,7 @@ public class AlertParam extends AbstractParam {
@Override
protected void parse() {
maximumInstances = getInt(PARAM_MAXIMUM_INSTANCES, DEFAULT_MAXIMUM_INSTANCES);
systemicLimit = getInt(PARAM_SYSTEMIC_LIMIT, DEFAULT_SYSTEMIC_LIMIT);
mergeRelatedIssues = getBoolean(PARAM_MERGE_RELATED_ISSUES, true);
overridesFilename = getString(PARAM_OVERRIDES_FILENAME, "");
}
Expand Down Expand Up @@ -86,6 +93,20 @@ public int getMaximumInstances() {
return maximumInstances;
}

public int getSystemicLimit() {
return systemicLimit;
}

public void setSystemicLimit(int systemicLimit) {
int newValue = Math.max(0, systemicLimit);

if (this.systemicLimit != newValue) {
this.systemicLimit = newValue;

getConfig().setProperty(PARAM_SYSTEMIC_LIMIT, this.systemicLimit);
}
}

public boolean isMergeRelatedIssues() {
return mergeRelatedIssues;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import javax.swing.ImageIcon;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeCellRenderer;
import org.parosproxy.paros.Constant;
import org.zaproxy.zap.utils.DisplayUtils;
import org.zaproxy.zap.view.SiteMapTreeCellRenderer;

Expand Down Expand Up @@ -63,8 +64,7 @@ public Component getTreeCellRendererComponent(

super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);

if (value instanceof AlertNode) {
AlertNode alertNode = (AlertNode) value;
if (value instanceof AlertNode alertNode) {
if (alertNode.isRoot()) {
if (expanded) {
this.setIcon(FOLDER_OPEN_ICON);
Expand All @@ -76,6 +76,17 @@ public Component getTreeCellRendererComponent(
} else {
this.setIcon(LEAF_ICON);
}

if (alertNode.getChildCount() > 1) {
if (alertNode.isSystemic()) {
this.setText(
Constant.messages.getString("alert.label.namesystemic", alertNode));
} else {
this.setText(
Constant.messages.getString(
"alert.label.namecount", alertNode, alertNode.getChildCount()));
}
}
}
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ class AlertTreeModel extends DefaultTreeModel {

private static final Logger LOGGER = LogManager.getLogger(AlertTreeModel.class);

AlertTreeModel() {
private ExtensionAlert ext;

AlertTreeModel(ExtensionAlert ext) {
super(
new AlertNode(
-1,
Constant.messages.getString("alerts.tree.title"),
GROUP_ALERT_CHILD_COMPARATOR));
this.ext = ext;
}

void addPath(final Alert alert) {
Expand Down Expand Up @@ -215,6 +218,14 @@ private AlertNode addLeaf(AlertNode parent, String nodeName, Alert alert) {
needle.setAlert(alert);
int idx = parent.findIndex(needle);
if (idx < 0) {
// Not a duplicate alert
if (ext.isOverSystemicLimit(alert)) {
if (!parent.isSystemic()) {
parent.setSystemic(true);
nodeChanged(parent);
}
return null;
}
idx = -(idx + 1);
parent.insert(needle, idx);
nodesWereInserted(parent, new int[] {idx});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.JTree;
import javax.swing.tree.TreePath;
import org.apache.commons.httpclient.URIException;
Expand Down Expand Up @@ -94,6 +96,9 @@ public class ExtensionAlert extends ExtensionAdaptor
private Properties alertOverrides = new Properties();
private AlertAddDialog dialogAlertAdd;

private Map<String, Map<String, AtomicInteger>> siteToSystemicAlertMap =
new ConcurrentHashMap<>();

public ExtensionAlert() {
super(NAME);
this.setOrder(27);
Expand Down Expand Up @@ -170,7 +175,7 @@ private OptionsAlertPanel getOptionsPanel() {
return optionsPanel;
}

private AlertParam getAlertParam() {
protected AlertParam getAlertParam() {
if (alertParam == null) {
alertParam = new AlertParam();
}
Expand Down Expand Up @@ -502,14 +507,14 @@ AlertPanel getAlertPanel() {

AlertTreeModel getTreeModel() {
if (treeModel == null) {
treeModel = new AlertTreeModel();
treeModel = new AlertTreeModel(this);
}
return treeModel;
}

private AlertTreeModel getFilteredTreeModel() {
if (filteredTreeModel == null) {
filteredTreeModel = new AlertTreeModel();
filteredTreeModel = new AlertTreeModel(this);
}
return filteredTreeModel;
}
Expand Down Expand Up @@ -689,11 +694,12 @@ public void run() {
}

private void sessionChangedEventHandler(Session session) {
setTreeModel(new AlertTreeModel());
setTreeModel(new AlertTreeModel(this));

treeModel = null;
filteredTreeModel = null;
hrefs = new HashMap<>();
siteToSystemicAlertMap = new ConcurrentHashMap<>();

if (session == null) {
// Null session indicated we're shutting down
Expand Down Expand Up @@ -1259,4 +1265,31 @@ public boolean supportsLowMemory() {
public boolean isNewAlert(Alert alertToCheck) {
return (getTreeModel().getAlertNode(alertToCheck) == null);
}

/**
* Returns true if the given alert is over the systemic limit. If the alert is systemic then it
* will increment the count of that type of alert.
*
* @since 2.17.0
*/
public boolean isOverSystemicLimit(Alert alert) {
if (alert == null || !alert.isSystemic()) {
return false;
}
try {
// Always count locally, even if the systemicLimit is zero as that could be changed
Map<String, AtomicInteger> m =
siteToSystemicAlertMap.computeIfAbsent(
SessionStructure.getHostName(alert.getMsgUri()),
a -> new ConcurrentHashMap<>());
int count =
m.computeIfAbsent(alert.getAlertRef(), a -> new AtomicInteger())
.incrementAndGet();
int limit = getAlertParam().getSystemicLimit();
return limit > 0 && count > limit;
} catch (URIException e) {
// Ignore
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public class OptionsAlertPanel extends AbstractParamPanel {
*/
private ZapNumberSpinner maxInstances;

private ZapNumberSpinner systemicLimit;

private JCheckBox mergeRelatedIssues;
private JTextField overridesFilename;

Expand All @@ -72,15 +74,23 @@ public OptionsAlertPanel() {

JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(new EmptyBorder(2, 2, 2, 2));
int y = 0;

panel.add(
getMergeRelatedIssues(), LayoutHelper.getGBC(0, 0, 2, 1.0, new Insets(2, 2, 2, 2)));
getMergeRelatedIssues(),
LayoutHelper.getGBC(0, y++, 2, 1.0, new Insets(2, 2, 2, 2)));

JLabel maxInstancesLabel =
new JLabel(Constant.messages.getString("alert.optionspanel.label.maxinstances"));
maxInstancesLabel.setLabelFor(getMaxInstances());
panel.add(maxInstancesLabel, LayoutHelper.getGBC(0, 1, 1, 1.0, new Insets(2, 2, 2, 2)));
panel.add(getMaxInstances(), LayoutHelper.getGBC(1, 1, 1, 1.0, new Insets(2, 2, 2, 2)));
panel.add(maxInstancesLabel, LayoutHelper.getGBC(0, y, 1, 1.0, new Insets(2, 2, 2, 2)));
panel.add(getMaxInstances(), LayoutHelper.getGBC(1, y++, 1, 1.0, new Insets(2, 2, 2, 2)));

JLabel systemicLimitLabel =
new JLabel(Constant.messages.getString("alert.optionspanel.label.systemiclimit"));
systemicLimitLabel.setLabelFor(getSystemicLimit());
panel.add(systemicLimitLabel, LayoutHelper.getGBC(0, y, 1, 1.0, new Insets(2, 2, 2, 2)));
panel.add(getSystemicLimit(), LayoutHelper.getGBC(1, y++, 1, 1.0, new Insets(2, 2, 2, 2)));

JButton overridesButton =
new JButton(
Expand All @@ -94,8 +104,8 @@ public OptionsAlertPanel() {
overridesPanel.add(getOverridesFilename());
overridesPanel.add(overridesButton);

panel.add(overridesLabel, LayoutHelper.getGBC(0, 2, 1, 1.0, new Insets(2, 2, 2, 2)));
panel.add(overridesPanel, LayoutHelper.getGBC(1, 2, 1, 1.0, new Insets(2, 2, 2, 2)));
panel.add(overridesLabel, LayoutHelper.getGBC(0, y, 1, 1.0, new Insets(2, 2, 2, 2)));
panel.add(overridesPanel, LayoutHelper.getGBC(1, y++, 1, 1.0, new Insets(2, 2, 2, 2)));

add(panel);
}
Expand Down Expand Up @@ -123,6 +133,13 @@ private ZapNumberSpinner getMaxInstances() {
return maxInstances;
}

private ZapNumberSpinner getSystemicLimit() {
if (systemicLimit == null) {
systemicLimit = new ZapNumberSpinner();
}
return systemicLimit;
}

private JTextField getOverridesFilename() {
if (overridesFilename == null) {
overridesFilename = new JTextField(20);
Expand All @@ -136,6 +153,7 @@ public void initParam(Object obj) {
final AlertParam param = options.getParamSet(AlertParam.class);

getMaxInstances().setValue(Integer.valueOf(param.getMaximumInstances()));
getSystemicLimit().setValue(param.getSystemicLimit());
getMergeRelatedIssues().setSelected(param.isMergeRelatedIssues());
getMaxInstances().setEditable(param.isMergeRelatedIssues());
getOverridesFilename().setText(param.getOverridesFilename());
Expand All @@ -160,6 +178,7 @@ public void saveParam(Object obj) throws Exception {
final AlertParam param = options.getParamSet(AlertParam.class);

param.setMaximumInstances(getMaxInstances().getValue());
param.setSystemicLimit(getSystemicLimit().getValue());
param.setMergeRelatedIssues(getMergeRelatedIssues().isSelected());
param.setOverridesFilename(getOverridesFilename().getText());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ alert.label.cweid = CWE ID:
alert.label.desc = Description:
alert.label.evidence = Evidence:
alert.label.inputvector = Input Vector:
alert.label.namecount = {0} ({1})
alert.label.namesystemic = {0} (Systemic)
alert.label.other = Other Info:
alert.label.parameter = Parameter:
alert.label.ref = Reference:
Expand All @@ -105,6 +107,7 @@ alert.optionspanel.button.overridesFilename = Select...
alert.optionspanel.label.maxinstances = Max Alert Instances in Report:
alert.optionspanel.label.mergerelated = Merge related alerts in report
alert.optionspanel.label.overridesFilename = Alert Overrides File:
alert.optionspanel.label.systemiclimit = Systemic Limit:
alert.optionspanel.name = Alerts
alert.optionspanel.warn.badOverridesFilename = Invalid Alert Overrides File
alert.source.active = Active
Expand Down
Loading
Loading