/*
*
* Paros and its related class files.
*
* Paros is an HTTP/HTTPS proxy for assessing web application security.
* Copyright (C) 2003-2004 Chinotec Technologies Company
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Clarified Artistic License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Clarified Artistic License for more details.
*
* You should have received a copy of the Clarified Artistic License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// ZAP: 2011/12/04 Support deleting alerts
// ZAP: 2012/01/02 Separate param and attack
// ZAP: 2012/01/23 Changed the method compareTo to compare the fields correctly
// with each other.
// ZAP: 2012/03/15 Changed the methods toPluginXML and getUrlParamXML to use the class
// StringBuilder instead of StringBuffer and replaced some string concatenations with
// calls to the method append of the class StringBuilder.
// ZAP: 2012/04/25 Added @Override annotation to all appropriate methods.
// ZAP: 2012/05/02 Changed to not create a new String in the setters.
// ZAP: 2012/07/10 Issue 323: Added getIconUrl()
// ZAP: 2012/10/08 Issue 391: Performance improvements
// ZAP: 2012/12/19 Code Cleanup: Moved array brackets from variable name to type
// ZAP: 2013/07/12 Issue 713: Add CWE and WASC numbers to issues
// ZAP: 2013/09/08 Issue 691: Handle old plugins
// ZAP: 2013/11/16 Issue 866: Alert keeps HttpMessage longer than needed when HistoryReference is set/available
// ZAP: 2014/04/10 Issue 1042: Having significant issues opening a previous session
// ZAP: 2014/05/23 Issue 1209: Reliability becomes Confidence and add levels
// ZAP: 2015/01/04 Issue 1419: Include alert's evidence in HTML report
// ZAP: 2014/01/04 Issue 1475: Alerts with different name from same scanner might not be shown in report
// ZAP: 2015/02/09 Issue 1525: Introduce a database interface layer to allow for alternative implementations
// ZAP: 2015/08/24 Issue 1849: Option to merge related issues in reports
// ZAP: 2015/11/16 Issue 1555: Rework inclusion of HTML tags in reports
// ZAP: 2016/02/26 Deprecate alert as an element of Alert in favour of name
// ZAP: 2016/05/25 Normalise equals/hashCode/compareTo
// ZAP: 2016/08/10 Issue 2757: Alerts with different request method are considered the same
// ZAP: 2016/08/25 Initialise the method to an empty string
// ZAP: 2016/09/20 JavaDoc tweaks
// ZAP: 2016/10/11 Issue 2592: Differentiate the source of alerts
// ZAP: 2017/02/22 Issue 3224: Use TreeCellRenderers to prevent HTML injection issues
package org.parosproxy.paros.core.scanner;
import java.net.URL;
import javax.swing.ImageIcon;
import org.apache.commons.httpclient.URI;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.db.DatabaseException;
import org.parosproxy.paros.db.RecordAlert;
import org.parosproxy.paros.extension.report.ReportGenerator;
import org.parosproxy.paros.model.HistoryReference;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.zap.utils.DisplayUtils;
public class Alert implements Comparable<Alert> {
/**
* The source of the alerts.
*
* @since 2.6.0
*/
public enum Source {
/**
* An alert raised by unknown tool/functionality, mostly for old alerts which source is not (well) known.
*/
UNKNOWN(0, "alert.source.unknown"),
/**
* An alert raised by an active scanner.
*/
ACTIVE(1, "alert.source.active"),
/**
* An alert raised manually (by the user).
*/
MANUAL(2, "alert.source.manual"),
/**
* An alert raised by a passive scanner.
*/
PASSIVE(3, "alert.source.passive"),
/**
* An alert raised by other tools/functionalities in ZAP (for example, fuzzer, HTTPS Info add-on, custom scripts...).
*/
TOOL(4, "alert.source.tool");
private final int id;
private final String i18nKey;
private Source(int id, String i18nKey) {
this.id = id;
this.i18nKey = i18nKey;
}
/**
* Gets the identifier of this {@code Source}.
* <p>
* Should be used for persistence.
*
* @return the identifier.
* @see #getSource(int)
*/
public int getId() {
return id;
}
/**
* Gets the key for the internationalised name.
*
* @return the key for the internationalised name.
*/
public String getI18nKey() {
return i18nKey;
}
/**
* Gets the {@code Source} with the given identifier.
*
* @param id the identifier of the {@code Source}
* @return the {@code Source} with the given identifier, or {@link #UNKNOWN} if not a recognised identifier.
* @see #getId()
*/
public static Source getSource(int id) {
switch (id) {
case 0:
return UNKNOWN;
case 1:
return ACTIVE;
case 2:
return MANUAL;
case 3:
return PASSIVE;
case 4:
return TOOL;
default:
return UNKNOWN;
}
}
}
public static final int RISK_INFO = 0;
public static final int RISK_LOW = 1;
public static final int RISK_MEDIUM = 2;
public static final int RISK_HIGH = 3;
// ZAP: Added FALSE_POSITIVE
public static final int CONFIDENCE_FALSE_POSITIVE = 0;
/**
* @deprecated (2.4.0) Replaced by {@link #CONFIDENCE_LOW} confidence.
* SUSPICIOUS reliability has been deprecated in favour of using CONFIDENCE_LOW confidence.
*/
@Deprecated
public static final int SUSPICIOUS = 1;
public static final int CONFIDENCE_LOW = 1;
/**
* @deprecated (2.4.0) Replaced by {@link #CONFIDENCE_MEDIUM} confidence.
* WARNING reliability has been deprecated in favour of using CONFIDENCE_MEDIUM confidence.
*/
@Deprecated
public static final int WARNING = 2;
public static final int CONFIDENCE_MEDIUM = 2;
public static final int CONFIDENCE_HIGH = 3;
public static final int CONFIDENCE_USER_CONFIRMED = 4;
public static final String[] MSG_RISK = {"Informational", "Low", "Medium", "High"};
// ZAP: Added "false positive"
/**
* @deprecated (2.4.0) Replaced by {@link #MSG_CONFIDENCE}.
* Use of reliability has been deprecated in favour of using confidence.
*/
@Deprecated
public static final String[] MSG_RELIABILITY = {"False Positive", "Low", "Medium", "High", "Confirmed"};
public static final String[] MSG_CONFIDENCE = {"False Positive", "Low", "Medium", "High", "Confirmed"};
private int alertId = -1; // ZAP: Changed default alertId
private int pluginId = 0;
private String name = "";
private int risk = RISK_INFO;
private int confidence = CONFIDENCE_MEDIUM;
private String description = "";
private String uri = "";
private String param = "";
private String attack = "";
private String otherInfo = "";
private String solution = "";
private String reference = "";
private String evidence = "";
private int cweId = -1;
private int wascId = -1;
// Tempory ref - should be cleared asap after use
private HttpMessage message = null;
// ZAP: Added sourceHistoryId to Alert
private int sourceHistoryId = 0;
private HistoryReference historyRef = null;
// ZAP: Added logger
private static final Logger logger = Logger.getLogger(Alert.class);
// Cache this info so that we dont have to keep a ref to the HttpMessage
private String method = "";
private String postData;
private URI msgUri = null;
private Source source = Source.UNKNOWN;
public Alert(int pluginId) {
this.pluginId = pluginId;
}
public Alert(int pluginId, int risk, int confidence, String name) {
this(pluginId);
setRiskConfidence(risk, confidence);
setName(name);
}
public Alert(RecordAlert recordAlert) {
this(recordAlert.getPluginId(), recordAlert.getRisk(), recordAlert.getConfidence(), recordAlert.getAlert());
// ZAP: Set the alertId
this.alertId = recordAlert.getAlertId();
this.source = Source.getSource(recordAlert.getSourceId());
try {
HistoryReference hRef = new HistoryReference(recordAlert.getHistoryId());
setDetail(recordAlert.getDescription(), recordAlert.getUri(),
recordAlert.getParam(), recordAlert.getAttack(), recordAlert.getOtherInfo(),
recordAlert.getSolution(), recordAlert.getReference(),
recordAlert.getEvidence(), recordAlert.getCweId(), recordAlert.getWascId(),
null);
setHistoryRef(hRef);
} catch (HttpMalformedHeaderException e) {
// ZAP: Just an indication the history record doesnt exist
logger.debug(e.getMessage(), e);
} catch (Exception e) {
// ZAP: Log the exception
logger.error(e.getMessage(), e);
}
}
public Alert(RecordAlert recordAlert, HistoryReference ref) {
this(recordAlert.getPluginId(), recordAlert.getRisk(), recordAlert.getConfidence(), recordAlert.getAlert());
// ZAP: Set the alertId
this.alertId = recordAlert.getAlertId();
setDetail(recordAlert.getDescription(), recordAlert.getUri(),
recordAlert.getParam(), recordAlert.getAttack(), recordAlert.getOtherInfo(),
recordAlert.getSolution(), recordAlert.getReference(),
recordAlert.getEvidence(), recordAlert.getCweId(), recordAlert.getWascId(),
null);
setHistoryRef(ref);
}
/**
* @deprecated (2.4.0) Replaced by {@link #setRiskConfidence(int, int)}.
* Use of reliability has been deprecated in favour of using confidence
* @param risk the new risk
* @param confidence the new confidence
*/
@Deprecated
public void setRiskReliability(int risk, int confidence) {
this.risk = risk;
this.confidence = confidence;
}
public void setRiskConfidence(int risk, int confidence) {
this.risk = risk;
this.confidence = confidence;
}
/**
* @deprecated (2.5.0) Replaced by {@link #setName}.
* Use of alert has been deprecated in favour of using name.
* @param alert the new name
*/
@Deprecated
public void setAlert(String alert) {
setName(alert);
}
/**
* Sets the name of the alert to name
* @param name the name to set for the alert
* @since 2.5.0
*/
public void setName(String name) {
if (name == null) return;
this.name = name;
}
/**
* Sets the details of the alert.
*
* @param description the description of the alert
* @param uri the URI that has the issue
* @param param the parameter that has the issue
* @param attack the attack that triggers the issue
* @param otherInfo other information about the issue
* @param solution the solution for the issue
* @param reference references about the issue
* @param msg the HTTP message that triggers/triggered the issue
* @deprecated (2.2.0) Replaced by
* {@link #setDetail(String, String, String, String, String, String, String, String, int, int, HttpMessage)}. It
* will be removed in a future release.
*/
@Deprecated
public void setDetail(String description, String uri, String param, String attack, String otherInfo,
String solution, String reference, HttpMessage msg) {
setDetail(description, uri, param, attack, otherInfo, solution, reference, "", -1, -1, msg);
}
/**
* Sets the details of the alert.
*
* @param description the description of the alert
* @param uri the URI that has the issue
* @param param the parameter that has the issue
* @param attack the attack that triggers the issue
* @param otherInfo other information about the issue
* @param solution the solution for the issue
* @param reference references about the issue
* @param evidence the evidence (in the HTTP response) that the issue exists
* @param cweId the CWE ID of the issue
* @param wascId the WASC ID of the issue
* @param msg the HTTP message that triggers/triggered the issue
* @since 2.2.0
*/
public void setDetail(String description, String uri, String param, String attack, String otherInfo,
String solution, String reference, String evidence, int cweId, int wascId, HttpMessage msg) {
setDescription(description);
setUri(uri);
setParam(param);
setAttack(attack);
setOtherInfo(otherInfo);
setSolution(solution);
setReference(reference);
setMessage(msg);
setEvidence(evidence);
setCweId(cweId);
setWascId(wascId);
if (msg != null) {
setHistoryRef(msg.getHistoryRef());
}
}
private void setDetail(String description, String uri, String param, String attack, String otherInfo,
String solution, String reference, HistoryReference href) {
setDescription(description);
setUri(uri);
setParam(param);
setAttack(attack);
setOtherInfo(otherInfo);
setSolution(solution);
setReference(reference);
setHistoryRef(href);
}
public void setUri(String uri) {
// ZAP: Cope with null
if (uri == null) return;
// ZAP: Changed to not create a new String.
this.uri = uri;
}
public void setDescription(String description) {
if (description == null) return;
// ZAP: Changed to not create a new String.
this.description = description;
}
public void setParam(String param) {
if (param == null) return;
// ZAP: Changed to not create a new String.
this.param = param;
}
public void setOtherInfo(String otherInfo) {
if (otherInfo == null) return;
// ZAP: Changed to not create a new String.
this.otherInfo = otherInfo;
}
public void setSolution(String solution) {
if (solution == null) return;
// ZAP: Changed to not create a new String.
this.solution = solution;
}
public void setReference(String reference) {
if (reference == null) return;
// ZAP: Changed to not create a new String.
this.reference = reference;
}
public void setMessage(HttpMessage message) {
if (message != null) {
this.message = message;
this.method = message.getRequestHeader().getMethod();
this.postData = message.getRequestBody().toString();
this.msgUri = message.getRequestHeader().getURI();
} else {
// Used to clear the ref so we dont hold onto it
this.message = null;
}
}
@Override
public int compareTo(Alert alert2) {
if (risk < alert2.risk) {
return -1;
} else if (risk > alert2.risk) {
return 1;
}
if (confidence < alert2.confidence) {
return -1;
} else if (confidence > alert2.confidence) {
return 1;
}
if (pluginId < alert2.pluginId) {
return -1;
} else if (pluginId > alert2.pluginId) {
return 1;
}
int result = name.compareToIgnoreCase(alert2.name);
if (result != 0) {
return result;
}
result = method.compareToIgnoreCase(alert2.method);
if (result != 0) {
return result;
}
// ZAP: changed to compare the field uri with alert2.uri
result = uri.compareToIgnoreCase(alert2.uri);
if (result != 0) {
return result;
}
// ZAP: changed to compare the field param with alert2.param
result = param.compareToIgnoreCase(alert2.param);
if (result != 0) {
return result;
}
result = otherInfo.compareToIgnoreCase(alert2.otherInfo);
if (result != 0) {
return result;
}
result = compareStrings(evidence, alert2.evidence);
if (result != 0) {
return result;
}
return compareStrings(attack, alert2.attack);
}
private int compareStrings(String string, String otherString) {
if (string == null) {
if (otherString == null) {
return 0;
}
return -1;
} else if (otherString == null) {
return 1;
}
return string.compareTo(otherString);
}
/**
Override equals. Alerts are equal if the plugin id, alert, other info, uri and param is the same.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Alert item = (Alert) obj;
if (risk != item.risk) {
return false;
}
if (confidence != item.confidence) {
return false;
}
if (pluginId != item.pluginId) {
return false;
}
if (!name.equals(item.name)) {
return false;
}
if (!method.equalsIgnoreCase(item.method)) {
return false;
}
if (!uri.equalsIgnoreCase(item.uri)) {
return false;
}
if (!param.equalsIgnoreCase(item.param)) {
return false;
}
if (!otherInfo.equalsIgnoreCase(item.otherInfo)) {
return false;
}
if (evidence == null) {
if (item.evidence != null) {
return false;
}
} else if (!evidence.equals(item.evidence)) {
return false;
}
if (attack == null) {
if (item.attack != null) {
return false;
}
} else if (!attack.equals(item.attack)) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + risk;
result = prime * result + confidence;
result = prime * result + ((evidence == null) ? 0 : evidence.hashCode());
result = prime * result + name.hashCode();
result = prime * result + otherInfo.hashCode();
result = prime * result + param.hashCode();
result = prime * result + pluginId;
result = prime * result + method.hashCode();
result = prime * result + uri.hashCode();
result = prime * result + ((attack == null) ? 0 : attack.hashCode());
return result;
}
/**
* Creates a new instance of {@code Alert} with same members.
* @return a new {@code Alert} instance
*/
public Alert newInstance() {
Alert item = new Alert(this.pluginId);
item.setRiskConfidence(this.risk, this.confidence);
item.setName(this.name);
item.setDetail(this.description, this.uri, this.param, this.attack, this.otherInfo, this.solution, this.reference, this.historyRef);
item.setSource(this.source);
return item;
}
public String toPluginXML(String urls) {
StringBuilder sb = new StringBuilder(150); // ZAP: Changed the type to StringBuilder.
sb.append("<alertitem>\r\n");
sb.append(" <pluginid>").append(pluginId).append("</pluginid>\r\n");
sb.append(" <alert>").append(replaceEntity(name)).append("</alert>\r\n"); //Deprecated in 2.5.0, maintain for compatibility with custom code
sb.append(" <name>").append(replaceEntity(name)).append("</name>\r\n");
sb.append(" <riskcode>").append(risk).append("</riskcode>\r\n");
sb.append(" <confidence>").append(confidence).append("</confidence>\r\n");
sb.append(" <riskdesc>").append(replaceEntity(MSG_RISK[risk] + " (" + MSG_CONFIDENCE[confidence] + ")")).append("</riskdesc>\r\n");
sb.append(" <desc>").append(replaceEntity(paragraph(description))).append("</desc>\r\n");
sb.append(urls);
sb.append(" <solution>").append(replaceEntity(paragraph(solution))).append("</solution>\r\n");
// ZAP: Added otherInfo to the report
if (otherInfo != null && otherInfo.length() > 0) {
sb.append(" <otherinfo>").append(replaceEntity(paragraph(otherInfo))).append("</otherinfo>\r\n");
}
sb.append(" <reference>" ).append(replaceEntity(paragraph(reference))).append("</reference>\r\n");
if (cweId > 0) {
sb.append(" <cweid>" ).append(cweId).append("</cweid>\r\n");
}
if (wascId > 0) {
sb.append(" <wascid>" ).append(wascId).append("</wascid>\r\n");
}
sb.append(" <sourceid>" ).append(source.getId()).append("</sourceid>\r\n");
sb.append("</alertitem>\r\n");
return sb.toString();
}
public String replaceEntity(String text) {
String result = null;
if (text != null) {
result = ReportGenerator.entityEncode(text);
}
return result;
}
public String paragraph(String text) {
return "<p>" + text.replaceAll("\\r\\n","</p><p>").replaceAll("\\n","</p><p>") + "</p>";
}
/**
* @deprecated (2.5.0) Replaced by {@link #getName}.
* Use of alert has been deprecated in favour of using name.
* @return Returns the alert.
*/
@Deprecated
public String getAlert() {
return name;
}
/**
* @return Returns the name of the alert.
* @since 2.5.0
*/
public String getName() {
return name;
}
/**
* @return Returns the description.
*/
public String getDescription() {
return description;
}
/**
* @return Returns the id.
*/
public int getPluginId() {
return pluginId;
}
/**
* @return Returns the message.
*/
public HttpMessage getMessage() {
if (this.message != null) {
return this.message;
}
if (this.historyRef != null) {
try {
return this.historyRef.getHttpMessage();
} catch (HttpMalformedHeaderException | DatabaseException e) {
logger.error(e.getMessage(), e);
}
}
return null;
}
/**
* @return Returns the otherInfo.
*/
public String getOtherInfo() {
return otherInfo;
}
/**
* @return Returns the param.
*/
public String getParam() {
return param;
}
/**
* @return Returns the reference.
*/
public String getReference() {
return reference;
}
/**
* @deprecated (2.4.0) Replaced by {@link #getConfidence()}.
* @return the reliability.
*/
@Deprecated
public int getReliability() {
return confidence;
}
/**
* @return Returns the confidence.
*/
public int getConfidence() {
return confidence;
}
/**
* @return Returns the risk.
*/
public int getRisk() {
return risk;
}
/**
* Gets the correctly scaled icon for this alert.
* @return the correctly scaled icon for this alert
* @since 2.6.0
*/
public ImageIcon getIcon() {
if (confidence == Alert.CONFIDENCE_FALSE_POSITIVE) {
// Special case - theres no risk - use the green flag
return DisplayUtils.getScaledIcon(Constant.OK_FLAG_IMAGE_URL);
}
switch (risk) {
case Alert.RISK_INFO:
return DisplayUtils.getScaledIcon(Constant.INFO_FLAG_IMAGE_URL);
case Alert.RISK_LOW:
return DisplayUtils.getScaledIcon(Constant.LOW_FLAG_IMAGE_URL);
case Alert.RISK_MEDIUM:
return DisplayUtils.getScaledIcon(Constant.MED_FLAG_IMAGE_URL);
case Alert.RISK_HIGH:
return DisplayUtils.getScaledIcon(Constant.HIGH_FLAG_IMAGE_URL);
}
return null;
}
@Deprecated
public URL getIconUrl() {
if (confidence == Alert.CONFIDENCE_FALSE_POSITIVE) {
// Special case - theres no risk - use the green flag
return Constant.OK_FLAG_IMAGE_URL;
}
switch (risk) {
case Alert.RISK_INFO:
return Constant.INFO_FLAG_IMAGE_URL;
case Alert.RISK_LOW:
return Constant.LOW_FLAG_IMAGE_URL;
case Alert.RISK_MEDIUM:
return Constant.MED_FLAG_IMAGE_URL;
case Alert.RISK_HIGH:
return Constant.HIGH_FLAG_IMAGE_URL;
}
return null;
}
/**
* @return Returns the solution.
*/
public String getSolution() {
return solution;
}
/**
* @return Returns the uri.
*/
public String getUri() {
return uri;
}
/**
* @return Returns the alertId.
*/
public int getAlertId() {
return alertId;
}
/**
* @param alertId The alertId to set.
*/
public void setAlertId(int alertId) {
this.alertId = alertId;
}
public String getUrlParamXML() {
StringBuilder sb = new StringBuilder(200); // ZAP: Changed the type to StringBuilder.
sb.append(" <uri>").append(replaceEntity(uri)).append("</uri>\r\n");
sb.append(" <method>").append(replaceEntity(method)).append("</method>\r\n");
if (param != null && param.length() > 0) {
sb.append(" <param>").append(replaceEntity(param)).append("</param>\r\n");
}
if (attack != null && attack.length() > 0) {
sb.append(" <attack>").append(replaceEntity(attack)).append("</attack>\r\n");
}
if (evidence != null && evidence.length() > 0) {
sb.append(" <evidence>").append(replaceEntity(evidence)).append("</evidence>\r\n");
}
return sb.toString();
}
public int getSourceHistoryId() {
return sourceHistoryId;
}
public void setSourceHistoryId(int sourceHistoryId) {
this.sourceHistoryId = sourceHistoryId;
}
public HistoryReference getHistoryRef () {
return this.historyRef;
}
public void setHistoryRef(HistoryReference historyRef) {
this.historyRef = historyRef;
if (historyRef != null) {
this.message = null;
this.method = historyRef.getMethod();
this.msgUri = historyRef.getURI();
this.postData = historyRef.getRequestBody();
this.sourceHistoryId = historyRef.getHistoryId();
}
}
public String getAttack() {
return attack;
}
public void setAttack(String attack) {
this.attack = attack;
}
public String getMethod() {
return method;
}
public String getPostData() {
return postData;
}
public URI getMsgUri() {
return msgUri;
}
public String getEvidence() {
return evidence;
}
public void setEvidence(String evidence) {
this.evidence = evidence;
}
public int getCweId() {
return cweId;
}
public void setCweId(int cweId) {
this.cweId = cweId;
}
public int getWascId() {
return wascId;
}
public void setWascId(int wascId) {
this.wascId = wascId;
}
/**
* Gets the source of the alert.
*
* @return the source of the alert, never {@code null}.
* @since 2.6.0
*/
public Source getSource() {
return source;
}
/**
* Sets the source of the alert.
* <p>
* <strong>Note:</strong> The source should be considered immutable and should be set before the alert is persisted
* (normally by the tool/functionality raising the alert).
*
* @param source the source of the alert.
* @throws IllegalArgumentException if the given {@code source} is {@code null}.
* @since 2.6.0
*/
public void setSource(Source source) {
if (source == null) {
throw new IllegalArgumentException("Parameter source must not be null.");
}
this.source = source;
}
}