diff --git a/README.md b/README.md index ad31a5e..8fd1f8d 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,38 @@ # MISP-Dashboard An experimental dashboard showing live data and statistics from the ZMQ of one or more MISP instances. + +# Installation +- Launch ```./install_dependencies.sh``` from the MISP-Dashboard directory +- Update the configuration file ```config.cfg``` so that it matches your system + - Fields that you may change: + - RedisGlobal -> host + - RedisGlobal -> port + - RedisGlobal -> zmq_url + - RedisGlobal -> misp_web_url + - RedisMap -> pathMaxMindDB + +# Starting the System +- Activate your virtualenv ```. ./DASHENV/bin/activate``` +- Listen to the MISP feed by starting the zmq_subscriber ```./zmq_subscriber.py``` +- Start the Flask server ```./server.py``` +- Access the interface at ```http://localhost:8001/``` + +# Features + ## Live Dashboard - Possibility to subscribe to multiple ZMQ feeds - Shows direct contribution made by organisations - Shows live resolvable posted locations -![MISP event view](./screenshots/dashboard-live.png) +![Dashboard live](./screenshots/dashboard-live.png) ## Geolocalisation Dashboard - Provides historical geolocalised information to support security teams, CSIRTs or SOC finding threats in their constituency - Possibility to get geospatial information from specific regions -![MISP event view](./screenshots/dashboard-geo.png) +![Dashbaord geo](./screenshots/dashboard-geo.png) ## Contributors Dashboard @@ -29,25 +48,25 @@ __Includes__: - Gamification of the platform: - Two different levels of ranking with unique icons - Exclusive obtainable badges for source code contributors and donator - -![Dashboard-contributor2](./screenshots/dashboard-contributors2.png) -![Dashboard-contributor3](./screenshots/dashboard-contributors3.png) +![Dashboard contributor](./screenshots/dashboard-contributors2.png) +![Dashboard contributor2](./screenshots/dashboard-contributors3.png) -# Installation -- Launch ```./install_dependencies.sh``` from the MISP-Dashboard directory -- Update the configuration file ```config.cfg``` so that it matches your system - - Fields that you may change: - - RedisGlobal -> host - - RedisGlobal -> port - - RedisGlobal -> zmq_url - - RedisGlobal -> misp_web_url - -# Starting the System -- Activate your virtualenv ```. ./DASHENV/bin/activate``` -- Listen to the MISP feed by starting the zmq_subscriber ```./zmq_subscriber.py``` -- Start the Flask server ```./server.py``` -- Access the interface at ```http://localhost:8001/``` +## Users Dashboard + +- Shows when and how the platform is used: + - Login punchcard and overtime + - Contribution vs login + +![Dashboard users](./screenshots/dashboard-users.png) + +## Trendings Dashboard + +- Provides real time information to support security teams, CSIRTs or SOC showing current threats and activity + - Shows most active events, categories and tags + - Shows sightings and discussion overtime + +![Dashboard users](./screenshots/dashboard-trendings.png) # zmq_subscriber options ```usage: zmq_subscriber.py [-h] [-n ZMQNAME] [-u ZMQURL] @@ -62,7 +81,7 @@ optional arguments: The URL to connect to ``` -## License +# License Images and logos are handmade for: - rankingMISPOrg/ - rankingMISPMonthly/ diff --git a/config/config.cfg.default b/config/config.cfg.default index f0c0bfb..cdb6eed 100644 --- a/config/config.cfg.default +++ b/config/config.cfg.default @@ -27,7 +27,7 @@ rankMultiplier = 2 categories_in_datatable = ["internal_reference", "targeting_data", "antivirus_detection", "payload_delivery", "artifacts_dropped", "payload_installation", "persistence_mechanism", "network_activity", "payload_type", "attribution", "external_analysis", "financial_fraud", "support_Tool", "social_network", "person", "other" ] default_pnts_per_contribution = 1 # array of the form [[category, pntsRcv], ...] -pnts_per_contribution = [["payload_delivery", 1], ["artifact_dropped", 1], ["network_activity", 1]] +pnts_per_contribution = [["payload_delivery", 1], ["artifacts_dropped", 1], ["network_activity", 1]] additional_help_text = ["Sightings multiplies earned points by 2", "Editing an attribute earns you the same as creating one"] [Log] diff --git a/install_dependencies.sh b/install_dependencies.sh index b8bfe64..8677b05 100755 --- a/install_dependencies.sh +++ b/install_dependencies.sh @@ -3,7 +3,7 @@ set -e set -x -sudo apt-get install python3-virtualenv -y +sudo apt-get install python3-virtualenv virtualenv screen redis-server -y if [ -z "$VIRTUAL_ENV" ]; then virtualenv -p python3 DASHENV @@ -102,4 +102,10 @@ wget https://cdn.datatables.net/${DATATABLE_VERSION}/js/dataTables.bootstrap.js git clone https://github.com/bassjobsen/Bootstrap-3-Typeahead.git temp/Bootstrap-3-Typeahead mv temp/Bootstrap-3-Typeahead/bootstrap3-typeahead.min.js ./static/js +#punchcard +git clone https://github.com/melenaos/jquery-punchcard.git temp/jquery-punchcard +mv temp/jquery-punchcard/src/punchcard.js ./static/js +mv temp/jquery-punchcard/src/punchcard.css ./static/css +wget https://momentjs.com/downloads/moment.js -O ./static/js/moment.js + rm -rf ./temp diff --git a/screenshots/dashboard-trendings.png b/screenshots/dashboard-trendings.png new file mode 100644 index 0000000..e8937e4 Binary files /dev/null and b/screenshots/dashboard-trendings.png differ diff --git a/screenshots/dashboard-users.png b/screenshots/dashboard-users.png new file mode 100644 index 0000000..0058be1 Binary files /dev/null and b/screenshots/dashboard-users.png differ diff --git a/server.py b/server.py index c83f43c..546031f 100755 --- a/server.py +++ b/server.py @@ -12,6 +12,7 @@ import os import util import contributor_helper import users_helper +import trendings_helper configfile = os.path.join(os.environ['DASH_CONFIG'], 'config.cfg') cfg = configparser.ConfigParser() @@ -34,6 +35,7 @@ serv_redis_db = redis.StrictRedis( contributor_helper = contributor_helper.Contributor_helper(serv_redis_db, cfg) users_helper = users_helper.Users_helper(serv_redis_db, cfg) +trendings_helper = trendings_helper.Trendings_helper(serv_redis_db, cfg) subscriber_log = redis_server_log.pubsub(ignore_subscribe_messages=True) subscriber_log.psubscribe(cfg.get('RedisLog', 'channel')) @@ -208,6 +210,18 @@ def users(): ) +@app.route("/trendings") +def trendings(): + maxNum = request.args.get('maxNum') + try: + maxNum = int(maxNum) + except: + maxNum = 15 + + return render_template('trendings.html', + maxNum=maxNum + ) + ''' INDEX ''' @app.route("/_logs") @@ -497,6 +511,82 @@ def getUserLoginsAndContribOvertime(): data = users_helper.getUserLoginsAndContribOvertime(date) return jsonify(data) +''' TRENDINGS ''' +@app.route("/_getTrendingEvents") +def getTrendingEvents(): + try: + dateS = datetime.datetime.fromtimestamp(float(request.args.get('dateS'))) + dateE = datetime.datetime.fromtimestamp(float(request.args.get('dateE'))) + except: + dateS = datetime.datetime.now() - datetime.timedelta(days=7) + dateE = datetime.datetime.now() + + specificLabel = request.args.get('specificLabel') + data = trendings_helper.getTrendingEvents(dateS, dateE, specificLabel) + return jsonify(data) + +@app.route("/_getTrendingCategs") +def getTrendingCategs(): + try: + dateS = datetime.datetime.fromtimestamp(float(request.args.get('dateS'))) + dateE = datetime.datetime.fromtimestamp(float(request.args.get('dateE'))) + except: + dateS = datetime.datetime.now() - datetime.timedelta(days=7) + dateE = datetime.datetime.now() + + + data = trendings_helper.getTrendingCategs(dateS, dateE) + return jsonify(data) + +@app.route("/_getTrendingTags") +def getTrendingTags(): + try: + dateS = datetime.datetime.fromtimestamp(float(request.args.get('dateS'))) + dateE = datetime.datetime.fromtimestamp(float(request.args.get('dateE'))) + except: + dateS = datetime.datetime.now() - datetime.timedelta(days=7) + dateE = datetime.datetime.now() + + + data = trendings_helper.getTrendingTags(dateS, dateE) + return jsonify(data) + +@app.route("/_getTrendingSightings") +def getTrendingSightings(): + try: + dateS = datetime.datetime.fromtimestamp(float(request.args.get('dateS'))) + dateE = datetime.datetime.fromtimestamp(float(request.args.get('dateE'))) + except: + dateS = datetime.datetime.now() - datetime.timedelta(days=7) + dateE = datetime.datetime.now() + + data = trendings_helper.getTrendingSightings(dateS, dateE) + return jsonify(data) + +@app.route("/_getTrendingDisc") +def getTrendingDisc(): + try: + dateS = datetime.datetime.fromtimestamp(float(request.args.get('dateS'))) + dateE = datetime.datetime.fromtimestamp(float(request.args.get('dateE'))) + except: + dateS = datetime.datetime.now() - datetime.timedelta(days=7) + dateE = datetime.datetime.now() + + + data = trendings_helper.getTrendingDisc(dateS, dateE) + return jsonify(data) + +@app.route("/_getTypeaheadData") +def getTypeaheadData(): + try: + dateS = datetime.datetime.fromtimestamp(float(request.args.get('dateS'))) + dateE = datetime.datetime.fromtimestamp(float(request.args.get('dateE'))) + except: + dateS = datetime.datetime.now() - datetime.timedelta(days=7) + dateE = datetime.datetime.now() + + data = trendings_helper.getTypeaheadData(dateS, dateE) + return jsonify(data) if __name__ == '__main__': app.run(host='localhost', port=8001, threaded=True) diff --git a/static/css/ranking.css b/static/css/ranking.css index ce76ca8..df7d62d 100644 --- a/static/css/ranking.css +++ b/static/css/ranking.css @@ -90,6 +90,11 @@ display: none; } +.table > thead > tr > th.centerCell { + text-align: center; + min-width: 45px; +} + .table > tbody > tr > td.centerCell { text-align: center; min-width: 45px; diff --git a/static/js/trendings.js b/static/js/trendings.js new file mode 100644 index 0000000..79741e9 --- /dev/null +++ b/static/js/trendings.js @@ -0,0 +1,441 @@ +/* VARS */ +var dateStart; +var dateEnd; +var eventPie = ["#eventPie"]; +var eventLine = ["#eventLine"]; +var categPie = ["#categPie"]; +var categLine = ["#categLine"]; +var tagPie = ["#tagPie"]; +var tagLine = ["#tagLine"]; +var sightingLineWidget; +var discLine = ["#discussionLine"]; +var allData; +var globalColorMapping = {}; + +/* OPTIONS */ +var datePickerOptions = { + showOn: "button", + maxDate: 0, + buttonImage: urlIconCalendar, + buttonImageOnly: true, + buttonText: "Select date", + showAnim: "slideDown", + onSelect: dateChanged +}; +var lineChartOption = { + lines: { + show: true, + }, + points: { show: true }, + xaxis: { + mode: "time", + minTickSize: [1, "day"], + }, + legend: { show: false }, + grid: { + hoverable: true + } +}; +var pieChartOption = { + series: { + pie: { + innerRadius: 0.2, + show: true, + radius: 100, + label: { + show: true, + radius: 6/10, + formatter: innerPieLabelFormatter, + } + } + }, + legend: { + show: true, + labelFormatter: legendFormatter + }, + grid: { + hoverable: true, + clickable: true + } +}; +var typeaheadOption_event = { + source: function (query, process) { + if (allData === undefined) { // caching + return $.getJSON(url_getTypeaheadData, function (data) { + allData = data; + return process(data.TRENDINGS_EVENTS); + }); + } else { + return process(allData.TRENDINGS_EVENTS); + } + }, + updater: function(theevent) { + updateLineForLabel(eventLine, theevent, undefined, url_getTrendingEvent); + } +} +var typeaheadOption_categ = { + source: function (query, process) { + if (allData === undefined) { // caching + return $.getJSON(url_getTypeaheadData, function (data) { + allData = data; + return process(data.TRENDINGS_CATEGS); + }); + } else { + return process(allData.TRENDINGS_CATEGS); + } + }, + updater: function(categ) { + updateLineForLabel(categLine, categ, undefined, url_getTrendingCateg); + } +} +var typeaheadOption_tag = { + source: function (query, process) { + if (allData === undefined) { // caching + return $.getJSON(url_getTypeaheadData, function (data) { + allData = data; + return process(data.TRENDINGS_TAGS); + }); + } else { + return process(allData.TRENDINGS_TAGS); + } + }, + updater: function(tag) { + updateLineForLabel(tagLine, tag, undefined, url_getTrendingTag); + } +} + +/* FUNCTIONS */ +function getColor(label) { + try { + return globalColorMapping[label]; + } catch(err) { + return undefined; + } + +} + +function innerPieLabelFormatter(label, series) { + var count = series.data[0][1]; + return '