org.zaproxy.zap.extension.alert.AlertAPI Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of zap Show documentation
Show all versions of zap Show documentation
The Zed Attack Proxy (ZAP) is an easy to use integrated penetration testing tool for finding vulnerabilities in web applications. It is designed to be used by people with a wide range of security experience and as such is ideal for developers and functional testers who are new to penetration testing. ZAP provides automated scanners as well as a set of tools that allow you to find security vulnerabilities manually.
The newest version!
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2018 The ZAP Development Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.zap.extension.alert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.function.Consumer;
import net.sf.json.JSON;
import net.sf.json.JSONObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.db.DatabaseException;
import org.parosproxy.paros.db.RecordAlert;
import org.parosproxy.paros.db.TableAlert;
import org.parosproxy.paros.model.HistoryReference;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import org.zaproxy.zap.db.TableAlertTag;
import org.zaproxy.zap.extension.api.ApiAction;
import org.zaproxy.zap.extension.api.ApiException;
import org.zaproxy.zap.extension.api.ApiImplementor;
import org.zaproxy.zap.extension.api.ApiResponse;
import org.zaproxy.zap.extension.api.ApiResponseElement;
import org.zaproxy.zap.extension.api.ApiResponseList;
import org.zaproxy.zap.extension.api.ApiResponseSet;
import org.zaproxy.zap.extension.api.ApiView;
import org.zaproxy.zap.model.Context;
import org.zaproxy.zap.utils.ApiUtils;
import org.zaproxy.zap.utils.XMLStringUtil;
public class AlertAPI extends ApiImplementor {
public static final String PREFIX = "alert";
private static final Logger LOGGER = LogManager.getLogger(AlertAPI.class);
private static final String ACTION_DELETE_ALL_ALERTS = "deleteAllAlerts";
private static final String ACTION_DELETE_ALERTS = "deleteAlerts";
private static final String ACTION_DELETE_ALERT = "deleteAlert";
private static final String ACTION_UPDATE_ALERT = "updateAlert";
private static final String ACTION_ADD_ALERT = "addAlert";
private static final String ACTION_UPDATE_ALERTS_CONFIDENCE = "updateAlertsConfidence";
private static final String ACTION_UPDATE_ALERTS_RISK = "updateAlertsRisk";
private static final String VIEW_ALERT = "alert";
private static final String VIEW_ALERTS = "alerts";
private static final String VIEW_ALERTS_SUMMARY = "alertsSummary";
private static final String VIEW_NUMBER_OF_ALERTS = "numberOfAlerts";
private static final String VIEW_ALERTS_BY_RISK = "alertsByRisk";
private static final String VIEW_ALERT_COUNTS_BY_RISK = "alertCountsByRisk";
private static final String PARAM_BASE_URL = "baseurl";
private static final String PARAM_COUNT = "count";
private static final String PARAM_CONTEXT_NAME = "contextName";
private static final String PARAM_URL = "url";
private static final String PARAM_ID = "id";
private static final String PARAM_RECURSE = "recurse";
private static final String PARAM_RISK = "riskId";
private static final String PARAM_START = "start";
private static final String PARAM_MESSAGE_ID = "messageId";
private static final String PARAM_ALERT_ID = "id";
private static final String PARAM_ALERT_IDS = "ids";
private static final String PARAM_ALERT_NAME = "name";
private static final String PARAM_CONFIDENCE = "confidenceId";
private static final String PARAM_ALERT_DESCRIPTION = "description";
private static final String PARAM_ALERT_PARAM = "param";
private static final String PARAM_ALERT_ATTACK = "attack";
private static final String PARAM_ALERT_OTHERINFO = "otherInfo";
private static final String PARAM_ALERT_SOLUTION = "solution";
private static final String PARAM_ALERT_REFS = "references";
private static final String PARAM_ALERT_EVIDENCE = "evidence";
private static final String PARAM_CWEID = "cweId";
private static final String PARAM_WASCID = "wascId";
/**
* The constant that indicates that no risk ID is being provided.
*
* @see #getRiskId(JSONObject)
* @see #processAlerts(String, int, int, int, Processor)
*/
private static final int NO_RISK_ID = -1;
/**
* The constant that indicates that no confidence ID is being provided.
*
* @see #getConfidenceId(JSONObject)
*/
private static final int NO_CONFIDENCE_ID = -1;
private ExtensionAlert extension;
public AlertAPI(ExtensionAlert ext) {
this.extension = ext;
this.addApiView(new ApiView(VIEW_ALERT, new String[] {PARAM_ID}));
this.addApiView(
new ApiView(
VIEW_ALERTS,
null,
new String[] {
PARAM_BASE_URL, PARAM_START, PARAM_COUNT, PARAM_RISK, PARAM_CONTEXT_NAME
}));
this.addApiView(new ApiView(VIEW_ALERTS_SUMMARY, null, new String[] {PARAM_BASE_URL}));
this.addApiView(
new ApiView(
VIEW_NUMBER_OF_ALERTS, null, new String[] {PARAM_BASE_URL, PARAM_RISK}));
this.addApiView(
new ApiView(VIEW_ALERTS_BY_RISK, null, new String[] {PARAM_URL, PARAM_RECURSE}));
this.addApiView(
new ApiView(
VIEW_ALERT_COUNTS_BY_RISK, null, new String[] {PARAM_URL, PARAM_RECURSE}));
this.addApiAction(new ApiAction(ACTION_DELETE_ALL_ALERTS));
this.addApiAction(
new ApiAction(
ACTION_DELETE_ALERTS,
null,
new String[] {PARAM_CONTEXT_NAME, PARAM_BASE_URL, PARAM_RISK}));
this.addApiAction(new ApiAction(ACTION_DELETE_ALERT, new String[] {PARAM_ID}));
this.addApiAction(
new ApiAction(
ACTION_UPDATE_ALERTS_CONFIDENCE,
new String[] {PARAM_ALERT_IDS, PARAM_CONFIDENCE}));
this.addApiAction(
new ApiAction(
ACTION_UPDATE_ALERTS_RISK, new String[] {PARAM_ALERT_IDS, PARAM_RISK}));
this.addApiAction(
new ApiAction(
ACTION_UPDATE_ALERT,
new String[] {
PARAM_ALERT_ID,
PARAM_ALERT_NAME,
PARAM_RISK,
PARAM_CONFIDENCE,
PARAM_ALERT_DESCRIPTION
},
new String[] {
PARAM_ALERT_PARAM,
PARAM_ALERT_ATTACK,
PARAM_ALERT_OTHERINFO,
PARAM_ALERT_SOLUTION,
PARAM_ALERT_REFS,
PARAM_ALERT_EVIDENCE,
PARAM_CWEID,
PARAM_WASCID
}));
this.addApiAction(
new ApiAction(
ACTION_ADD_ALERT,
new String[] {
PARAM_MESSAGE_ID,
PARAM_ALERT_NAME,
PARAM_RISK,
PARAM_CONFIDENCE,
PARAM_ALERT_DESCRIPTION
},
new String[] {
PARAM_ALERT_PARAM,
PARAM_ALERT_ATTACK,
PARAM_ALERT_OTHERINFO,
PARAM_ALERT_SOLUTION,
PARAM_ALERT_REFS,
PARAM_ALERT_EVIDENCE,
PARAM_CWEID,
PARAM_WASCID
}));
}
@Override
public String getPrefix() {
return PREFIX;
}
@Override
public ApiResponse handleApiView(String name, JSONObject params) throws ApiException {
ApiResponse result = null;
if (VIEW_ALERT.equals(name)) {
TableAlert tableAlert = Model.getSingleton().getDb().getTableAlert();
TableAlertTag tableAlertTag = Model.getSingleton().getDb().getTableAlertTag();
RecordAlert recordAlert;
Map alertTags;
try {
recordAlert = tableAlert.read(this.getParam(params, PARAM_ID, -1));
alertTags = tableAlertTag.getTagsByAlertId(this.getParam(params, PARAM_ID, -1));
} catch (DatabaseException e) {
LOGGER.error("Failed to read the alert from the session:", e);
throw new ApiException(ApiException.Type.INTERNAL_ERROR);
}
if (recordAlert == null) {
throw new ApiException(ApiException.Type.DOES_NOT_EXIST);
}
Alert alert = new Alert(recordAlert);
alert.setTags(alertTags);
result = new ApiResponseElement(alertToSet(alert));
} else if (VIEW_ALERTS.equals(name)) {
final ApiResponseList resultList = new ApiResponseList(name);
String contextName = this.getParam(params, PARAM_CONTEXT_NAME, "");
Context context = contextName.isEmpty() ? null : ApiUtils.getContextByName(contextName);
processAlerts(
this.getParam(params, PARAM_BASE_URL, (String) null),
this.getParam(params, PARAM_START, -1),
this.getParam(params, PARAM_COUNT, -1),
getRiskId(params),
(Alert alert) -> {
if (context == null || context.isInContext(alert.getUri())) {
resultList.addItem(alertToSet(alert));
}
});
result = resultList;
} else if (VIEW_NUMBER_OF_ALERTS.equals(name)) {
CounterProcessor counter = new CounterProcessor<>();
processAlerts(
this.getParam(params, PARAM_BASE_URL, (String) null),
this.getParam(params, PARAM_START, -1),
this.getParam(params, PARAM_COUNT, -1),
getRiskId(params),
counter);
result = new ApiResponseElement(name, Integer.toString(counter.getCount()));
} else if (VIEW_ALERTS_SUMMARY.equals(name)) {
final int[] riskSummary = {0, 0, 0, 0};
Processor counter = (Alert alert) -> riskSummary[alert.getRisk()]++;
processAlerts(
this.getParam(params, PARAM_BASE_URL, (String) null),
-1,
-1,
NO_RISK_ID,
counter);
Map alertData = new HashMap<>();
for (int i = 0; i < riskSummary.length; i++) {
alertData.put(Alert.MSG_RISK[i], riskSummary[i]);
}
result =
new ApiResponseSet<>("risk", alertData) {
@Override
public JSON toJSON() {
JSONObject response = new JSONObject();
response.put(name, super.toJSON());
return response;
}
};
} else if (VIEW_ALERTS_BY_RISK.equals(name)) {
String url = this.getParam(params, PARAM_URL, "");
boolean recurse = this.getParam(params, PARAM_RECURSE, false);
ApiResponseList resultList = new ApiResponseList(name);
result = resultList;
// 0 (RISK_INFO) -> 3 (RISK_HIGH)
ApiResponseList[] list = new ApiResponseList[Alert.NUMBER_RISKS];
for (int i = 0; i < list.length; i++) {
list[i] = new ApiResponseList(Alert.MSG_RISK[i]);
}
AlertTreeModel model = extension.getTreeModel();
AlertNode root = (AlertNode) model.getRoot();
Enumeration> enumAllAlerts = root.children();
while (enumAllAlerts.hasMoreElements()) {
AlertNode child = (AlertNode) enumAllAlerts.nextElement();
Alert alert = child.getUserObject();
ApiResponseList alertList = filterAlertInstances(child, url, recurse);
if (!alertList.getItems().isEmpty()) {
list[alert.getRisk()].addItem(alertList);
}
}
Arrays.stream(list).forEach(resultList::addItem);
} else if (VIEW_ALERT_COUNTS_BY_RISK.equals(name)) {
String url = this.getParam(params, PARAM_URL, "");
boolean recurse = this.getParam(params, PARAM_RECURSE, false);
// 0 (RISK_INFO) -> 3 (RISK_HIGH)
int[] riskCounts = new int[] {0, 0, 0, 0};
int falsePositiveCount = 0;
AlertTreeModel model = extension.getTreeModel();
AlertNode root = (AlertNode) model.getRoot();
Enumeration> enumAllAlerts = root.children();
while (enumAllAlerts.hasMoreElements()) {
AlertNode child = (AlertNode) enumAllAlerts.nextElement();
Alert alert = child.getUserObject();
ApiResponseList alertList = filterAlertInstances(child, url, recurse);
if (!alertList.getItems().isEmpty()) {
if (alert.getConfidence() == Alert.CONFIDENCE_FALSE_POSITIVE) {
falsePositiveCount += 1;
} else {
riskCounts[alert.getRisk()] += 1;
}
}
}
Map map = new HashMap<>();
map.put(Alert.MSG_RISK[Alert.RISK_HIGH], riskCounts[Alert.RISK_HIGH]);
map.put(Alert.MSG_RISK[Alert.RISK_MEDIUM], riskCounts[Alert.RISK_MEDIUM]);
map.put(Alert.MSG_RISK[Alert.RISK_LOW], riskCounts[Alert.RISK_LOW]);
map.put(Alert.MSG_RISK[Alert.RISK_INFO], riskCounts[Alert.RISK_INFO]);
map.put(Alert.MSG_CONFIDENCE[Alert.CONFIDENCE_FALSE_POSITIVE], falsePositiveCount);
result = new ApiResponseSet<>(name, map);
} else {
throw new ApiException(ApiException.Type.BAD_VIEW);
}
return result;
}
@Override
public ApiResponse handleApiAction(String name, JSONObject params) throws ApiException {
if (ACTION_DELETE_ALERT.equals(name)) {
int alertId = ApiUtils.getIntParam(params, PARAM_ID);
extension.deleteAlert(getAlertFromDb(alertId));
} else if (ACTION_DELETE_ALL_ALERTS.equals(name)) {
extension.deleteAllAlerts();
} else if (ACTION_DELETE_ALERTS.equals(name)) {
String contextName = this.getParam(params, PARAM_CONTEXT_NAME, "");
Context context = contextName.isEmpty() ? null : ApiUtils.getContextByName(contextName);
final int[] count = {0};
Processor counter =
(Alert alert) -> {
if (context == null || context.isInContext(alert.getUri())) {
extension.deleteAlert(alert);
count[0]++;
}
};
processAlerts(
this.getParam(params, PARAM_BASE_URL, (String) null),
-1,
-1,
getRiskId(params),
counter);
return new ApiResponseElement(ACTION_DELETE_ALERTS, String.valueOf(count[0]));
} else if (ACTION_UPDATE_ALERT.equals(name)) {
int alertId = ApiUtils.getIntParam(params, PARAM_ALERT_ID);
String alertName = params.getString(PARAM_ALERT_NAME);
int riskId = getRiskId(params);
int confidenceId = getConfidenceId(params);
String desc = params.getString(PARAM_ALERT_DESCRIPTION);
String param = ApiUtils.getOptionalStringParam(params, PARAM_ALERT_PARAM);
String attack = ApiUtils.getOptionalStringParam(params, PARAM_ALERT_ATTACK);
String otherInfo = ApiUtils.getOptionalStringParam(params, PARAM_ALERT_OTHERINFO);
String solution = ApiUtils.getOptionalStringParam(params, PARAM_ALERT_SOLUTION);
String refs = ApiUtils.getOptionalStringParam(params, PARAM_ALERT_REFS);
String evidence = ApiUtils.getOptionalStringParam(params, PARAM_ALERT_EVIDENCE);
int cweId = getParam(params, PARAM_CWEID, 0);
int wascId = getParam(params, PARAM_WASCID, 0);
Alert updatedAlert = getAlertFromDb(alertId);
updatedAlert.setName(alertName);
updatedAlert.setRisk(riskId);
updatedAlert.setConfidence(confidenceId);
updatedAlert.setDescription(desc);
updatedAlert.setParam(param);
updatedAlert.setAttack(attack);
updatedAlert.setOtherInfo(otherInfo);
updatedAlert.setSolution(solution);
updatedAlert.setReference(refs);
updatedAlert.setEvidence(evidence);
updatedAlert.setCweId(cweId);
updatedAlert.setWascId(wascId);
processAlertUpdate(updatedAlert);
} else if (ACTION_ADD_ALERT.equals(name)) {
int messageId = ApiUtils.getIntParam(params, PARAM_MESSAGE_ID);
String alertName = ApiUtils.getNonEmptyStringParam(params, PARAM_ALERT_NAME);
int riskId = getRiskId(params);
int confidenceId = getConfidenceId(params);
String desc = params.getString(PARAM_ALERT_DESCRIPTION);
String param = ApiUtils.getOptionalStringParam(params, PARAM_ALERT_PARAM);
String attack = ApiUtils.getOptionalStringParam(params, PARAM_ALERT_ATTACK);
String otherInfo = ApiUtils.getOptionalStringParam(params, PARAM_ALERT_OTHERINFO);
String solution = ApiUtils.getOptionalStringParam(params, PARAM_ALERT_SOLUTION);
String refs = ApiUtils.getOptionalStringParam(params, PARAM_ALERT_REFS);
String evidence = ApiUtils.getOptionalStringParam(params, PARAM_ALERT_EVIDENCE);
int cweId = getParam(params, PARAM_CWEID, 0);
int wascId = getParam(params, PARAM_WASCID, 0);
HttpMessage msg = getHttpMessage(messageId);
Alert newAlert = new Alert(-1, riskId, confidenceId, alertName);
newAlert.setSource(Alert.Source.MANUAL);
newAlert.setMessage(msg);
newAlert.setUri(msg.getRequestHeader().getURI().toString());
newAlert.setName(alertName);
newAlert.setRisk(riskId);
newAlert.setConfidence(confidenceId);
newAlert.setDescription(desc);
newAlert.setParam(param);
newAlert.setAttack(attack);
newAlert.setOtherInfo(otherInfo);
newAlert.setSolution(solution);
newAlert.setReference(refs);
newAlert.setEvidence(evidence);
newAlert.setCweId(cweId);
newAlert.setWascId(wascId);
extension.alertFound(newAlert, msg.getHistoryRef());
return new ApiResponseElement(name, Integer.toString(newAlert.getAlertId()));
} else if (ACTION_UPDATE_ALERTS_CONFIDENCE.equals(name)) {
int confidenceId = getConfidenceId(params);
updateAlerts(params, alert -> alert.setConfidence(confidenceId));
} else if (ACTION_UPDATE_ALERTS_RISK.equals(name)) {
int riskId = getRiskId(params);
updateAlerts(params, alert -> alert.setRisk(riskId));
} else {
throw new ApiException(ApiException.Type.BAD_ACTION);
}
return ApiResponseElement.OK;
}
private static ApiResponseList filterAlertInstances(
AlertNode alertNode, String url, boolean recurse) {
ApiResponseList alertList = new ApiResponseList(alertNode.getUserObject().getName());
Enumeration> enumAlertInsts = alertNode.children();
while (enumAlertInsts.hasMoreElements()) {
AlertNode childAlert = (AlertNode) enumAlertInsts.nextElement();
if (!url.isEmpty()) {
String alertUrl = childAlert.getUserObject().getUri();
if (!alertUrl.startsWith(url)) {
continue;
}
if (!recurse) {
// Exact match, excluding url params
if (alertUrl.indexOf('?') > 0) {
alertUrl = alertUrl.substring(0, alertUrl.indexOf('?'));
}
if (!alertUrl.equals(url)) {
continue;
}
}
}
alertList.addItem(alertSummaryToSet(childAlert.getUserObject()));
}
return alertList;
}
private static void processAlerts(
String baseUrl, int start, int count, int riskId, Processor processor)
throws ApiException {
List alerts = new ArrayList<>();
try {
TableAlert tableAlert = Model.getSingleton().getDb().getTableAlert();
TableAlertTag tableAlertTag = Model.getSingleton().getDb().getTableAlertTag();
// TODO this doesn't work, but should be used when its fixed :/
// Vector v =
// tableAlert.getAlertListBySession(Model.getSingleton().getSession().getSessionId());
Vector v = tableAlert.getAlertList();
PaginationConstraintsChecker pcc = new PaginationConstraintsChecker(start, count);
for (int alertId : v) {
RecordAlert recAlert = tableAlert.read(alertId);
Alert alert = new Alert(recAlert);
if (alert.getConfidence() != Alert.CONFIDENCE_FALSE_POSITIVE
&& !alerts.contains(alert)) {
if (baseUrl != null && !alert.getUri().startsWith(baseUrl)) {
// Not subordinate to the specified URL
continue;
}
if (riskId != NO_RISK_ID && alert.getRisk() != riskId) {
continue;
}
pcc.recordProcessed();
alerts.add(alert);
if (!pcc.hasPageStarted()) {
continue;
}
alert.setTags(tableAlertTag.getTagsByAlertId(alertId));
processor.process(alert);
if (pcc.hasPageEnded()) {
break;
}
}
}
} catch (DatabaseException e) {
LOGGER.error(e.getMessage(), e);
throw new ApiException(ApiException.Type.INTERNAL_ERROR);
}
}
private static ApiResponseSet