Java tutorial
/* /* * Zed Attack Proxy (ZAP) and its related class files. * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * * 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.api; import java.awt.EventQueue; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.cert.Certificate; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Vector; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; import org.apache.log4j.Logger; import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator; import org.bouncycastle.util.io.pem.PemWriter; import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.core.proxy.ProxyParam; import org.parosproxy.paros.core.scanner.Alert; import org.parosproxy.paros.db.DatabaseException; import org.parosproxy.paros.db.RecordAlert; import org.parosproxy.paros.db.RecordHistory; import org.parosproxy.paros.db.TableAlert; import org.parosproxy.paros.db.TableHistory; import org.parosproxy.paros.extension.history.ExtensionHistory; import org.parosproxy.paros.extension.report.ReportGenerator; import org.parosproxy.paros.extension.report.ReportLastScan; import org.parosproxy.paros.model.HistoryReference; import org.parosproxy.paros.model.Model; import org.parosproxy.paros.model.Session; import org.parosproxy.paros.model.SessionListener; import org.parosproxy.paros.model.SiteNode; import org.parosproxy.paros.network.HttpHeader; import org.parosproxy.paros.network.HttpMalformedHeaderException; import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.network.HttpRequestHeader; import org.parosproxy.paros.network.HttpSender; import org.parosproxy.paros.network.HttpStatusCode; import org.zaproxy.zap.extension.alert.ExtensionAlert; import org.zaproxy.zap.extension.dynssl.ExtensionDynSSL; import org.zaproxy.zap.model.SessionUtils; import org.zaproxy.zap.utils.HarUtils; import org.zaproxy.zap.utils.Stats; import edu.umass.cs.benchlab.har.HarEntries; import edu.umass.cs.benchlab.har.HarLog; public class CoreAPI extends ApiImplementor implements SessionListener { private static final Logger logger = Logger.getLogger(CoreAPI.class); private enum ScanReportType { HTML, XML } private static final String PREFIX = "core"; private static final String ACTION_LOAD_SESSION = "loadSession"; private static final String ACTION_NEW_SESSION = "newSession"; private static final String ACTION_SAVE_SESSION = "saveSession"; private static final String ACTION_SNAPSHOT_SESSION = "snapshotSession"; private static final String ACTION_SHUTDOWN = "shutdown"; private static final String ACTION_EXCLUDE_FROM_PROXY = "excludeFromProxy"; private static final String ACTION_CLEAR_EXCLUDED_FROM_PROXY = "clearExcludedFromProxy"; private static final String ACTION_SET_HOME_DIRECTORY = "setHomeDirectory"; private static final String ACTION_GENERATE_ROOT_CA = "generateRootCA"; private static final String ACTION_SEND_REQUEST = "sendRequest"; private static final String ACTION_DELETE_ALL_ALERTS = "deleteAllAlerts"; private static final String ACTION_COLLECT_GARBAGE = "runGarbageCollection"; private static final String ACTION_CLEAR_STATS = "clearStats"; private static final String VIEW_ALERT = "alert"; private static final String VIEW_ALERTS = "alerts"; private static final String VIEW_NUMBER_OF_ALERTS = "numberOfAlerts"; private static final String VIEW_HOSTS = "hosts"; private static final String VIEW_SITES = "sites"; private static final String VIEW_URLS = "urls"; private static final String VIEW_MESSAGE = "message"; private static final String VIEW_MESSAGES = "messages"; private static final String VIEW_NUMBER_OF_MESSAGES = "numberOfMessages"; private static final String VIEW_VERSION = "version"; private static final String VIEW_EXCLUDED_FROM_PROXY = "excludedFromProxy"; private static final String VIEW_HOME_DIRECTORY = "homeDirectory"; private static final String VIEW_STATS = "stats"; private static final String OTHER_PROXY_PAC = "proxy.pac"; private static final String OTHER_SET_PROXY = "setproxy"; private static final String OTHER_ROOT_CERT = "rootcert"; private static final String OTHER_XML_REPORT = "xmlreport"; private static final String OTHER_HTML_REPORT = "htmlreport"; private static final String OTHER_MESSAGE_HAR = "messageHar"; private static final String OTHER_MESSAGES_HAR = "messagesHar"; private static final String OTHER_SEND_HAR_REQUEST = "sendHarRequest"; private static final String PARAM_BASE_URL = "baseurl"; private static final String PARAM_COUNT = "count"; private static final String PARAM_DIR = "dir"; private static final String PARAM_SESSION = "name"; private static final String PARAM_OVERWRITE_SESSION = "overwrite"; private static final String PARAM_REGEX = "regex"; private static final String PARAM_START = "start"; private static final String PARAM_PROXY_DETAILS = "proxy"; private static final String PARAM_ID = "id"; private static final String PARAM_REQUEST = "request"; private static final String PARAM_FOLLOW_REDIRECTS = "followRedirects"; private static final String PARAM_KEY_PREFIX = "keyPrefix"; private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss"); private boolean savingSession = false; public CoreAPI() { this.addApiAction(new ApiAction(ACTION_SHUTDOWN)); this.addApiAction( new ApiAction(ACTION_NEW_SESSION, null, new String[] { PARAM_SESSION, PARAM_OVERWRITE_SESSION })); this.addApiAction(new ApiAction(ACTION_LOAD_SESSION, new String[] { PARAM_SESSION })); this.addApiAction(new ApiAction(ACTION_SAVE_SESSION, new String[] { PARAM_SESSION }, new String[] { PARAM_OVERWRITE_SESSION })); this.addApiAction(new ApiAction(ACTION_SNAPSHOT_SESSION)); this.addApiAction(new ApiAction(ACTION_CLEAR_EXCLUDED_FROM_PROXY)); this.addApiAction(new ApiAction(ACTION_EXCLUDE_FROM_PROXY, new String[] { PARAM_REGEX })); this.addApiAction(new ApiAction(ACTION_SET_HOME_DIRECTORY, new String[] { PARAM_DIR })); this.addApiAction(new ApiAction(ACTION_GENERATE_ROOT_CA)); this.addApiAction(new ApiAction(ACTION_SEND_REQUEST, new String[] { PARAM_REQUEST }, new String[] { PARAM_FOLLOW_REDIRECTS })); this.addApiAction(new ApiAction(ACTION_DELETE_ALL_ALERTS)); this.addApiAction(new ApiAction(ACTION_COLLECT_GARBAGE)); this.addApiAction(new ApiAction(ACTION_CLEAR_STATS, new String[] { PARAM_KEY_PREFIX })); 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 })); this.addApiView(new ApiView(VIEW_NUMBER_OF_ALERTS, null, new String[] { PARAM_BASE_URL })); this.addApiView(new ApiView(VIEW_HOSTS)); this.addApiView(new ApiView(VIEW_SITES)); this.addApiView(new ApiView(VIEW_URLS)); this.addApiView(new ApiView(VIEW_MESSAGE, new String[] { PARAM_ID })); this.addApiView( new ApiView(VIEW_MESSAGES, null, new String[] { PARAM_BASE_URL, PARAM_START, PARAM_COUNT })); this.addApiView(new ApiView(VIEW_NUMBER_OF_MESSAGES, null, new String[] { PARAM_BASE_URL })); this.addApiView(new ApiView(VIEW_VERSION)); this.addApiView(new ApiView(VIEW_EXCLUDED_FROM_PROXY)); this.addApiView(new ApiView(VIEW_HOME_DIRECTORY)); this.addApiView(new ApiView(VIEW_STATS, null, new String[] { PARAM_KEY_PREFIX })); this.addApiOthers(new ApiOther(OTHER_PROXY_PAC, false)); this.addApiOthers(new ApiOther(OTHER_ROOT_CERT, false)); this.addApiOthers(new ApiOther(OTHER_SET_PROXY, new String[] { PARAM_PROXY_DETAILS })); this.addApiOthers(new ApiOther(OTHER_XML_REPORT)); this.addApiOthers(new ApiOther(OTHER_HTML_REPORT)); this.addApiOthers(new ApiOther(OTHER_MESSAGE_HAR, new String[] { PARAM_ID })); this.addApiOthers( new ApiOther(OTHER_MESSAGES_HAR, null, new String[] { PARAM_BASE_URL, PARAM_START, PARAM_COUNT })); this.addApiOthers(new ApiOther(OTHER_SEND_HAR_REQUEST, new String[] { PARAM_REQUEST }, new String[] { PARAM_FOLLOW_REDIRECTS })); this.addApiShortcut(OTHER_PROXY_PAC); // this.addApiShortcut(OTHER_ROOT_CERT); this.addApiShortcut(OTHER_SET_PROXY); } @Override public String getPrefix() { return PREFIX; } @Override public ApiResponse handleApiAction(String name, JSONObject params) throws ApiException { Session session = Model.getSingleton().getSession(); if (ACTION_SHUTDOWN.equals(name)) { Thread thread = new Thread() { @Override public void run() { try { // Give the API a chance to return sleep(1000); } catch (InterruptedException e) { // Ignore } Control.getSingleton().shutdown( Model.getSingleton().getOptionsParam().getDatabaseParam().isCompactDatabase()); logger.info(Constant.PROGRAM_TITLE + " terminated."); System.exit(0); } }; thread.start(); } else if (ACTION_SAVE_SESSION.equalsIgnoreCase(name)) { // Ignore case for backwards compatibility Path sessionPath = SessionUtils.getSessionPath(params.getString(PARAM_SESSION)); String filename = sessionPath.toAbsolutePath().toString(); final boolean overwrite = getParam(params, PARAM_OVERWRITE_SESSION, false); boolean sameSession = false; if (!session.isNewState()) { try { sameSession = Files.isSameFile(Paths.get(session.getFileName()), sessionPath); } catch (IOException e) { throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); } } if (Files.exists(sessionPath) && (!overwrite || sameSession)) { throw new ApiException(ApiException.Type.ALREADY_EXISTS, filename); } this.savingSession = true; try { Control.getSingleton().saveSession(filename, this); } catch (Exception e) { this.savingSession = false; throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); } // Wait for notification that its worked ok try { while (this.savingSession) { Thread.sleep(200); } } catch (InterruptedException e) { // Probably not an error logger.debug(e.getMessage(), e); } logger.debug("Can now return after saving session"); } else if (ACTION_SNAPSHOT_SESSION.equalsIgnoreCase(name)) { // Ignore case for backwards compatibility if (session.isNewState()) { throw new ApiException(ApiException.Type.DOES_NOT_EXIST); } String fileName = session.getFileName(); if (fileName.endsWith(".session")) { fileName = fileName.substring(0, fileName.length() - 8); } fileName += "-" + dateFormat.format(new Date()) + ".session"; this.savingSession = true; try { Control.getSingleton().snapshotSession(fileName, this); } catch (Exception e) { this.savingSession = false; throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); } // Wait for notification that its worked ok try { while (this.savingSession) { Thread.sleep(200); } } catch (InterruptedException e) { // Probably not an error logger.debug(e.getMessage(), e); } logger.debug("Can now return after saving session"); } else if (ACTION_LOAD_SESSION.equalsIgnoreCase(name)) { // Ignore case for backwards compatibility Path sessionPath = SessionUtils.getSessionPath(params.getString(PARAM_SESSION)); String filename = sessionPath.toAbsolutePath().toString(); if (!Files.exists(sessionPath)) { throw new ApiException(ApiException.Type.DOES_NOT_EXIST, filename); } try { Control.getSingleton().runCommandLineOpenSession(filename); } catch (Exception e) { throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); } } else if (ACTION_NEW_SESSION.equalsIgnoreCase(name)) { // Ignore case for backwards compatibility String sessionName = null; try { sessionName = params.getString(PARAM_SESSION); } catch (Exception e1) { // Ignore } if (sessionName == null || sessionName.length() == 0) { // Create a new 'unnamed' session Control.getSingleton().discardSession(); try { Control.getSingleton().createAndOpenUntitledDb(); } catch (Exception e) { throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); } Control.getSingleton().newSession(); } else { Path sessionPath = SessionUtils.getSessionPath(sessionName); String filename = sessionPath.toAbsolutePath().toString(); final boolean overwrite = getParam(params, PARAM_OVERWRITE_SESSION, false); if (Files.exists(sessionPath) && !overwrite) { throw new ApiException(ApiException.Type.ALREADY_EXISTS, filename); } try { Control.getSingleton().runCommandLineNewSession(filename); } catch (Exception e) { throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); } } } else if (ACTION_CLEAR_EXCLUDED_FROM_PROXY.equals(name)) { try { session.setExcludeFromProxyRegexs(new ArrayList<String>()); } catch (DatabaseException e) { throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); } } else if (ACTION_EXCLUDE_FROM_PROXY.equals(name)) { String regex = params.getString(PARAM_REGEX); try { session.addExcludeFromProxyRegex(regex); } catch (DatabaseException e) { logger.error(e.getMessage(), e); throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); } catch (PatternSyntaxException e) { throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, PARAM_REGEX); } } else if (ACTION_SET_HOME_DIRECTORY.equals(name)) { File f = new File(params.getString(PARAM_DIR)); if (f.exists() && f.isDirectory()) { Model.getSingleton().getOptionsParam().setUserDirectory(f); } else { throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, PARAM_DIR); } } else if (ACTION_GENERATE_ROOT_CA.equals(name)) { ExtensionDynSSL extDyn = (ExtensionDynSSL) Control.getSingleton().getExtensionLoader() .getExtension(ExtensionDynSSL.EXTENSION_ID); if (extDyn != null) { try { extDyn.createNewRootCa(); } catch (Exception e) { throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); } } } else if (ACTION_SEND_REQUEST.equals(name)) { HttpMessage request; try { request = createRequest(params.getString(PARAM_REQUEST)); } catch (HttpMalformedHeaderException e) { throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, PARAM_REQUEST, e); } boolean followRedirects = getParam(params, PARAM_FOLLOW_REDIRECTS, false); final ApiResponseList resultList = new ApiResponseList(name); try { sendRequest(request, followRedirects, new Processor<HttpMessage>() { @Override public void process(HttpMessage msg) { int id = msg.getHistoryRef() != null ? msg.getHistoryRef().getHistoryId() : -1; resultList.addItem(ApiResponseConversionUtils.httpMessageToSet(id, msg)); } }); return resultList; } catch (Exception e) { throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); } } else if (ACTION_DELETE_ALL_ALERTS.equals(name)) { final ExtensionAlert extAlert = (ExtensionAlert) Control.getSingleton().getExtensionLoader() .getExtension(ExtensionAlert.NAME); if (extAlert != null) { extAlert.deleteAllAlerts(); } else { try { Model.getSingleton().getDb().getTableAlert().deleteAllAlerts(); } catch (DatabaseException e) { logger.error(e.getMessage(), e); } SiteNode rootNode = (SiteNode) Model.getSingleton().getSession().getSiteTree().getRoot(); rootNode.deleteAllAlerts(); removeHistoryReferenceAlerts(rootNode); } } else if (ACTION_COLLECT_GARBAGE.equals(name)) { System.gc(); return ApiResponseElement.OK; } else if (ACTION_CLEAR_STATS.equals(name)) { Stats.clear(this.getParam(params, PARAM_KEY_PREFIX, "")); return ApiResponseElement.OK; } else { throw new ApiException(ApiException.Type.BAD_ACTION); } return ApiResponseElement.OK; } private static HttpMessage createRequest(String request) throws HttpMalformedHeaderException { HttpMessage requestMsg = new HttpMessage(); String[] parts = request.split(Pattern.quote(HttpHeader.CRLF + HttpHeader.CRLF), 2); requestMsg.setRequestHeader(parts[0]); if (parts.length > 1) { requestMsg.setRequestBody(parts[1]); } else { requestMsg.setRequestBody(""); } return requestMsg; } private static void sendRequest(HttpMessage request, boolean followRedirects, Processor<HttpMessage> processor) throws IOException { HttpSender sender = null; try { sender = createHttpSender(); sender.sendAndReceive(request); persistMessage(request); processor.process(request); if (followRedirects) { HttpMessage tempReq = request; for (int i = 0; i < 10 && HttpStatusCode.isRedirection(tempReq.getResponseHeader().getStatusCode()); i++) { tempReq = tempReq.cloneAll(); String location = tempReq.getResponseHeader().getHeader(HttpHeader.LOCATION); URI baseUri = tempReq.getRequestHeader().getURI(); URI newLocation = new URI(baseUri, location, false); tempReq.getRequestHeader().setURI(newLocation); tempReq.getRequestHeader().setMethod(HttpRequestHeader.GET); tempReq.getRequestHeader().setHeader(HttpHeader.CONTENT_LENGTH, null); sender.sendAndReceive(tempReq); persistMessage(tempReq); processor.process(tempReq); } } } finally { if (sender != null) { sender.shutdown(); } } } private static HttpSender createHttpSender() { return new HttpSender(Model.getSingleton().getOptionsParam().getConnectionParam(), true, HttpSender.MANUAL_REQUEST_INITIATOR); } private static void persistMessage(final HttpMessage message) { final HistoryReference historyRef; try { historyRef = new HistoryReference(Model.getSingleton().getSession(), HistoryReference.TYPE_ZAP_USER, message); } catch (Exception e) { logger.warn(e.getMessage(), e); return; } final ExtensionHistory extHistory = (ExtensionHistory) Control.getSingleton().getExtensionLoader() .getExtension(ExtensionHistory.NAME); if (extHistory != null) { EventQueue.invokeLater(new Runnable() { @Override public void run() { extHistory.addHistory(historyRef); Model.getSingleton().getSession().getSiteTree().addPath(historyRef, message); } }); } } private static void removeHistoryReferenceAlerts(SiteNode siteNode) { for (int i = 0; i < siteNode.getChildCount(); i++) { removeHistoryReferenceAlerts((SiteNode) siteNode.getChildAt(i)); } if (siteNode.getHistoryReference() != null) { siteNode.getHistoryReference().deleteAllAlerts(); } for (HistoryReference hRef : siteNode.getPastHistoryReference()) { hRef.deleteAllAlerts(); } } @Override public ApiResponse handleApiView(String name, JSONObject params) throws ApiException { ApiResponse result = null; Session session = Model.getSingleton().getSession(); if (VIEW_HOSTS.equals(name)) { result = new ApiResponseList(name); SiteNode root = (SiteNode) session.getSiteTree().getRoot(); @SuppressWarnings("unchecked") Enumeration<SiteNode> en = root.children(); while (en.hasMoreElements()) { String site = en.nextElement().getNodeName(); if (site.indexOf("//") >= 0) { site = site.substring(site.indexOf("//") + 2); } if (site.indexOf(":") >= 0) { site = site.substring(0, site.indexOf(":")); } ((ApiResponseList) result).addItem(new ApiResponseElement("host", site)); } } else if (VIEW_SITES.equals(name)) { result = new ApiResponseList(name); SiteNode root = (SiteNode) session.getSiteTree().getRoot(); @SuppressWarnings("unchecked") Enumeration<SiteNode> en = root.children(); while (en.hasMoreElements()) { ((ApiResponseList) result).addItem(new ApiResponseElement("site", en.nextElement().getNodeName())); } } else if (VIEW_URLS.equals(name)) { result = new ApiResponseList(name); SiteNode root = (SiteNode) session.getSiteTree().getRoot(); this.getURLs(root, (ApiResponseList) result); } else if (VIEW_ALERT.equals(name)) { TableAlert tableAlert = Model.getSingleton().getDb().getTableAlert(); RecordAlert recordAlert; try { recordAlert = tableAlert.read(this.getParam(params, PARAM_ID, -1)); } catch (DatabaseException e) { throw new ApiException(ApiException.Type.INTERNAL_ERROR); } if (recordAlert == null) { throw new ApiException(ApiException.Type.DOES_NOT_EXIST); } result = new ApiResponseElement(alertToSet(new Alert(recordAlert))); } else if (VIEW_ALERTS.equals(name)) { final ApiResponseList resultList = new ApiResponseList(name); processAlerts(this.getParam(params, PARAM_BASE_URL, (String) null), this.getParam(params, PARAM_START, -1), this.getParam(params, PARAM_COUNT, -1), new Processor<Alert>() { @Override public void process(Alert alert) { resultList.addItem(alertToSet(alert)); } }); result = resultList; } else if (VIEW_NUMBER_OF_ALERTS.equals(name)) { CounterProcessor<Alert> counter = new CounterProcessor<>(); processAlerts(this.getParam(params, PARAM_BASE_URL, (String) null), this.getParam(params, PARAM_START, -1), this.getParam(params, PARAM_COUNT, -1), counter); result = new ApiResponseElement(name, Integer.toString(counter.getCount())); } else if (VIEW_MESSAGE.equals(name)) { TableHistory tableHistory = Model.getSingleton().getDb().getTableHistory(); RecordHistory recordHistory; try { recordHistory = tableHistory.read(this.getParam(params, PARAM_ID, -1)); } catch (HttpMalformedHeaderException | DatabaseException e) { throw new ApiException(ApiException.Type.INTERNAL_ERROR); } if (recordHistory == null || recordHistory.getHistoryType() == HistoryReference.TYPE_TEMPORARY) { throw new ApiException(ApiException.Type.DOES_NOT_EXIST); } result = new ApiResponseElement(ApiResponseConversionUtils .httpMessageToSet(recordHistory.getHistoryId(), recordHistory.getHttpMessage())); } else if (VIEW_MESSAGES.equals(name)) { final ApiResponseList resultList = new ApiResponseList(name); processHttpMessages(this.getParam(params, PARAM_BASE_URL, (String) null), this.getParam(params, PARAM_START, -1), this.getParam(params, PARAM_COUNT, -1), new Processor<RecordHistory>() { @Override public void process(RecordHistory recordHistory) { resultList.addItem(ApiResponseConversionUtils.httpMessageToSet( recordHistory.getHistoryId(), recordHistory.getHttpMessage())); } }); result = resultList; } else if (VIEW_NUMBER_OF_MESSAGES.equals(name)) { CounterProcessor<RecordHistory> counter = new CounterProcessor<>(); processHttpMessages(this.getParam(params, PARAM_BASE_URL, (String) null), this.getParam(params, PARAM_START, -1), this.getParam(params, PARAM_COUNT, -1), counter); result = new ApiResponseElement(name, Integer.toString(counter.getCount())); } else if (VIEW_VERSION.equals(name)) { result = new ApiResponseElement(name, Constant.PROGRAM_VERSION); } else if (VIEW_EXCLUDED_FROM_PROXY.equals(name)) { result = new ApiResponseList(name); List<String> regexs = session.getExcludeFromProxyRegexs(); for (String regex : regexs) { ((ApiResponseList) result).addItem(new ApiResponseElement("regex", regex)); } } else if (VIEW_HOME_DIRECTORY.equals(name)) { result = new ApiResponseElement(name, Model.getSingleton().getOptionsParam().getUserDirectory().getAbsolutePath()); } else if (VIEW_STATS.equals(name)) { Map<String, String> map = new HashMap<>(); for (Entry<String, Long> stat : Stats.getStats(this.getParam(params, PARAM_KEY_PREFIX, "")) .entrySet()) { map.put(stat.getKey(), Long.toString(stat.getValue())); } result = new ApiResponseSet(name, map); } else { throw new ApiException(ApiException.Type.BAD_VIEW); } return result; } @Override public HttpMessage handleApiOther(HttpMessage msg, String name, JSONObject params) throws ApiException { if (OTHER_PROXY_PAC.equals(name)) { final ProxyParam proxyParam = Model.getSingleton().getOptionsParam().getProxyParam(); final int port = proxyParam.getProxyPort(); try { String domain = null; if (proxyParam.isProxyIpAnyLocalAddress()) { String localDomain = msg.getRequestHeader().getHostName(); if (!API.API_DOMAIN.equals(localDomain)) { domain = localDomain; } } if (domain == null) { domain = proxyParam.getProxyIp(); } String response = this.getPacFile(domain, port); msg.setResponseHeader(API.getDefaultResponseHeader("text/html", response.length())); msg.setResponseBody(response); } catch (Exception e) { logger.error(e.getMessage(), e); } return msg; } else if (OTHER_SET_PROXY.equals(name)) { /* JSON string: * {"type":1, * "http": {"host":"proxy.corp.com","port":80}, * "ssl": {"host":"proxy.corp.com","port":80}, * "ftp":{"host":"proxy.corp.com","port":80}, * "socks":{"host":"proxy.corp.com","port":80}, * "shareSettings":true,"socksVersion":5, * "proxyExcludes":"localhost, 127.0.0.1"} */ String proxyDetails = params.getString(PARAM_PROXY_DETAILS); String response = "OK"; try { try { JSONObject json = JSONObject.fromObject(proxyDetails); if (json.getInt("type") == 1) { JSONObject httpJson = JSONObject.fromObject(json.get("http")); String proxyHost = httpJson.getString("host"); int proxyPort = httpJson.getInt("port"); if (proxyHost != null && proxyHost.length() > 0 && proxyPort > 0) { Model.getSingleton().getOptionsParam().getConnectionParam() .setProxyChainName(proxyHost); Model.getSingleton().getOptionsParam().getConnectionParam() .setProxyChainPort(proxyPort); } } } catch (JSONException e) { throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, PARAM_PROXY_DETAILS); } msg.setResponseHeader(API.getDefaultResponseHeader("text/html", response.length())); msg.setResponseBody(response); } catch (Exception e) { logger.error(e.getMessage(), e); } return msg; } else if (OTHER_ROOT_CERT.equals(name)) { ExtensionDynSSL extDynSSL = (ExtensionDynSSL) Control.getSingleton().getExtensionLoader() .getExtension(ExtensionDynSSL.EXTENSION_ID); if (extDynSSL != null) { try { Certificate rootCA = extDynSSL.getRootCA(); if (rootCA == null) { throw new ApiException(ApiException.Type.DOES_NOT_EXIST); } final StringWriter sw = new StringWriter(); try (final PemWriter pw = new PemWriter(sw)) { pw.writeObject(new JcaMiscPEMGenerator(rootCA)); pw.flush(); } String response = sw.toString(); msg.setResponseHeader( API.getDefaultResponseHeader("application/pkix-cert;", response.length())); msg.setResponseBody(response); } catch (Exception e) { logger.error(e.getMessage(), e); throw new ApiException(ApiException.Type.INTERNAL_ERROR); } } else { throw new ApiException(ApiException.Type.DOES_NOT_EXIST); } return msg; } else if (OTHER_XML_REPORT.equals(name)) { try { writeReportLastScanTo(msg, ScanReportType.XML); return msg; } catch (Exception e) { logger.error(e.getMessage(), e); throw new ApiException(ApiException.Type.INTERNAL_ERROR); } } else if (OTHER_HTML_REPORT.equals(name)) { try { writeReportLastScanTo(msg, ScanReportType.HTML); return msg; } catch (Exception e) { logger.error(e.getMessage(), e); throw new ApiException(ApiException.Type.INTERNAL_ERROR); } } else if (OTHER_MESSAGE_HAR.equals(name)) { byte[] responseBody; try { final HarEntries entries = new HarEntries(); TableHistory tableHistory = Model.getSingleton().getDb().getTableHistory(); RecordHistory recordHistory; try { recordHistory = tableHistory.read(this.getParam(params, PARAM_ID, -1)); } catch (HttpMalformedHeaderException | DatabaseException e) { throw new ApiException(ApiException.Type.INTERNAL_ERROR); } if (recordHistory == null || recordHistory.getHistoryType() == HistoryReference.TYPE_TEMPORARY) { throw new ApiException(ApiException.Type.DOES_NOT_EXIST); } entries.addEntry(HarUtils.createHarEntry(recordHistory.getHttpMessage())); HarLog harLog = HarUtils.createZapHarLog(); harLog.setEntries(entries); responseBody = HarUtils.harLogToByteArray(harLog); } catch (Exception e) { logger.error(e.getMessage(), e); ApiException apiException = new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); responseBody = apiException.toString(API.Format.JSON, incErrorDetails()) .getBytes(StandardCharsets.UTF_8); } try { msg.setResponseHeader( API.getDefaultResponseHeader("application/json; charset=UTF-8", responseBody.length)); } catch (HttpMalformedHeaderException e) { logger.error("Failed to create response header: " + e.getMessage(), e); } msg.setResponseBody(responseBody); return msg; } else if (OTHER_MESSAGES_HAR.equals(name)) { byte[] responseBody; try { final HarEntries entries = new HarEntries(); processHttpMessages(this.getParam(params, PARAM_BASE_URL, (String) null), this.getParam(params, PARAM_START, -1), this.getParam(params, PARAM_COUNT, -1), new Processor<RecordHistory>() { @Override public void process(RecordHistory recordHistory) { entries.addEntry(HarUtils.createHarEntry(recordHistory.getHttpMessage())); } }); HarLog harLog = HarUtils.createZapHarLog(); harLog.setEntries(entries); responseBody = HarUtils.harLogToByteArray(harLog); } catch (Exception e) { logger.error(e.getMessage(), e); ApiException apiException = new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); responseBody = apiException.toString(API.Format.JSON, incErrorDetails()) .getBytes(StandardCharsets.UTF_8); } try { msg.setResponseHeader( API.getDefaultResponseHeader("application/json; charset=UTF-8", responseBody.length)); } catch (HttpMalformedHeaderException e) { logger.error("Failed to create response header: " + e.getMessage(), e); } msg.setResponseBody(responseBody); return msg; } else if (OTHER_SEND_HAR_REQUEST.equals(name)) { byte[] responseBody = {}; HttpMessage request = null; try { request = HarUtils.createHttpMessage(params.getString(PARAM_REQUEST)); } catch (IOException e) { ApiException apiException = new ApiException(ApiException.Type.ILLEGAL_PARAMETER, PARAM_REQUEST, e); responseBody = apiException.toString(API.Format.JSON, incErrorDetails()) .getBytes(StandardCharsets.UTF_8); msg.setResponseBody(responseBody); } if (request != null) { boolean followRedirects = getParam(params, PARAM_FOLLOW_REDIRECTS, false); try { final HarEntries entries = new HarEntries(); sendRequest(request, followRedirects, new Processor<HttpMessage>() { @Override public void process(HttpMessage msg) { entries.addEntry(HarUtils.createHarEntry(msg)); } }); HarLog harLog = HarUtils.createZapHarLog(); harLog.setEntries(entries); responseBody = HarUtils.harLogToByteArray(harLog); } catch (Exception e) { logger.error(e.getMessage(), e); ApiException apiException = new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage()); responseBody = apiException.toString(API.Format.JSON, incErrorDetails()) .getBytes(StandardCharsets.UTF_8); } } try { msg.setResponseHeader( API.getDefaultResponseHeader("application/json; charset=UTF-8", responseBody.length)); } catch (HttpMalformedHeaderException e) { logger.error("Failed to create response header: " + e.getMessage(), e); } msg.setResponseBody(responseBody); return msg; } else { throw new ApiException(ApiException.Type.BAD_OTHER); } } private boolean incErrorDetails() { return Model.getSingleton().getOptionsParam().getApiParam().isIncErrorDetails(); } private static void writeReportLastScanTo(HttpMessage msg, ScanReportType reportType) throws Exception { ReportLastScan rls = new ReportLastScan(); StringBuilder report = new StringBuilder(); rls.generate(report, Model.getSingleton()); String response; if (ScanReportType.XML == reportType) { // Copy as is msg.setResponseHeader(API.getDefaultResponseHeader("text/xml; charset=UTF-8")); response = report.toString(); } else { msg.setResponseHeader(API.getDefaultResponseHeader("text/html; charset=UTF-8")); response = ReportGenerator.stringToHtml(report.toString(), Paths.get(Constant.getZapInstall(), "xml/report.html.xsl").toString()); } msg.setResponseBody(response); msg.getResponseHeader().setContentLength(msg.getResponseBody().length()); } @Override public HttpMessage handleShortcut(HttpMessage msg) throws ApiException { try { if (msg.getRequestHeader().getURI().getPath().startsWith("/" + OTHER_PROXY_PAC)) { return this.handleApiOther(msg, OTHER_PROXY_PAC, null); } else if (msg.getRequestHeader().getURI().getPath().startsWith("/" + OTHER_SET_PROXY)) { JSONObject params = new JSONObject(); params.put(PARAM_PROXY_DETAILS, msg.getRequestBody()); return this.handleApiOther(msg, OTHER_SET_PROXY, params); } } catch (URIException e) { logger.error(e.getMessage(), e); throw new ApiException(ApiException.Type.INTERNAL_ERROR); } throw new ApiException(ApiException.Type.URL_NOT_FOUND, msg.getRequestHeader().getURI().toString()); } private String getPacFile(String host, int port) { // Could put in 'ignore urls'? StringBuilder sb = new StringBuilder(100); sb.append("function FindProxyForURL(url, host) {\n"); sb.append(" return \"PROXY ").append(host).append(':').append(port).append("\";\n"); sb.append("} // End of function\n"); return sb.toString(); } private void getURLs(SiteNode parent, ApiResponseList list) { @SuppressWarnings("unchecked") Enumeration<SiteNode> en = parent.children(); while (en.hasMoreElements()) { SiteNode child = en.nextElement(); String site = child.getNodeName(); if (site.indexOf("//") >= 0) { site = site.substring(site.indexOf("//") + 2); } try { list.addItem(new ApiResponseElement("url", child.getHistoryReference().getURI().toString())); } catch (Exception e) { logger.error(e.getMessage(), e); } getURLs(child, list); } } private ApiResponseSet alertToSet(Alert alert) { Map<String, String> map = new HashMap<>(); map.put("id", String.valueOf(alert.getAlertId())); map.put("alert", alert.getAlert()); map.put("description", alert.getDescription()); map.put("risk", Alert.MSG_RISK[alert.getRisk()]); map.put("confidence", Alert.MSG_CONFIDENCE[alert.getConfidence()]); map.put("url", alert.getUri()); map.put("other", alert.getOtherInfo()); map.put("param", alert.getParam()); map.put("attack", alert.getAttack()); map.put("evidence", alert.getEvidence()); map.put("reference", alert.getReference()); map.put("cweid", String.valueOf(alert.getCweId())); map.put("wascid", String.valueOf(alert.getWascId())); map.put("solution", alert.getSolution()); if (alert.getHistoryRef() != null) { map.put("messageId", String.valueOf(alert.getHistoryRef().getHistoryId())); } return new ApiResponseSet("alert", map); } private void processAlerts(String baseUrl, int start, int count, Processor<Alert> processor) throws ApiException { List<Alert> alerts = new ArrayList<>(); try { TableAlert tableAlert = Model.getSingleton().getDb().getTableAlert(); // TODO this doesnt work, but should be used when its fixed :/ //Vector<Integer> v = tableAlert.getAlertListBySession(Model.getSingleton().getSession().getSessionId()); Vector<Integer> v = tableAlert.getAlertList(); PaginationConstraintsChecker pcc = new PaginationConstraintsChecker(start, count); for (int i = 0; i < v.size(); i++) { int alertId = v.get(i).intValue(); 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; } pcc.recordProcessed(); alerts.add(alert); if (!pcc.hasPageStarted()) { continue; } processor.process(alert); if (pcc.hasPageEnded()) { break; } } } } catch (DatabaseException e) { logger.error(e.getMessage(), e); throw new ApiException(ApiException.Type.INTERNAL_ERROR); } } private void processHttpMessages(String baseUrl, int start, int count, Processor<RecordHistory> processor) throws ApiException { try { TableHistory tableHistory = Model.getSingleton().getDb().getTableHistory(); List<Integer> historyIds = tableHistory.getHistoryIdsExceptOfHistType( Model.getSingleton().getSession().getSessionId(), HistoryReference.TYPE_TEMPORARY); PaginationConstraintsChecker pcc = new PaginationConstraintsChecker(start, count); for (Integer id : historyIds) { RecordHistory recHistory = tableHistory.read(id.intValue()); HttpMessage msg = recHistory.getHttpMessage(); if (msg.getRequestHeader().isImage() || msg.getResponseHeader().isImage()) { continue; } if (baseUrl != null && !msg.getRequestHeader().getURI().toString().startsWith(baseUrl)) { // Not subordinate to the specified URL continue; } pcc.recordProcessed(); if (!pcc.hasPageStarted()) { continue; } processor.process(recHistory); if (pcc.hasPageEnded()) { break; } } } catch (HttpMalformedHeaderException | DatabaseException e) { logger.error(e.getMessage(), e); throw new ApiException(ApiException.Type.INTERNAL_ERROR); } } private interface Processor<T> { void process(T object); } private static class CounterProcessor<T> implements Processor<T> { private int count; public CounterProcessor() { count = 0; } @Override public void process(T object) { ++count; } public int getCount() { return count; } } @Override public void sessionOpened(File file, Exception e) { // Ignore } @Override public void sessionSaved(Exception e) { logger.debug("Saved session notification"); this.savingSession = false; } @Override public void sessionSnapshot(Exception e) { logger.debug("Snaphot session notification"); this.savingSession = false; } private static class PaginationConstraintsChecker { private boolean pageStarted; private boolean pageEnded; private final int startRecord; private final boolean hasEnd; private final int finalRecord; private int recordsProcessed; public PaginationConstraintsChecker(int start, int count) { recordsProcessed = 0; if (start > 0) { pageStarted = false; startRecord = start; } else { pageStarted = true; startRecord = 0; } if (count > 0) { hasEnd = true; finalRecord = !pageStarted ? start + count - 1 : count; } else { hasEnd = false; finalRecord = 0; } pageEnded = false; } public void recordProcessed() { ++recordsProcessed; if (!pageStarted) { pageStarted = recordsProcessed >= startRecord; } if (hasEnd && !pageEnded) { pageEnded = recordsProcessed >= finalRecord; } } public boolean hasPageStarted() { return pageStarted; } public boolean hasPageEnded() { return pageEnded; } } }