/* * * 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/08/30 Support for scanner levels // ZAP: 2012/01/02 Separate param and attack // ZAP: 2012/03/03 Added getLevel(boolean incDefault) // ZAP: 2102/03/15 Changed the type of the parameter "sb" of the method matchBodyPattern to // StringBuilder. // ZAP: 2012/04/25 Added @Override annotation to all appropriate methods. // ZAP: 2012/08/07 Renamed Level to AlertThreshold and added support for AttackStrength // ZAP: 2012/08/31 Enabled control of AttackStrength // ZAP: 2012/10/03 Issue 388 Added enabling support for technologies // ZAP: 2013/01/19 Issue 460 Add support for a scan progress dialog // ZAP: 2013/01/25 Removed the "(non-Javadoc)" comments. // ZAP: 2013/02/19 Issue 528 Scan progress dialog can show negative progress times // ZAP: 2013/04/14 Issue 611: Log the exceptions thrown by active scanners as error // ZAP: 2013/05/02 Re-arranged all modifiers into Java coding standard order // 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 842: NullPointerException while active scanning with ExtensionAntiCSRF disabled // ZAP: 2014/01/16 Add support to plugin skipping // ZAP: 2014/02/12 Issue 1030: Load and save scan policies // ZAP: 2014/02/21 Issue 1043: Custom active scan dialog // ZAP: 2014/05/15 Issue 1196: AbstractPlugin.bingo incorrectly sets evidence to attack // ZAP: 2014/05/23 Issue 1209: Reliability becomes Confidence and add levels // ZAP: 2014/07/07 Issue 389: Enable technology scope for scanners // ZAP: 2014/10/25 Issue 1062: Made plugins that calls sendandrecieve also invoke scanner // hook before and after message update // ZAP: 2014/11/19 Issue 1412: Init scan rule status (quality) from add-on // ZAP: 2015/03/26 Issue 1573: Add option to inject plugin ID in header for all ascan requests // ZAP: 2015/07/26 Issue 1618: Target Technology Not Honored // ZAP: 2015/08/19 Issue 1785: Plugin enabled even if dependencies are not, "hangs" active scan // ZAP: 2016/03/22 Implement init() and getDependency() by default, most plugins do not use them // ZAP: 2016/04/21 Include Plugin itself when notifying of a new message sent // ZAP: 2016/05/03 Remove exceptions' stack trace prints // ZAP: 2016/06/10 Honour scan's scope when following redirections // ZAP: 2016/07/12 Do not allow techSet to be null // ZAP: 2017/03/27 Use HttpRequestConfig. package org.parosproxy.paros.core.scanner; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.security.InvalidParameterException; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.configuration.Configuration; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.URI; import org.apache.log4j.Logger; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.extension.encoder.Encoder; import org.parosproxy.paros.network.HttpHeader; import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.zap.control.AddOn; import org.zaproxy.zap.extension.anticsrf.AntiCsrfToken; import org.zaproxy.zap.extension.anticsrf.ExtensionAntiCSRF; import org.zaproxy.zap.model.Tech; import org.zaproxy.zap.model.TechSet; import org.zaproxy.zap.network.HttpRedirectionValidator; import org.zaproxy.zap.network.HttpRequestConfig; public abstract class AbstractPlugin implements Plugin, Comparable<Object> { private static final String[] NO_DEPENDENCIES = {}; /** * Default pattern used in pattern check for most plugins. */ protected static final int PATTERN_PARAM = Pattern.CASE_INSENSITIVE | Pattern.MULTILINE; /** * CRLF string. */ protected static final String CRLF = "\r\n"; private HostProcess parent = null; private HttpMessage msg = null; private boolean enabled = true; private Logger log = Logger.getLogger(this.getClass()); private Configuration config = null; // ZAP Added delayInMs private int delayInMs; private ExtensionAntiCSRF extAntiCSRF = null; private Encoder encoder = new Encoder(); private AlertThreshold defaultAttackThreshold = AlertThreshold.MEDIUM; private static final AlertThreshold[] alertThresholdsSupported = new AlertThreshold[]{AlertThreshold.MEDIUM}; private AttackStrength defaultAttackStrength = AttackStrength.MEDIUM; private static final AttackStrength[] attackStrengthsSupported = new AttackStrength[]{AttackStrength.MEDIUM}; private TechSet techSet; private Date started = null; private Date finished = null; private AddOn.Status status = AddOn.Status.unknown; /** * The HTTP request configuration, uses a {@link HttpRedirectionValidator} that ensures the followed redirections are in * scan's scope. * <p> * Lazily initialised. * * @see #getHttpRequestConfig() */ private HttpRequestConfig httpRequestConfig; /** * Default Constructor */ public AbstractPlugin() { this.techSet = TechSet.AllTech; } @Override public abstract int getId(); @Override public abstract String getName(); @Override public String getCodeName() { String result = getClass().getName(); int pos = getClass().getName().lastIndexOf("."); if (pos > -1) { result = result.substring(pos + 1); } return result; } /** * Returns no dependencies by default. * * @since 2.5.0 * @return an empty array (that is, no dependencies) */ @Override public String[] getDependency() { return NO_DEPENDENCIES; } @Override public abstract String getDescription(); @Override public abstract int getCategory(); @Override public abstract String getSolution(); @Override public abstract String getReference(); @Override public void init(HttpMessage msg, HostProcess parent) { this.msg = msg.cloneAll(); this.parent = parent; if (this.parent.getScannerParam().isInjectPluginIdInHeader()) { this.msg.getRequestHeader().setHeader(HttpHeader.X_ZAP_SCAN_ID, Integer.toString(getId())); } init(); } /** * Finishes the initialisation of the plugin, subclasses should add any initialisation logic/code to this method. * <p> * Called after the plugin has been initialised with the message being scanned. By default it does nothing. * <p> * Since 2.5.0 it is no longer abstract. * * @see #init(HttpMessage, HostProcess) */ public void init() { } /** * Obtain a new HttpMessage with the same request as the base. The response * is empty. This is used by plugin to build/craft a new message to * send/receive. It does not affect the base message. * * @return A new HttpMessage with cloned request. Response is empty. */ protected HttpMessage getNewMsg() { return msg.cloneRequest(); } /** * Get the base reference HttpMessage for this check. Both request and * response is present. It should not be modified during when the plugin * runs. * * @return The base HttpMessage with request/response. */ protected HttpMessage getBaseMsg() { return msg; } /** * Sends and receives the given {@code message}, always following redirections. * <p> * The following changes are made to the request before being sent: * <ul> * <li>The anti-CSRF token contained in the message will be handled/regenerated, if any;</li> * <li>The request headers {@link HttpHeader#IF_MODIFIED_SINCE} and {@link HttpHeader#IF_NONE_MATCH} are removed, to always * obtain a fresh response;</li> * <li>The header {@link HttpHeader#CONTENT_LENGTH} is updated, to match the length of the request body.</li> * <li>Changes done by {@link org.zaproxy.zap.network.HttpSenderListener HttpSenderListener} (for example, scripts).</li> * </ul> * * @param message the message to be sent and received * @throws HttpException if a HTTP error occurred * @throws IOException if an I/O error occurred (for example, read time out) * @see #sendAndReceive(HttpMessage, boolean) * @see #sendAndReceive(HttpMessage, boolean, boolean) */ protected void sendAndReceive(HttpMessage message) throws IOException { sendAndReceive(message, true); } /** * Sends and receives the given {@code message}, optionally following redirections. * <p> * The following changes are made to the request before being sent: * <ul> * <li>The anti-CSRF token contained in the message will be handled/regenerated, if any;</li> * <li>The request headers {@link HttpHeader#IF_MODIFIED_SINCE} and {@link HttpHeader#IF_NONE_MATCH} are removed, to always * obtain a fresh response;</li> * <li>The header {@link HttpHeader#CONTENT_LENGTH} is updated, to match the length of the request body.</li> * <li>Changes done by {@link org.zaproxy.zap.network.HttpSenderListener HttpSenderListener} (for example, scripts).</li> * </ul> * * @param message the message to be sent and received * @param isFollowRedirect {@code true} if redirections should be followed, {@code false} otherwise * @throws HttpException if a HTTP error occurred * @throws IOException if an I/O error occurred (for example, read time out) * @see #sendAndReceive(HttpMessage) * @see #sendAndReceive(HttpMessage, boolean, boolean) */ protected void sendAndReceive(HttpMessage message, boolean isFollowRedirect) throws IOException { sendAndReceive(message, isFollowRedirect, true); } /** * Sends and receives the given {@code message}, optionally following redirections and optionally regenerating anti-CSRF * token, if any. * <p> * The following changes are made to the request before being sent: * <ul> * <li>The request headers {@link HttpHeader#IF_MODIFIED_SINCE} and {@link HttpHeader#IF_NONE_MATCH} are removed, to always * obtain a fresh response;</li> * <li>The header {@link HttpHeader#CONTENT_LENGTH} is updated, to match the length of the request body.</li> * <li>Changes done by {@link org.zaproxy.zap.network.HttpSenderListener HttpSenderListener} (for example, scripts).</li> * </ul> * * @param message the message to be sent and received * @param isFollowRedirect {@code true} if redirections should be followed, {@code false} otherwise * @param handleAntiCSRF {@code true} if the anti-CSRF token present in the request should be handled/regenerated, * {@code false} otherwise * @throws HttpException if a HTTP error occurred * @throws IOException if an I/O error occurred (for example, read time out) * @see #sendAndReceive(HttpMessage) * @see #sendAndReceive(HttpMessage, boolean) */ protected void sendAndReceive(HttpMessage message, boolean isFollowRedirect, boolean handleAntiCSRF) throws IOException { if (parent.handleAntiCsrfTokens() && handleAntiCSRF) { if (extAntiCSRF == null) { extAntiCSRF = (ExtensionAntiCSRF) Control.getSingleton().getExtensionLoader().getExtension(ExtensionAntiCSRF.NAME); } if (extAntiCSRF != null) { List<AntiCsrfToken> tokens = extAntiCSRF.getTokens(message); AntiCsrfToken antiCsrfToken = null; if (tokens.size() > 0) { antiCsrfToken = tokens.get(0); } if (antiCsrfToken != null) { regenerateAntiCsrfToken(message, antiCsrfToken); } } } // always get the fresh copy message.getRequestHeader().setHeader(HttpHeader.IF_MODIFIED_SINCE, null); message.getRequestHeader().setHeader(HttpHeader.IF_NONE_MATCH, null); message.getRequestHeader().setContentLength(message.getRequestBody().length()); if (this.getDelayInMs() > 0) { try { Thread.sleep(this.getDelayInMs()); } catch (InterruptedException e) { // Ignore } } //ZAP: Runs the "beforeScan" methods of any ScannerHooks parent.performScannerHookBeforeScan(message, this); if (isFollowRedirect) { parent.getHttpSender().sendAndReceive(message, getHttpRequestConfig()); } else { parent.getHttpSender().sendAndReceive(message, false); } // ZAP: Notify parent parent.notifyNewMessage(this, message); //ZAP: Set the history reference back and run the "afterScan" methods of any ScannerHooks parent.performScannerHookAfterScan(message, this); } /** * Gets the HTTP request configuration, that ensures the followed redirections are in scan's scope. * * @return the HTTP request configuration, never {@code null}. * @see #httpRequestConfig */ private HttpRequestConfig getHttpRequestConfig() { if (httpRequestConfig == null) { httpRequestConfig = HttpRequestConfig.builder().setRedirectionValidator(new HttpRedirectionValidator() { @Override public boolean isValid(URI redirection) { if (!getParent().nodeInScope(redirection.getEscapedURI())) { if (log.isDebugEnabled()) { log.debug("Skipping redirection out of scan's scope: " + redirection); } return false; } return true; } @Override public void notifyMessageReceived(HttpMessage message) { // Nothing to do with the message. } }).build(); } return httpRequestConfig; } private void regenerateAntiCsrfToken(HttpMessage msg, AntiCsrfToken antiCsrfToken) { if (antiCsrfToken == null) { return; } String tokenValue = null; try { HttpMessage tokenMsg = antiCsrfToken.getMsg().cloneAll(); // Ensure we dont loop sendAndReceive(tokenMsg, true, false); tokenValue = extAntiCSRF.getTokenValue(tokenMsg, antiCsrfToken.getName()); } catch (Exception e) { log.error(e.getMessage(), e); } if (tokenValue != null) { // Replace token value - only supported in the body right now log.debug("regenerateAntiCsrfToken replacing " + antiCsrfToken.getValue() + " with " + encoder.getURLEncode(tokenValue)); String replaced = msg.getRequestBody().toString(); replaced = replaced.replace(encoder.getURLEncode(antiCsrfToken.getValue()), encoder.getURLEncode(tokenValue)); msg.setRequestBody(replaced); extAntiCSRF.registerAntiCsrfToken(new AntiCsrfToken(msg, antiCsrfToken.getName(), tokenValue, antiCsrfToken.getFormIndex())); } } @Override public void run() { // ZAP : set skipped to false otherwise the plugin shoud stop continously //this.skipped = false; try { if (!isStop()) { this.started = new Date(); scan(); } } catch (Exception e) { getLog().error(e.getMessage(), e); } notifyPluginCompleted(getParent()); this.finished = new Date(); } /** * The core scan method to be implemented by subclass. */ @Override public abstract void scan(); /** * Generate an alert when a security issue (risk/info) is found. Default * name, description, solution of this Plugin will be used. * * @param risk the risk of the new alert * @param confidence the confidence of the new alert * @param uri the affected URI * @param param the name/ID of the affected parameter * @param attack the attack that shows the issue * @param otherInfo other information about the issue * @param msg the message that shows the issue */ protected void bingo(int risk, int confidence, String uri, String param, String attack, String otherInfo, HttpMessage msg) { bingo(risk, confidence, this.getName(), this.getDescription(), uri, param, attack, otherInfo, this.getSolution(), msg); } /** * Generate an alert when a security issue (risk/info) is found. Custom * alert name, description and solution will be used. * * @param risk the risk of the new alert * @param confidence the confidence of the new alert * @param name the name of the new alert * @param description the description of the new alert * @param uri the affected URI * @param param the name/ID of the affected parameter * @param attack the attack that shows the issue * @param otherInfo other information about the issue * @param solution the solution for the issue * @param msg the message that shows the issue */ protected void bingo(int risk, int confidence, String name, String description, String uri, String param, String attack, String otherInfo, String solution, HttpMessage msg) { log.debug("New alert pluginid=" + +this.getId() + " " + name + " uri=" + uri); Alert alert = new Alert(this.getId(), risk, confidence, name); if (uri == null || uri.equals("")) { uri = msg.getRequestHeader().getURI().toString(); } if (param == null) { param = ""; } alert.setDetail(description, uri, param, attack, otherInfo, solution, this.getReference(), "", this.getCweId(), this.getWascId(), msg); parent.alertFound(alert); } /** * Generate an alert when a security issue (risk/info) is found. Default * name, description, solution of this Plugin will be used. * * @param risk the risk of the new alert * @param confidence the confidence of the new alert * @param uri the affected URI * @param param the name/ID of the affected parameter * @param attack the attack that shows the issue * @param otherInfo other information about the issue * @param evidence the evidence (in the response) that shows the issue * @param msg the message that shows the issue */ protected void bingo(int risk, int confidence, String uri, String param, String attack, String otherInfo, String evidence, HttpMessage msg) { bingo(risk, confidence, this.getName(), this.getDescription(), uri, param, attack, otherInfo, this.getSolution(), evidence, msg); } /** * Generate an alert when a security issue (risk/info) is found. Custom * alert name, description and solution will be used. * * @param risk the risk of the new alert * @param confidence the confidence of the new alert * @param name the name of the new alert * @param description the description of the new alert * @param uri the affected URI * @param param the name/ID of the affected parameter * @param attack the attack that shows the issue * @param otherInfo other information about the issue * @param solution the solution for the issue * @param evidence the evidence (in the response) that shows the issue * @param msg the message that shows the issue */ protected void bingo(int risk, int confidence, String name, String description, String uri, String param, String attack, String otherInfo, String solution, String evidence, HttpMessage msg) { log.debug("New alert pluginid=" + +this.getId() + " " + name + " uri=" + uri); Alert alert = new Alert(this.getId(), risk, confidence, name); if (uri == null || uri.equals("")) { uri = msg.getRequestHeader().getURI().toString(); } if (param == null) { param = ""; } alert.setDetail(description, uri, param, attack, otherInfo, solution, this.getReference(), evidence, this.getCweId(), this.getWascId(), msg); parent.alertFound(alert); } protected void bingo(int risk, int confidence, String name, String description, String uri, String param, String attack, String otherInfo, String solution, String evidence, int cweId, int wascId, HttpMessage msg) { log.debug("New alert pluginid=" + +this.getId() + " " + name + " uri=" + uri); Alert alert = new Alert(this.getId(), risk, confidence, name); if (uri == null || uri.equals("")) { uri = msg.getRequestHeader().getURI().toString(); } if (param == null) { param = ""; } alert.setDetail(description, uri, param, attack, otherInfo, solution, this.getReference(), evidence, cweId, wascId, msg); parent.alertFound(alert); } /** * Tells whether or not the file exists, based on previous analysis. * * @param msg the message that will be checked * @return {@code true} if the file exists, {@code false} otherwise */ protected boolean isFileExist(HttpMessage msg) { return parent.getAnalyser().isFileExist(msg); } /** * Check if this test should be stopped. It should be checked periodically * in Plugin (eg when in loops) so the HostProcess can stop this Plugin * cleanly. * * @return {@code true} if the scanner should stop, {@code false} otherwise */ protected boolean isStop() { // ZAP: added skipping controls return parent.isStop() || parent.isSkipped(this); } @Override public boolean isEnabled() { return enabled; } @Override public boolean isVisible() { return true; } /** * Enable this test */ @Override public void setEnabled(boolean enabled) { if (this.enabled != enabled) { this.enabled = enabled; setProperty("enabled", Boolean.toString(enabled)); if (enabled && getAlertThreshold() == AlertThreshold.OFF) { setAlertThreshold(AlertThreshold.DEFAULT); } } } @Override public AlertThreshold getAlertThreshold() { return this.getAlertThreshold(false); } @Override public AlertThreshold getAlertThreshold(boolean incDefault) { AlertThreshold level = null; try { level = AlertThreshold.valueOf(getProperty("level")); //log.debug("getAlertThreshold from configs: " + level.name()); } catch (Exception e) { // Ignore } if (level == null) { if (this.isEnabled()) { if (incDefault) { level = AlertThreshold.DEFAULT; } else { level = defaultAttackThreshold; } //log.debug("getAlertThreshold default: " + level.name()); } else { level = AlertThreshold.OFF; //log.debug("getAlertThreshold not enabled: " + level.name()); } } else if (level.equals(AlertThreshold.DEFAULT)) { if (incDefault) { level = AlertThreshold.DEFAULT; } else { level = defaultAttackThreshold; } //log.debug("getAlertThreshold default: " + level.name()); } return level; } @Override public void setAlertThreshold(AlertThreshold level) { setProperty("level", level.name()); setEnabled(level != AlertThreshold.OFF); } @Override public void setDefaultAlertThreshold(AlertThreshold level) { this.defaultAttackThreshold = level; } /** * Override this if you plugin supports other levels. */ @Override public AlertThreshold[] getAlertThresholdsSupported() { return alertThresholdsSupported; } @Override public AttackStrength getAttackStrength(boolean incDefault) { AttackStrength level = null; try { level = AttackStrength.valueOf(getProperty("strength")); //log.debug("getAttackStrength from configs: " + level.name()); } catch (Exception e) { // Ignore } if (level == null) { if (incDefault) { level = AttackStrength.DEFAULT; } else { level = this.defaultAttackStrength; } //log.debug("getAttackStrength default: " + level.name()); } else if (level.equals(AttackStrength.DEFAULT)) { if (incDefault) { level = AttackStrength.DEFAULT; } else { level = this.defaultAttackStrength; } //log.debug("getAttackStrength default: " + level.name()); } return level; } @Override public AttackStrength getAttackStrength() { return this.getAttackStrength(false); } @Override public void setAttackStrength(AttackStrength level) { setProperty("strength", level.name()); } @Override public void setDefaultAttackStrength(AttackStrength strength) { this.defaultAttackStrength = strength; } /** * Override this if you plugin supports other levels. */ @Override public AttackStrength[] getAttackStrengthsSupported() { return attackStrengthsSupported; } /** * Compare if 2 plugin is the same. */ @Override public int compareTo(Object obj) { int result = -1; if (obj instanceof AbstractPlugin) { AbstractPlugin test = (AbstractPlugin) obj; if (getId() < test.getId()) { result = -1; } else if (getId() > test.getId()) { result = 1; } else { result = 0; } } return result; } @Override public boolean equals(Object obj) { if (compareTo(obj) == 0) { return true; } return false; } /** * Check if the given pattern can be found in the header. * * @param msg the message that will be checked * @param header the name of the header * @param pattern the pattern that will be used * @return true if the pattern can be found. */ protected boolean matchHeaderPattern(HttpMessage msg, String header, Pattern pattern) { if (msg.getResponseHeader().isEmpty()) { return false; } String val = msg.getResponseHeader().getHeader(header); if (val == null) { return false; } Matcher matcher = pattern.matcher(val); return matcher.find(); } /** * Check if the given pattern can be found in the msg body. If the supplied * StringBuilder is not null, append the result to the StringBuilder. * * @param msg the message that will be checked * @param pattern the pattern that will be used * @param sb where the regex match should be appended * @return true if the pattern can be found. */ protected boolean matchBodyPattern(HttpMessage msg, Pattern pattern, StringBuilder sb) { // ZAP: Changed the type of the parameter "sb" to StringBuilder. Matcher matcher = pattern.matcher(msg.getResponseBody().toString()); boolean result = matcher.find(); if (result) { if (sb != null) { sb.append(matcher.group()); } } return result; } /** * Write a progress update message. Currently this just display in * System.out * * @param msg the progress message */ protected void writeProgress(String msg) { //System.out.println(msg); } /** * Get the parent HostProcess. * * @return the parent HostProcess */ //ZAP: Changed from protected to public access modifier. public HostProcess getParent() { return parent; } @Override public abstract void notifyPluginCompleted(HostProcess parent); /** * Replace body by stripping of pattern string. The URLencoded and * URLdecoded pattern will also be stripped off. This is mainly used for * stripping off a testing string in HTTP response for comparison against * the original response. Reference: TestInjectionSQL * * @param body the body that will be used * @param pattern the pattern used for the removals * @return the body without the pattern */ protected String stripOff(String body, String pattern) { String urlEncodePattern = getURLEncode(pattern); String urlDecodePattern = getURLDecode(pattern); String htmlEncodePattern1 = getHTMLEncode(pattern); String htmlEncodePattern2 = getHTMLEncode(urlEncodePattern); String htmlEncodePattern3 = getHTMLEncode(urlDecodePattern); String result = body.replaceAll("\\Q" + pattern + "\\E", "").replaceAll("\\Q" + urlEncodePattern + "\\E", "").replaceAll("\\Q" + urlDecodePattern + "\\E", ""); result = result.replaceAll("\\Q" + htmlEncodePattern1 + "\\E", "").replaceAll("\\Q" + htmlEncodePattern2 + "\\E", "").replaceAll("\\Q" + htmlEncodePattern3 + "\\E", ""); return result; } public static String getURLEncode(String msg) { String result = ""; try { result = URLEncoder.encode(msg, "UTF8"); } catch (UnsupportedEncodingException ignore) { // Shouldn't happen UTF-8 is a standard Charset (see java.nio.charset.StandardCharsets) } return result; } public static String getURLDecode(String msg) { String result = ""; try { result = URLDecoder.decode(msg, "UTF8"); } catch (UnsupportedEncodingException ignore) { // Shouldn't happen UTF-8 is a standard Charset (see java.nio.charset.StandardCharsets) } return result; } public static String getHTMLEncode(String msg) { String result = msg.replaceAll("<", "<"); result = result.replaceAll(">", ">"); return result; } protected Kb getKb() { return getParent().getKb(); } protected Logger getLog() { return log; } public String getProperty(String key) { return this.getProperty(config, key); } private String getProperty(Configuration conf, String key) { return conf.getString("plugins." + "p" + getId() + "." + key); } public void setProperty(String key, String value) { this.setProperty(config, key, value); } private void setProperty(Configuration conf, String key, String value) { conf.setProperty("plugins." + "p" + getId() + "." + key, value); } @Override public void setConfig(Configuration config) { this.config = config; } @Override public Configuration getConfig() { return config; } @Override public void saveTo(Configuration conf) { setProperty(conf, "enabled", Boolean.toString(enabled)); setProperty(conf, "level", getProperty("level")); setProperty(conf, "strength", getProperty("strength")); } @Override public void loadFrom(Configuration conf) { setProperty("level", getProperty(conf, "level")); setProperty("strength", getProperty(conf, "strength")); String enabledProperty = getProperty(conf, "enabled"); if (enabledProperty != null) { enabled = Boolean.parseBoolean(enabledProperty); } else { enabled = getAlertThreshold() != AlertThreshold.OFF; enabledProperty = Boolean.toString(enabled); } setProperty("enabled", enabledProperty); } @Override public void cloneInto(Plugin plugin) { if (plugin instanceof AbstractPlugin) { AbstractPlugin ap = (AbstractPlugin) plugin; ap.setAlertThreshold(this.getAlertThreshold(true)); ap.setEnabled(this.isEnabled()); ap.setAttackStrength(this.getAttackStrength(true)); ap.setDefaultAlertThreshold(this.defaultAttackThreshold); ap.setDefaultAttackStrength(this.defaultAttackStrength); ap.setTechSet(this.getTechSet()); ap.setStatus(this.getStatus()); ap.saveTo(plugin.getConfig()); } else { throw new InvalidParameterException("Not an AbstractPlugin"); } } /** * Check and create necessary parameter in config file if not already * present. * */ @Override public void createParamIfNotExist() { if (getProperty("enabled") == null) { setEnabled(getAlertThreshold() != AlertThreshold.OFF); } } // ZAP Added isDepreciated @Override public boolean isDepreciated() { return false; } /** * @since 2.2.0 */ @Override public int getRisk() { return Alert.RISK_MEDIUM; } @Override public int getDelayInMs() { return delayInMs; } @Override public void setDelayInMs(int delayInMs) { this.delayInMs = delayInMs; } @Override public boolean inScope(Tech tech) { return this.techSet.includes(tech); } @Override public void setTechSet(TechSet ts) { if (ts == null) { throw new IllegalArgumentException("Parameter ts must not be null."); } this.techSet = ts; } /** * Returns the technologies enabled for the scan. * * @return a {@code TechSet} with the technologies enabled for the scan, never {@code null} (since 2.6.0). * @since 2.4.0 * @see #inScope(Tech) * @see #targets(TechSet) */ public TechSet getTechSet() { return this.techSet; } /** * Returns {@code true} by default. * * @since 2.4.1 * @see #getTechSet() */ @Override public boolean targets(TechSet technologies) { return true; } @Override public Date getTimeStarted() { return this.started; } @Override public Date getTimeFinished() { return this.finished; } @Override public void setTimeStarted() { this.started = new Date(); this.finished = null; } @Override public void setTimeFinished() { this.finished = new Date(); } @Override public int getCweId() { // Default 'unknown' value return 0; } @Override public int getWascId() { // Default 'unknown' value return 0; } @Override public AddOn.Status getStatus() { return status; } public void setStatus(AddOn.Status status) { this.status = status; } }