diff --git a/AUTHORS b/AUTHORS index 487ad43f1e6..84d7816dfc5 100755 --- a/AUTHORS +++ b/AUTHORS @@ -9,9 +9,12 @@ The PRIMARY AUTHORS are: * Franco Linares * German Riera * Joaquín López Pereyra + * Leonardo Lazzaro * Martín Rocha * Matias Ariel Ré Medina + * Matias Lang * Micaela Ranea Sánchez + * Sebastian Kulesz Project contributors @@ -20,14 +23,15 @@ Project contributors * Andres Tarantini * Brice Samulenok * Elian Gidoni + * Endrigo Antonini * Federico Fernandez * James Jara + * Jorge Luis Gonzalez Iznaga * Juan Urbano * Korantin Auguste * Martin Tartarelli * Ronald Iraheta * Roberto Focke - * Sebastian Kulesz * Sliim * Thierry Beauquier * tsxltjecwb diff --git a/RELEASE.md b/RELEASE.md index 2d89f01d4e2..eab3833ae12 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -8,6 +8,19 @@ Make sure you run ```./faraday.py --update``` the first time after an update! New features in the latest update ===================================== +July 19, 2017: +--- +* Added the ability to select more than one target when creating a vuln in the Web UI +* Merged PR #182 - problems with zonatransfer.me +* Fixed bug in Download CSV of Status report with old versions of Firefox. +* Fixed formula injection vulnerability in export to CSV feature +* Fixed DOM-based XSS in the Top Services widget of the dashboard +* Fix in AppScan plugin. +* Fix HTML injection in Vulnerability template. +* Add new plugin: Junit XML +* Improved pagination in new vuln modal of status report +* Added "Policy Violations" field for Vulnerabilities + May 24, 2017: --- * Fixed bug when editing workspaces created in GTK diff --git a/VERSION b/VERSION index 437459cd94c..e70b4523ae7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5.0 +2.6.0 diff --git a/config/default.xml b/config/default.xml index bc492f1033c..ee2f3f405e6 100644 --- a/config/default.xml +++ b/config/default.xml @@ -2,7 +2,7 @@ Faraday - Penetration Test IDE - 2.5.0 + 2.6.0 0 -Misc-Fixed-medium-r-normal-*-12-100-100-100-c-70-iso8859-1 ~/ diff --git a/faraday-server.py b/faraday-server.py index 6f462b5cd34..646a3993fa8 100755 --- a/faraday-server.py +++ b/faraday-server.py @@ -2,10 +2,10 @@ # Faraday Penetration Test IDE # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) # See the file 'doc/LICENSE' for the license information -import argparse import os -import subprocess import sys +import argparse +import subprocess import server.config import server.couchdb @@ -13,6 +13,7 @@ from server.utils import daemonize from utils import dependencies from utils.user_input import query_yes_no +from faraday import FARADAY_BASE logger = server.utils.logger.get_logger(__name__) @@ -87,6 +88,7 @@ def run_server(args): def main(): + os.chdir(FARADAY_BASE) parser = argparse.ArgumentParser() parser.add_argument('--ssl', action='store_true', help='enable HTTPS') parser.add_argument('--debug', action='store_true', help='run Faraday Server in debug mode') diff --git a/persistence/server/models.py b/persistence/server/models.py index 3f84abc7d36..d0808f0a0b4 100644 --- a/persistence/server/models.py +++ b/persistence/server/models.py @@ -1029,6 +1029,7 @@ def __init__(self, vuln, workspace_name): self.confirmed = vuln.get('confirmed', False) self.resolution = vuln.get('resolution') self.status = vuln.get('status', "opened") + self.policyviolations = vuln.get('policyviolations', list()) def setID(self, parent_id): ModelBase.setID(self, parent_id, self.name, self.description) @@ -1100,7 +1101,7 @@ def align_string_based_vulns(severity): return severity def updateAttributes(self, name=None, desc=None, data=None, - severity=None, resolution=None, refs=None, status=None): + severity=None, resolution=None, refs=None, status=None, policyviolations=None): if name is not None: self.name = name if desc is not None: @@ -1115,6 +1116,8 @@ def updateAttributes(self, name=None, desc=None, data=None, self.refs = refs if status is not None: self.setStatus(status) + if policyviolations is not None: + self.policyviolations = policyviolations def getID(self): return self.id def getDesc(self): return self.desc @@ -1124,6 +1127,7 @@ def getRefs(self): return self.refs def getConfirmed(self): return self.confirmed def getResolution(self): return self.resolution def getStatus(self): return self.status + def getPolicyViolations(self): return self.policyviolations def setStatus(self, status): self.status = status @@ -1155,6 +1159,7 @@ def __init__(self, vuln_web, workspace_name): self.tags = vuln_web.get('tags') self.target = vuln_web.get('target') self.parent = vuln_web.get('parent') + self.policyviolations = vuln_web.get('policyviolations', list()) def setID(self, parent_id): ModelBase.setID(self, parent_id, self.name, self.website, self.path, self.description) @@ -1178,7 +1183,7 @@ def publicattrsrefs(): def updateAttributes(self, name=None, desc=None, data=None, website=None, path=None, refs=None, severity=None, resolution=None, request=None,response=None, method=None, - pname=None, params=None, query=None, category=None, status=None): + pname=None, params=None, query=None, category=None, status=None, policyviolations=None): super(self.__class__, self).updateAttributes(name, desc, data, severity, resolution, refs, status) @@ -1200,6 +1205,8 @@ def updateAttributes(self, name=None, desc=None, data=None, website=None, path=N self.query = query if category is not None: self.category = category + if policyviolations is not None: + self.policyviolations = policyviolations def getDescription(self): return self.description def getPath(self): return self.path @@ -1220,6 +1227,7 @@ def getStatus(self): return self.status def getTags(self): return self.tags def getTarget(self): return self.target def getParent(self): return self.parent + def getPolicyViolations(self): return self.policyviolations def tieBreakable(self, key): """ diff --git a/plugins/repo/appscan/plugin.py b/plugins/repo/appscan/plugin.py index 266721d3511..9e2e78d3a35 100644 --- a/plugins/repo/appscan/plugin.py +++ b/plugins/repo/appscan/plugin.py @@ -44,7 +44,6 @@ def __init__(self, output): self.obj_xml = objectify.fromstring(output) def parse_issues(self): - for issue in self.obj_xml["issue-type-group"]["item"]: url_list = [] obj_issue = {} @@ -52,7 +51,7 @@ def parse_issues(self): obj_issue["name"] = issue["name"].text obj_issue['advisory'] = issue["advisory"]["ref"].text - if(issue["cve"]): + if("cve" in issue): obj_issue['cve'] = issue["cve"].text for threat in self.obj_xml["url-group"]["item"]: @@ -65,7 +64,7 @@ def parse_issues(self): for item in self.obj_xml["issue-group"]["item"]: if int(item["url"]["ref"]) == int(threat.get('id')): - if item["issue-type"]["ref"] == threat['issue-type']: + if "test-http-traffic" in item["variant-group"]["item"] and item["issue-type"]["ref"] == threat['issue-type']: http_traffic = item["variant-group"]["item"]["test-http-traffic"].text.split("\n\n") @@ -76,7 +75,8 @@ def parse_issues(self): obj_issue["severity"] = item["severity"].text obj_issue["cvss_score"] = item["cvss-score"].text - obj_issue["issue_description"] = item["variant-group"]["item"]["issue-information"]["issue-tip"].text + if ("issue-tip" in item["variant-group"]["item"]["issue-information"]): + obj_issue["issue_description"] = item["variant-group"]["item"]["issue-information"]["issue-tip"].text break for recomendation in self.obj_xml["fix-recommendation-group"]["item"]: diff --git a/plugins/repo/arp-scan/plugin.py b/plugins/repo/arp-scan/plugin.py index 324cbec4b80..66375c39886 100644 --- a/plugins/repo/arp-scan/plugin.py +++ b/plugins/repo/arp-scan/plugin.py @@ -56,14 +56,14 @@ def parseOutputString(self, output, debug=False): for line in output.split('\n'): vals = line.split("\t") - if len(vals[0].split(".")) == 4: - - host = vals[0] - h_id = self.createAndAddHost(host) - i_id = self.createAndAddInterface( - h_id, host, ipv4_address=host, mac=vals[1]) - n_id = self.createAndAddNoteToHost( - h_id, "NIC VENDOR:", vals[2]) + if len(vals) == 3: + + if len(vals[0].split(".")) == 4: + + host = vals[0] + h_id = self.createAndAddHost(host) + i_id = self.createAndAddInterface(h_id, host, ipv4_address=host, mac=vals[1]) + n_id = self.createAndAddNoteToHost(h_id, "NIC VENDOR:", vals[2]) return True diff --git a/plugins/repo/fierce/plugin.py b/plugins/repo/fierce/plugin.py index 65c7c44da07..ed8241d330d 100644 --- a/plugins/repo/fierce/plugin.py +++ b/plugins/repo/fierce/plugin.py @@ -67,9 +67,9 @@ def __init__(self, output): self.items.append(item) self.isZoneVuln = False + output= output.replace('\\$', '') r = re.search( - "Whoah, it worked - misconfigured DNS server found:\r\n([^$]+)There isn't much point continuing, you have everything.", - output) + "Whoah, it worked - misconfigured DNS server found:([^$]+)\There isn't much point continuing, you have everything.", output) if r is not None: diff --git a/plugins/repo/junit/__init__.py b/plugins/repo/junit/__init__.py new file mode 100644 index 00000000000..004c49be6c6 --- /dev/null +++ b/plugins/repo/junit/__init__.py @@ -0,0 +1,6 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' diff --git a/plugins/repo/junit/plugin.py b/plugins/repo/junit/plugin.py new file mode 100644 index 00000000000..b5c33a7461d --- /dev/null +++ b/plugins/repo/junit/plugin.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' +from __future__ import with_statement +from plugins import core +from model import api +import re +import os +import pprint +import sys +from lxml import etree + +try: + import xml.etree.cElementTree as ET + import xml.etree.ElementTree as ET_ORIG + ETREE_VERSION = ET_ORIG.VERSION +except ImportError: + import xml.etree.ElementTree as ET + ETREE_VERSION = ET.VERSION + +ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] + +current_path = os.path.abspath(os.getcwd()) + +__author__ = "Thierry Beauquier" +__license__ = "" +__version__ = "1.0.0" +__maintainer__ = "Thierry Beauquier" +__email__ = "thierry.beauquier@ericsson.com" +__status__ = "Development" + +''' +This plugin has been designed to be used with python-unittest2/paramiko script to perform security compliancy verification. It enables to have displayed both security scans results (nmap, +nessus, ..) and security verification compliancy (CIS-CAT, compagny's product security requirement) by Faraday-IPE + +This plugin requires that a element "host" is added to (sed -i 's/ + + + + + + + + + + + +''' + +class JunitXmlParser(object): + """ + The objective of this class is to parse an xml file generated by the junit. + + @param junit_xml_filepath A proper xml generated by junit + """ + + def __init__(self, xml_output): + + tree = self.parse_xml(xml_output) + if tree: + self.items = [data for data in self.get_items(tree)] + else: + self.items = [] + + def parse_xml(self, xml_output): + """ + Open and parse an xml file. + + @return xml_tree An xml tree instance. None if error. + """ + try: +# return ET.fromstring(xml_output) + tree = etree.fromstring(xml_output) + except SyntaxError, err: + print "SyntaxError: %s. %s" % (err, xml_output) + return None + return tree + + def get_items(self, tree): + """ + @return items A list of Failure instances + """ + + for node in tree.findall('testsuite/testcase/failure'): + yield Testsuite(node) + + +class Testsuite(object): + + def __init__(self, testsuite_node): + self.node = testsuite_node + + self.parent = self.node.getparent() + self.name = self.parent.get('name') + self.host = self.parent.get('host') + if self.host is None: + print 'host element is missing' + self.host = '' + + self.message = self.get_text_from_subnode('message') + + def get_text_from_subnode(self, subnode_xpath_expr): + """ + Finds a subnode in the host node and the retrieves a value from it. + + @return An attribute value + """ + sub_node = self.node.get(subnode_xpath_expr) + if sub_node is not None: + return sub_node + + return None + +class JunitPlugin(core.PluginBase): + """ + Example plugin to parse junit output. + """ + + def __init__(self): + core.PluginBase.__init__(self) + self.id = "Junit" + self.name = "Junit XML Output Plugin" + self.plugin_version = "0.0.1" + self.version = "" + self.framework_version = "1.0.0" + self.options = None + self._current_output = None + self._command_regex = None + + def parseOutputString(self, output, debug=False): + + parser = JunitXmlParser(output) + for item in parser.items: + h_id = self.createAndAddHost(item.host, os="Linux") + i_id = self.createAndAddInterface(h_id, item.host, ipv4_address=item.host) + self.createAndAddVulnToHost(h_id, name=item.name, desc=item.message, ref=[], severity="High") + del parser + +def createPlugin(): + return JunitPlugin() + +if __name__ == '__main__': + parser = JunitXmlParser(sys.argv[1]) + for item in parser.items: + if item.status == 'up': + print item diff --git a/requirements_server.txt b/requirements_server.txt index 117bf842227..7fe736a1fcb 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -6,3 +6,4 @@ twisted>=16.1.1 sqlalchemy>=1.0.12 pyopenssl>=16.0.0 service_identity>=16.0.0 +pyasn1-modules diff --git a/server/api/modules/workspaces.py b/server/api/modules/workspaces.py index 7a0059add93..0d1a8d9c784 100644 --- a/server/api/modules/workspaces.py +++ b/server/api/modules/workspaces.py @@ -11,8 +11,17 @@ from server.dao.service import ServiceDAO from server.dao.interface import InterfaceDAO from server.dao.note import NoteDAO -from server.utils.web import gzipped, validate_workspace, get_basic_auth, validate_admin_perm, validate_database, build_bad_request_response -from server.couchdb import list_workspaces_as_user, get_workspace, get_auth_info +from server.utils.web import ( + gzipped, + validate_workspace, + get_basic_auth, + validate_admin_perm, + build_bad_request_response +) +from server.couchdb import ( + list_workspaces_as_user, + get_workspace +) from server.database import get_manager @@ -23,6 +32,7 @@ def workspace_list(): list_workspaces_as_user( flask.request.cookies, get_basic_auth())) + @app.route('/ws//summary', methods=['GET']) @gzipped def workspace_summary(workspace=None): @@ -48,6 +58,7 @@ def workspace_summary(workspace=None): return flask.jsonify(response) + @app.route('/ws/', methods=['GET']) @gzipped def workspace(workspace): @@ -56,10 +67,13 @@ def workspace(workspace): flask.request.cookies, get_basic_auth())['workspaces'] ws = get_workspace(workspace, flask.request.cookies, get_basic_auth()) if workspace in workspaces else None # TODO: When the workspace DAO is ready, we have to remove this next line - if not ws.get('fdate') and ws.get('duration'): ws['fdate'] = ws.get('duration').get('end') - if not ws.get('description'): ws['description'] = '' + if not ws.get('fdate') and ws.get('duration'): + ws['fdate'] = ws.get('duration').get('end') + if not ws.get('description'): + ws['description'] = '' return flask.jsonify(ws) + @app.route('/ws/', methods=['PUT']) @gzipped def workspace_create_or_update(workspace): @@ -87,7 +101,7 @@ def workspace_create_or_update(workspace): elif workspace not in db_manager and not is_update_request: res = db_manager.create_workspace(document) else: - abort(400) + flask.abort(400) if not res: response = flask.jsonify({'error': "There was an error {0} the workspace".format("updating" if is_update_request else "creating")}) @@ -96,6 +110,7 @@ def workspace_create_or_update(workspace): return flask.jsonify({'ok': True}) + @app.route('/ws/', methods=['DELETE']) @gzipped def workspace_delete(workspace): diff --git a/server/dao/vuln.py b/server/dao/vuln.py index e08eab119a7..a2376ba2c29 100644 --- a/server/dao/vuln.py +++ b/server/dao/vuln.py @@ -51,7 +51,8 @@ class VulnerabilityDAO(FaradayDAO): "web": [], "issuetracker": [], "creator": [EntityMetadata.creator], - "command_id": [EntityMetadata.command_id] + "command_id": [EntityMetadata.command_id], + "policyviolations": [Vulnerability.policyviolations] } STRICT_FILTERING = ["type", "service", "couchid", "hostid", "serviceid", 'interfaceid', 'id', 'status', 'command_id'] @@ -80,7 +81,8 @@ def __query_database(self, search=None, page=0, page_size=0, order_by=None, orde Vulnerability.method, Vulnerability.params, Vulnerability.pname, Vulnerability.query,\ EntityMetadata.couchdb_id, EntityMetadata.revision, EntityMetadata.create_time, EntityMetadata.creator,\ EntityMetadata.owner, EntityMetadata.update_action, EntityMetadata.update_controller_action,\ - EntityMetadata.update_time, EntityMetadata.update_user, EntityMetadata.document_type, EntityMetadata.command_id, Vulnerability.attachments) + EntityMetadata.update_time, EntityMetadata.update_user, EntityMetadata.document_type, EntityMetadata.command_id, \ + Vulnerability.attachments, Vulnerability.policyviolations) service_bundle = Bundle('service', Service.name.label('s_name'), Service.ports, Service.protocol, Service.id) host_bundle = Bundle('host', Host.name) @@ -172,6 +174,7 @@ def get_parent_id(couchdb_id): 'owned': vuln.owned, 'owner': vuln.owner, 'parent': get_parent_id(vuln.couchdb_id), + 'policyviolations': json.loads(vuln.policyviolations), 'refs': json.loads(vuln.refs), 'status': vuln.status, 'website': vuln.website, diff --git a/server/models.py b/server/models.py index 0bb295ed3e0..145e142d5fc 100644 --- a/server/models.py +++ b/server/models.py @@ -9,7 +9,7 @@ from sqlalchemy.ext.declarative import declarative_base -SCHEMA_VERSION = 'W.2.5.0' +SCHEMA_VERSION = 'W.2.6.0' Base = declarative_base() @@ -309,6 +309,7 @@ class Vulnerability(FaradayEntity, Base): severity = Column(String(50)) owned = Column(Boolean) attachments = Column(Text(), nullable=True) + policyviolations = Column(Text()) impact_accountability = Column(Boolean) impact_availability = Column(Boolean) @@ -347,6 +348,7 @@ def update_from_document(self, document): self.severity=document.get('severity') self.owned=document.get('owned', False) self.attachments = json.dumps(document.get('_attachments', {})) + self.policyviolations = json.dumps(document.get('policyviolations', [])) self.impact_accountability=document.get('impact', {}).get('accountability') self.impact_availability=document.get('impact', {}).get('availability') self.impact_confidentiality=document.get('impact', {}).get('confidentiality') diff --git a/server/www/estilos.css b/server/www/estilos.css index 4273e2be327..cde25056779 100644 --- a/server/www/estilos.css +++ b/server/www/estilos.css @@ -794,7 +794,7 @@ input.form-control.vuln_per_page{width: 10%;margin: auto} .reference{margin-top: 5px;} div.form-group.editArray{margin-bottom: 0px} div.modal-footer.editArray{margin-top: 0px} -input#vuln-refs{border-radius: 5px 0 0 5px} +input#vuln-refs, input#vuln-policyviolations {border-radius: 5px 0 0 5px} i.fa.fa-plus-circle{color: green;} i.fa.fa-minus-circle{color: red;cursor: pointer;} span.input-group-addon.button-radius{ @@ -1137,4 +1137,14 @@ a.button-disable{cursor: not-allowed;pointer-events: none;opacity: 0.5} .header_right { float: right; margin: 8px; +} + +.accordion-expand-button { + color: #008000; + right: 10px; + top: -25px !important; +} + +.accordion-expand-button-disabled { + display: none !important; } \ No newline at end of file diff --git a/server/www/index.html b/server/www/index.html index 55d9dabd74b..070f187447c 100644 --- a/server/www/index.html +++ b/server/www/index.html @@ -65,7 +65,7 @@
- +
- +
diff --git a/server/www/scripts/commons/partials/home.html b/server/www/scripts/commons/partials/home.html index c0c4e7e2cb1..e52faa7995d 100644 --- a/server/www/scripts/commons/partials/home.html +++ b/server/www/scripts/commons/partials/home.html @@ -51,6 +51,14 @@

Manage reports + + + Tasks + + Control all your tasks.
+ Plan your Job +
+
Chat diff --git a/server/www/scripts/csv/providers/csv.js b/server/www/scripts/csv/providers/csv.js index 80a85500f9f..4b91c6aa9d5 100644 --- a/server/www/scripts/csv/providers/csv.js +++ b/server/www/scripts/csv/providers/csv.js @@ -34,7 +34,7 @@ angular.module('faradayApp') object[prop] = ""; } if(prop === "date") object[prop] = parseDate(v["metadata"]["create_time"] * 1000); - if(prop === "creator") object[prop] = v["metadata"]["creator"]; + if(prop === "creator") object[prop] = excelEscape(v["metadata"]["creator"]); if(prop === "web") { if(v.type === "Vulnerability") { object[prop] = false; @@ -59,7 +59,7 @@ angular.module('faradayApp') }; cleanCSV = function(field) { - return field.replace(/\"/g, "\"\""); + return excelEscape(field.replace(/\"/g, "\"\"")); }; parseObject = function(object) { @@ -77,13 +77,25 @@ angular.module('faradayApp') }); } parsedData = parsedData.substring(0, parsedData.length - 1); - return parsedData; + return excelEscape(parsedData); }; parseDate = function(date) { var d = new Date(date); - return d.getMonth()+1 +"/" + d.getDate() + "/" + d.getFullYear(); + return excelEscape(d.getMonth()+1 +"/" + d.getDate() + "/" + d.getFullYear()); }; + excelEscape = function(data){ + // Patch possible formula injection attacks + // See https://www.contextis.com/resources/blog/comma-separated-vulnerabilities/ for more info. + if(data.startsWith('=') || data.startsWith('+') + || data.startsWith('-') + || data.startsWith('@')){ + return "'" + data + }else{ + return data + } + } + return csvService; }); diff --git a/server/www/scripts/cwe/providers/CweService.js b/server/www/scripts/cwe/providers/CweService.js index 2d915967d52..e0fda750f6e 100644 --- a/server/www/scripts/cwe/providers/CweService.js +++ b/server/www/scripts/cwe/providers/CweService.js @@ -3,7 +3,7 @@ // See the file 'doc/LICENSE' for the license information angular.module('faradayApp') - .factory('cweFact', ['BASEURL', '$http', '$q', 'vulnModelsManager', function(BASEURL, $http, $q, vulnModelsManager) { + .factory('cweFact', ['BASEURL', '$http', '$q', 'vulnModelsManager', 'commonsFact', function(BASEURL, $http, $q, vulnModelsManager, commonsFact) { var cweFact = {}; // XXX: this is still not using the server @@ -20,8 +20,8 @@ angular.module('faradayApp') var c = { id: obj.id, cwe: obj.doc.cwe, - name: obj.doc.name, - desc: description, + name: commonsFact.htmlentities(obj.doc.name), + desc: commonsFact.htmlentities(description), resolution: obj.doc.resolution, exploitation: obj.doc.exploitation, refs: obj.doc.references diff --git a/server/www/scripts/d3/directives/bar.js b/server/www/scripts/d3/directives/bar.js index 39f013a92cf..8e839c67a24 100644 --- a/server/www/scripts/d3/directives/bar.js +++ b/server/www/scripts/d3/directives/bar.js @@ -80,7 +80,16 @@ angular.module('faradayApp') var hurl = "/" + workspace + "/_design/hosts/_view/hosts"; hosts = get_obj(hurl); var name = hosts[d.key].name; - document.getElementById("barText").innerHTML = "
" + name + '
' + d.value; + var element = document.getElementById("barText"); + var colored = document.createElement('div'); + colored.style = "background-color:" + d.color; // Color is safe (its value is in a whitelist) + colored.innerText = name; + while (element.firstChild) { + // https://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript + element.removeChild(element.firstChild); + } + element.appendChild(colored); + element.appendChild(document.createTextNode(d.value)); }) .on('mouseenter', function(d) { var line = d3.select('.id-'+d.key) diff --git a/server/www/scripts/d3/directives/cake.js b/server/www/scripts/d3/directives/cake.js index 019a8dbe7d9..b056dce34d1 100644 --- a/server/www/scripts/d3/directives/cake.js +++ b/server/www/scripts/d3/directives/cake.js @@ -100,7 +100,16 @@ angular.module('faradayApp') .style("stroke-width", "0.5") .style("opacity", 0) .on('mouseover', function(d) { - document.getElementById("cakeText").innerHTML = "
" + d.key + '
' + d.value; + var element = document.getElementById("cakeText"); + var colored = document.createElement('div'); + colored.style = "background-color:" + d.color; // Color is safe (its value is in a whitelist) + colored.innerText = d.key; + while (element.firstChild) { + // https://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript + element.removeChild(element.firstChild); + } + element.appendChild(colored); + element.appendChild(document.createTextNode(d.value)); }) .on('mouseenter', function(d) { var slice = d3.select('.cake-'+d.key) diff --git a/server/www/scripts/d3/directives/treemap.js b/server/www/scripts/d3/directives/treemap.js index 1a8ebcec995..0583e8b3ef9 100644 --- a/server/www/scripts/d3/directives/treemap.js +++ b/server/www/scripts/d3/directives/treemap.js @@ -3,7 +3,7 @@ // See the file 'doc/LICENSE' for the license information angular.module('faradayApp') - .directive('d3Treemap', ['d3Service', + .directive('d3Treemap', ['d3Service', function(d3Service) { return { restrict: 'EA', @@ -61,12 +61,18 @@ angular.module('faradayApp') .sticky(true) .value(function(d) {return d.count}); + function nameToClassSafeName(name) { + // Remove non alphanumeric characters to safely use the (modified) + // name as a class name + return name.replace(/[^0-9a-zA-Z]/g, ''); + } + var node = div.datum(data_cp).selectAll(".node") .data(treemap.nodes) .enter().append("div") .attr("class", function(d) { var ret = "node treemap-tooltip"; - if(d.name) ret += " tm-" + d.name; + if(d.name) ret += " tm-" + nameToClassSafeName(d.name); return ret; }) .call(position) @@ -79,18 +85,31 @@ angular.module('faradayApp') } }) .on('mouseover', function(d){ - if (!data.width){ - document.getElementById("treemapText").innerHTML = "
" + d.name + '
' + d.count; - }else{ - document.getElementById("treemapTextModel").innerHTML = "
" + d.name + '
' + d.count; - } + if(typeof(d.name) === 'undefined') return; // I don't know why this happens + if (!data.width){ + var element = document.getElementById("treemapText"); + //.innerHTML = "
" + d.name + '
' + d.count; + }else{ + var element = document.getElementById("treemapTextModel"); + } + var colored = document.createElement('div'); + colored.style = "background-color:" + d.color; // Color is safe (its value is in a whitelist) + colored.innerText = d.name; + while (element.firstChild) { + // https://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript + element.removeChild(element.firstChild); + } + element.appendChild(colored); + element.appendChild(document.createTextNode(d.count)); }) .on('mouseenter', function(d) { - var line = d3.select('.tm-'+d.name) + if(typeof(d.name) === 'undefined') return; // I don't know why this happens + var line = d3.select('.tm-'+nameToClassSafeName(d.name)) .style("opacity", 1); }) .on('mouseleave', function(d) { - var line = d3.select('.tm-'+d.name) + if(typeof(d.name) === 'undefined') return; // I don't know why this happens + var line = d3.select('.tm-'+nameToClassSafeName(d.name)) .style("opacity", 0.8); document.getElementById("treemapText").innerHTML = ""; }) diff --git a/server/www/scripts/fileExporter/directives/download.js b/server/www/scripts/fileExporter/directives/download.js index 8d7128ca0e1..a9dc726a04c 100644 --- a/server/www/scripts/fileExporter/directives/download.js +++ b/server/www/scripts/fileExporter/directives/download.js @@ -32,7 +32,10 @@ angular.module('faradayApp') a_href = element.find('a')[0]; $click.on(a_href); - $timeout(function() {$blob.revoke(url);}); + + //Delay is necesary only for old versions of firefox browser. + //Ref: https://stackoverflow.com/questions/30694453/blob-createobjecturl-download-not-working-in-firefox-but-works-when-debugging + $timeout(function() {$blob.revoke(url);}, 1000); element[0].removeChild(a_href); }); diff --git a/server/www/scripts/navigation/partials/leftBar.html b/server/www/scripts/navigation/partials/leftBar.html index e9296dd2702..70666631716 100644 --- a/server/www/scripts/navigation/partials/leftBar.html +++ b/server/www/scripts/navigation/partials/leftBar.html @@ -29,6 +29,11 @@ Executive Report
+
  • + + + +
  • Communication diff --git a/server/www/scripts/statusReport/controllers/modalEdit.js b/server/www/scripts/statusReport/controllers/modalEdit.js index 72f9e73346f..111e004a6f2 100644 --- a/server/www/scripts/statusReport/controllers/modalEdit.js +++ b/server/www/scripts/statusReport/controllers/modalEdit.js @@ -11,6 +11,7 @@ angular.module('faradayApp') vm.saveAsModelDisabled = false; vm.easeofresolution; vm.new_ref; + vm.new_policyviolation; vm.icons; vm.cweList; vm.cweLimit; @@ -27,6 +28,7 @@ angular.module('faradayApp') vm.severities = severities; vm.statuses = STATUSES; vm.new_ref = ""; + vm.new_policyviolation = ""; vm.icons = {}; vm.cweList = []; @@ -63,6 +65,7 @@ angular.module('faradayApp') response: "", website: "", status: "opened", + policyviolations: [] }; vm.vuln = angular.copy(vuln); @@ -90,7 +93,7 @@ angular.module('faradayApp') vm.file_name_error = true; } }); - vm.icons = commons.loadIcons(vm._attachments); + vm.icons = commons.loadIcons(vm._attachments); } vm.removeEvidence = function(name) { @@ -101,7 +104,7 @@ angular.module('faradayApp') vm.toggleImpact = function(key) { vm.data.impact[key] = !vm.data.impact[key]; }; - + vm.ok = function() { // add the ref in new_ref, if there's any vm.newReference(); @@ -111,6 +114,15 @@ angular.module('faradayApp') refs.push(ref.value); }); vm.data.refs = refs; + + // add the policy violation in new_policyviolation, if there's any + vm.newPolicyViolation(); + // convert policy violations to an array of strings + var policyviolations = []; + vm.data.policyviolations.forEach(function(policyviolation) { + policyviolations.push(policyviolation.value); + }); + vm.data.policyviolations = policyviolations; $modalInstance.close(vm.data); }; @@ -128,9 +140,19 @@ angular.module('faradayApp') } } + vm.newPolicyViolation = function() { + if (vm.new_policyviolation != "") { + // we need to check if the policy violation already exists + if (vm.data.policyviolations.filter(function(policyviolation) {return policyviolation.value === vm.new_policyviolation}).length == 0) { + vm.data.policyviolations.push({value: vm.new_policyviolation}); + vm.new_policyviolation = ""; + } + } + } + vm.populate = function(item) { for (var key in vm.data) { - if (key != "refs" && item.hasOwnProperty(key) && vm.data.hasOwnProperty(key)) { + if (key != "refs" && key != "policyviolations" && item.hasOwnProperty(key) && vm.data.hasOwnProperty(key)) { vm.data[key] = item[key]; } } @@ -140,6 +162,13 @@ angular.module('faradayApp') refs.push({value: ref}); }); vm.data.refs = refs; + + // convert policyviolations to an array of objects + var policyviolations = []; + item.policyviolations.forEach(function(policyviolation) { + policyviolations.push({value: policyviolation}); + }); + vm.data.policyviolations = policyviolations; } init(); diff --git a/server/www/scripts/statusReport/controllers/modalNew.js b/server/www/scripts/statusReport/controllers/modalNew.js index ec41b848fc3..df9e4647382 100644 --- a/server/www/scripts/statusReport/controllers/modalNew.js +++ b/server/www/scripts/statusReport/controllers/modalNew.js @@ -13,6 +13,7 @@ angular.module('faradayApp') vm.easeofresolution; vm.workspace; vm.new_ref; + vm.new_policyviolation; vm.icons; vm.cweList; vm.cweLimit; @@ -24,11 +25,14 @@ angular.module('faradayApp') vm.newCurrentPage; vm.pageSize; - vm.targets; + vm.targets; vm.target_filter; vm.data; + // true if all the parents in data.parents are type Host + vm.host_parents; + init = function() { vm.vuln_types = [ {name:'Vulnerability', value:'Vulnerability'}, @@ -38,8 +42,11 @@ angular.module('faradayApp') vm.severities = severities; vm.workspace = workspace; vm.new_ref = ""; + vm.new_policyviolation = ""; vm.icons = {}; + vm.host_parents = false; + vm.cweList = []; cweFact.get().then(function(data) { vm.cweList = data; @@ -48,14 +55,13 @@ angular.module('faradayApp') vm.cwe_filter = ""; vm.file_name_error = false; - + vm.pageSize = 5; vm.currentPage = 0; vm.newCurrentPage = 0; vm.data = { _attachments: {}, - type: "Vulnerability", data: "", desc: "", easeofresolution: undefined, @@ -65,19 +71,21 @@ angular.module('faradayApp') confidentiality: false, integrity: false }, + method: "", name: "", owned: false, - parent: undefined, - refs: [], - resolution: "", - severity: undefined, - method: "", - path: "", - pname: "", params: "", - query: "", + parents: [], + path: "", + pname: "", + policyviolations: [], + query: "", + refs: [], request: "", + resolution: "", response: "", + severity: undefined, + type: "Vulnerability", website: "" }; @@ -111,43 +119,73 @@ angular.module('faradayApp') }; vm.ok = function() { - if (!(vm.data.type === "VulnerabilityWeb" && vm.data.parent.type === "Host")) { - // add the ref in new_ref, if there's any - vm.newReference(); + // add the ref in new_ref, if there's any + vm.newReference(); + vm.newPolicyViolation(); - // convert refs to an array of strings - var refs = []; - vm.data.refs.forEach(function(ref) { - refs.push(ref.value); - }); - vm.data.refs = refs; + // convert refs to an array of strings + var refs = []; + vm.data.refs.forEach(function(ref) { + refs.push(ref.value); + }); + vm.data.refs = refs; - // delete selection - delete vm.data.parent.selected_modalNewCtrl; + var policyviolations = []; + vm.data.policyviolations.forEach(function(violation) { + policyviolations.push(violation.value); + }); + vm.data.policyviolations = policyviolations; - vm.data.parent = vm.data.parent._id; + var parents = vm.data.parents; + vm.data.parents = []; + parents.forEach(function(parent) { + vm.data.parents.push(parent._id); + }); - $modalInstance.close(vm.data); - } + $modalInstance.close(vm.data); }; vm.cancel = function() { $modalInstance.dismiss('cancel'); }; + vm.setTargets = function(filter, start, size) { + var end = start + size, + targets = vm.targets; + + if(filter) { + targets = vm.targets_filtered; + } + + vm.data.parents = targets.slice(start, end); + + vm.host_parents = vm.data.parents.some(function(elem, ind, arr) { + return elem.type === 'Host'; + }); + }; + vm.setTarget = function(target) { - if (vm.data.parent != undefined) { - delete vm.data.parent.selected_modalNewCtrl; + var index = vm.data.parents.indexOf(target); + + if(index >= 0) { + // if target already selected, user is deselecting + vm.data.parents.splice(index, 1); + } else { + // else, add to parents list + vm.data.parents.push(target); } - target.selected_modalNewCtrl = true; - vm.data.parent = target; + + // refresh host_parents var + vm.host_parents = vm.data.parents.some(function(elem, ind, arr) { + return elem.type === 'Host'; + }); } vm.go = function() { vm.currentPage = 0; - if(vm.newCurrentPage <= parseInt(vm.targets.length/vm.pageSize) - && vm.newCurrentPage > -1) { - vm.currentPage = vm.newCurrentPage; + if((vm.newCurrentPage-1) <= parseInt(vm.targets.length/vm.pageSize) + && (vm.newCurrentPage-1) > -1) { + vm.currentPage = (vm.newCurrentPage-1); } } @@ -161,10 +199,20 @@ angular.module('faradayApp') } } + vm.newPolicyViolation = function() { + if (vm.new_policyviolation != "") { + // we need to check if the policy violation already exists + if (vm.data.policyviolations.filter(function(policyviolation) {return policyviolation.value === vm.new_policyviolation}).length == 0) { + vm.data.policyviolations.push({value: vm.new_policyviolation}); + vm.new_policyviolation = ""; + } + } + } + vm.populate = function(item, model, label) { for (var key in item) { - if (key != "refs" && vm.data.hasOwnProperty(key)) { + if(key != "refs" && key != "policyviolations" && vm.data.hasOwnProperty(key)) { vm.data[key] = item[key]; } } @@ -175,6 +223,12 @@ angular.module('faradayApp') refs.push({value: ref}); }); vm.data.refs = refs; + + var policyviolations = []; + item.policyviolations.forEach(function(policyviolation) { + policyviolations.push({value: policyviolation}); + }); + vm.data.policyviolations = policyviolations; } init(); diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index ac6a7c5f5c2..a2cb6c1f083 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -175,7 +175,8 @@ angular.module('faradayApp') "query": false, "response": false, "web": false, - "creator": false + "creator": false, + "policyviolations": false }; // created object for columns cookie columns @@ -394,6 +395,14 @@ angular.module('faradayApp') sort: getColumnSort('metadata.creator'), visible: $scope.columns["creator"] }); + $scope.gridOptions.columnDefs.push({ name : 'policyviolations', + displayName : "policy violations", + cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/policyviolationscolumn.html', + headerCellTemplate: header, + width: '100', + sort: getColumnSort('policyviolations'), + visible: $scope.columns["policyviolations"] + }); }; @@ -736,6 +745,25 @@ angular.module('faradayApp') {options: EASEOFRESOLUTION}); }; + $scope.editPolicyviolations = function() { + editProperty( + 'scripts/commons/partials/editArray.html', + 'commonsModalEditArray', + 'Enter the new policy violations:', + 'policyviolations', + {callback: function (vuln, policyviolations) { + var violations = vuln.policyviolations.concat([]); + policyviolations.forEach(function(policyviolation) { + if(vuln.policyviolations.indexOf(policyviolation) == -1){ + violations.push(policyviolation); + } + }); + + return {'policyviolations': violations}; + }} + ); + }; + $scope.editReferences = function() { editProperty( 'scripts/commons/partials/editArray.html', diff --git a/server/www/scripts/statusReport/partials/accordion-group.html b/server/www/scripts/statusReport/partials/accordion-group.html new file mode 100644 index 00000000000..eda6bf222c7 --- /dev/null +++ b/server/www/scripts/statusReport/partials/accordion-group.html @@ -0,0 +1,15 @@ +
    + +
    +
    +
    +
    \ No newline at end of file diff --git a/server/www/scripts/statusReport/partials/modalEdit.html b/server/www/scripts/statusReport/partials/modalEdit.html index ac54de8641b..acab6c1fc77 100644 --- a/server/www/scripts/statusReport/partials/modalEdit.html +++ b/server/www/scripts/statusReport/partials/modalEdit.html @@ -75,7 +75,7 @@
    Confirmed
    - +
    @@ -85,6 +85,22 @@
    Confirmed
  • + +
    +
    +
    + + + +
    +
    +
    +
    + + + +
    +
    diff --git a/server/www/scripts/statusReport/partials/modalNew.html b/server/www/scripts/statusReport/partials/modalNew.html index 8a5568fb69c..6c4a0592185 100644 --- a/server/www/scripts/statusReport/partials/modalNew.html +++ b/server/www/scripts/statusReport/partials/modalNew.html @@ -5,35 +5,59 @@