From e08c190b33d2fe083e588cd340c2c898424d67ce Mon Sep 17 00:00:00 2001 From: Yogesh Ojha Date: Wed, 20 Oct 2021 00:03:14 +0530 Subject: [PATCH] Modified Scan API, Sidebar Scan Status completed --- web/api/serializers.py | 12 +++ web/api/views.py | 6 +- web/dashboard/views.py | 8 -- .../migrations/0002_auto_20211019_1756.py | 28 ++++++ .../migrations/0003_auto_20211019_1757.py | 28 ++++++ web/startScan/models.py | 25 ++++- web/static/custom/dashboard.js | 95 +++++++++++++++++++ web/static/plugins/snackbar.min.css | 1 + web/static/plugins/snackbar.min.js | 17 ++++ web/templates/base/_items/top_bar.html | 7 +- web/templates/base/base.html | 39 +++++++- 11 files changed, 247 insertions(+), 19 deletions(-) create mode 100644 web/startScan/migrations/0002_auto_20211019_1756.py create mode 100644 web/startScan/migrations/0003_auto_20211019_1757.py create mode 100644 web/static/custom/dashboard.js create mode 100644 web/static/plugins/snackbar.min.css create mode 100644 web/static/plugins/snackbar.min.js diff --git a/web/api/serializers.py b/web/api/serializers.py index 330ec991..924db7ca 100644 --- a/web/api/serializers.py +++ b/web/api/serializers.py @@ -42,6 +42,9 @@ class ScanHistorySerializer(serializers.ModelSerializer): subdomain_count = serializers.SerializerMethodField('get_subdomain_count') endpoint_count = serializers.SerializerMethodField('get_endpoint_count') vulnerability_count = serializers.SerializerMethodField('get_vulnerability_count') + current_progress = serializers.SerializerMethodField('get_progress') + completed_time = serializers.SerializerMethodField('get_completed_time_in_sec') + elapsed_time = serializers.SerializerMethodField('get_elapsed_time') class Meta: model = ScanHistory @@ -60,6 +63,15 @@ class ScanHistorySerializer(serializers.ModelSerializer): if scan_history.get_vulnerability_count: return scan_history.get_vulnerability_count() + def get_progress(self, scan_history): + return scan_history.get_progress() + + def get_completed_time_in_sec(self, scan_history): + return scan_history.get_completed_time_in_sec() + + def get_elapsed_time(self, scan_history): + return scan_history.get_elapsed_time() + class OrganizationSerializer(serializers.ModelSerializer): diff --git a/web/api/views.py b/web/api/views.py index 09c78fcb..a6dea5bb 100644 --- a/web/api/views.py +++ b/web/api/views.py @@ -40,9 +40,9 @@ class ScanStatus(APIView): pending_scans = ScanHistory.objects.filter(scan_status=-1) response = { - 'recently_completed_scans': ScanHistorySerializer(recently_completed_scans, many=True).data, - 'scanning': currently_scanning.values(), - 'pending': pending_scans.values() + 'pending': ScanHistorySerializer(pending_scans, many=True).data, + 'scanning': ScanHistorySerializer(currently_scanning, many=True).data, + 'recently_completed_scans': ScanHistorySerializer(recently_completed_scans, many=True).data } return Response(response) diff --git a/web/dashboard/views.py b/web/dashboard/views.py index f6d517bf..8bc5b9f6 100644 --- a/web/dashboard/views.py +++ b/web/dashboard/views.py @@ -26,11 +26,6 @@ def index(request): Subdomain.objects.all().exclude(http_status__exact=0).count() endpoint_alive_count = \ EndPoint.objects.filter(http_status__exact=200).count() - recent_completed_scans = ScanHistory.objects.all().order_by( - '-start_scan_date').filter(Q(scan_status=0) | Q(scan_status=2) | Q(scan_status=3))[:5] - currently_scanning = ScanHistory.objects.order_by( - '-start_scan_date').filter(scan_status=1)[:5] - pending_scans = ScanHistory.objects.filter(scan_status=-1)[:5] info_count = Vulnerability.objects.filter(severity=0).count() low_count = Vulnerability.objects.filter(severity=1).count() medium_count = Vulnerability.objects.filter(severity=2).count() @@ -118,9 +113,6 @@ def index(request): 'subdomain_with_ip_count': subdomain_with_ip_count, 'alive_count': alive_count, 'endpoint_alive_count': endpoint_alive_count, - 'recent_completed_scans': recent_completed_scans, - 'pending_scans': pending_scans, - 'currently_scanning': currently_scanning, 'info_count': info_count, 'low_count': low_count, 'medium_count': medium_count, diff --git a/web/startScan/migrations/0002_auto_20211019_1756.py b/web/startScan/migrations/0002_auto_20211019_1756.py new file mode 100644 index 00000000..dcf454b5 --- /dev/null +++ b/web/startScan/migrations/0002_auto_20211019_1756.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.4 on 2021-10-19 17:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('startScan', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='scanhistory', + name='dorks', + field=models.ManyToManyField(null=True, related_name='dorks', to='startScan.Dork'), + ), + migrations.AlterField( + model_name='scanhistory', + name='emails', + field=models.ManyToManyField(null=True, related_name='emails', to='startScan.Email'), + ), + migrations.AlterField( + model_name='scanhistory', + name='employees', + field=models.ManyToManyField(null=True, related_name='employees', to='startScan.Employee'), + ), + ] diff --git a/web/startScan/migrations/0003_auto_20211019_1757.py b/web/startScan/migrations/0003_auto_20211019_1757.py new file mode 100644 index 00000000..a553d266 --- /dev/null +++ b/web/startScan/migrations/0003_auto_20211019_1757.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.4 on 2021-10-19 17:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('startScan', '0002_auto_20211019_1756'), + ] + + operations = [ + migrations.AlterField( + model_name='scanhistory', + name='dorks', + field=models.ManyToManyField(blank=True, related_name='dorks', to='startScan.Dork'), + ), + migrations.AlterField( + model_name='scanhistory', + name='emails', + field=models.ManyToManyField(blank=True, related_name='emails', to='startScan.Email'), + ), + migrations.AlterField( + model_name='scanhistory', + name='employees', + field=models.ManyToManyField(blank=True, related_name='employees', to='startScan.Employee'), + ), + ] diff --git a/web/startScan/models.py b/web/startScan/models.py index d79d54c4..13cf21ba 100644 --- a/web/startScan/models.py +++ b/web/startScan/models.py @@ -30,9 +30,9 @@ class ScanHistory(models.Model): used_gf_patterns = models.CharField(max_length=500, null=True, blank=True) # osint is directly linked to scan history and not subdomains - emails = models.ManyToManyField('Email', related_name='emails') - employees = models.ManyToManyField('Employee', related_name='employees') - dorks = models.ManyToManyField('Dork', related_name='dorks') + emails = models.ManyToManyField('Email', related_name='emails', blank=True) + employees = models.ManyToManyField('Employee', related_name='employees', blank=True) + dorks = models.ManyToManyField('Dork', related_name='dorks', blank=True) def __str__(self): # debug purpose remove scan type and id in prod @@ -90,6 +90,25 @@ class ScanHistory(models.Model): scan_history__id=self.id).filter( severity=4).count() + def get_progress(self): + ''' + formulae to calculate + count number of true things to do, for http crawler, it is always +1 + divided by total scan activity associated - 2 (Start and Stop) + ''' + number_of_steps = sum([ + self.subdomain_discovery, + self.dir_file_search, + self.port_scan, + self.fetch_url, + self.vulnerability_scan, + self.osint, + self.screenshot, + True + ]) + steps_done = len(self.scanactivity_set.all()) + return (number_of_steps / (steps_done - 2))*100 + def get_completed_time(self): return self.stop_scan_date - self.start_scan_date diff --git a/web/static/custom/dashboard.js b/web/static/custom/dashboard.js new file mode 100644 index 00000000..3337d06e --- /dev/null +++ b/web/static/custom/dashboard.js @@ -0,0 +1,95 @@ +function getScanStatusSidebar(reload) { + $.getJSON('/api/scan_status', function(data) { + $('#currently_scanning').empty(); + $('#recently_completed_scans').empty(); + $('#upcoming_scans').empty(); + + if (data['scanning'].length > 0){ + $('#current_scan_count').html(`${data['scanning'].length} Scans Currently Running`) + for (var scan in data['scanning']) { + scan_object = data['scanning'][scan]; + $('#currently_scanning').append(` +

${scan_object.domain.name}${scan_object.current_progress}%

+

Started ${scan_object.elapsed_time} ago

+
${scan_object.scan_type.engine_name}
+

+   ${scan_object.subdomain_count}   +   ${scan_object.endpoint_count}   +   ${scan_object.vulnerability_count}  

+
+
+
+
`); + } + } + else{ + $('#currently_scanning').html(``); + } + + if (data['recently_completed_scans'].length > 0){ + for (var scan in data['recently_completed_scans']) { + scan_object = data['recently_completed_scans'][scan]; + if (scan_object.scan_status == 0 ) { + bg_color = 'bg-soft-danger'; + status_badge = 'Failed'; + } + else if (scan_object.scan_status == 3) { + bg_color = 'bg-soft-danger'; + status_badge = 'Aborted'; + } + else if (scan_object.scan_status == 2){ + bg_color = 'bg-soft-success'; + status_badge = 'Successful'; + } + + $('#recently_completed_scans').append(` +

${scan_object.domain.name}${status_badge}

+
${scan_object.scan_type.engine_name}
+

+   ${scan_object.subdomain_count}   +   ${scan_object.endpoint_count}   +   ${scan_object.vulnerability_count}  

+
`); + } + } + else{ + $('#recently_completed_scans').html(``); + } + + if (data['pending'].length > 0){ + $('#pending_scan_count').html(`${data['pending'].length} Scans Pending`) + for (var scan in data['pending']) { + scan_object = data['pending'][scan]; + $('#upcoming_scans').append(` +

${scan_object.domain.name}

+
${scan_object.scan_type.engine_name}
+
`); + } + } + else{ + $('#upcoming_scans').html(``); + } + }).done(function() { + tippy('.badge-subdomain-count', { + content: 'Subdomains', + }); + tippy('.badge-endpoint-count', { + content: 'Endpoints', + }); + tippy('.badge-vuln-count', { + content: 'Vulnerabilities', + }); + tippy('.badge-scan_engine-type', { + content: 'Scan Engine', + }); + if(reload){ + Snackbar.show({ + text: 'Scan Status Reloaded.', + pos: 'top-right', + actionTextColor: '#42A5F5', + duration: 1500 + }); + } + }); + +} diff --git a/web/static/plugins/snackbar.min.css b/web/static/plugins/snackbar.min.css new file mode 100644 index 00000000..e74cee78 --- /dev/null +++ b/web/static/plugins/snackbar.min.css @@ -0,0 +1 @@ +.snackbar-container{transition:all .5s ease;transition-property:top,right,bottom,left,opacity;font-family:Roboto,sans-serif;font-size:14px;min-height:14px;background-color:#070b0e;position:fixed;display:flex;justify-content:space-between;align-items:center;color:#fff;line-height:22px;padding:18px 24px;bottom:-100px;top:-100px;opacity:0;z-index:9999}.snackbar-container .action{background:inherit;display:inline-block;border:none;font-size:inherit;text-transform:uppercase;color:#4caf50;margin:0 0 0 24px;padding:0;min-width:min-content;cursor:pointer}@media (min-width:640px){.snackbar-container{min-width:288px;max-width:568px;display:inline-flex;border-radius:2px;margin:24px}}@media (max-width:640px){.snackbar-container{left:0;right:0;width:100%}}.snackbar-pos.bottom-center{top:auto!important;bottom:0;left:50%;transform:translate(-50%,0)}.snackbar-pos.bottom-left{top:auto!important;bottom:0;left:0}.snackbar-pos.bottom-right{top:auto!important;bottom:0;right:0}.snackbar-pos.top-left{bottom:auto!important;top:0;left:0}.snackbar-pos.top-center{bottom:auto!important;top:0;left:50%;transform:translate(-50%,0)}.snackbar-pos.top-right{bottom:auto!important;top:0;right:0}@media (max-width:640px){.snackbar-pos.bottom-center,.snackbar-pos.top-center{left:0;transform:none}} \ No newline at end of file diff --git a/web/static/plugins/snackbar.min.js b/web/static/plugins/snackbar.min.js new file mode 100644 index 00000000..1e191a37 --- /dev/null +++ b/web/static/plugins/snackbar.min.js @@ -0,0 +1,17 @@ +/*! + * Snackbar v0.1.14 + * http://polonel.com/Snackbar + * + * Copyright 2018 Chris Brame and other contributors + * Released under the MIT license + * https://github.com/polonel/Snackbar/blob/master/LICENSE + */ +!function(a,b){"use strict";"function"==typeof define&&define.amd?define([],function(){return a.Snackbar=b()}):"object"==typeof module&&module.exports?module.exports=a.Snackbar=b():a.Snackbar=b()}(this,function(){var a={};a.current=null;var b={text:"Default Text",textColor:"#FFFFFF",width:"auto",showAction:!0,actionText:"Dismiss",actionTextAria:"Dismiss, Description for Screen Readers",alertScreenReader:!1,actionTextColor:"#4CAF50",showSecondButton:!1,secondButtonText:"",secondButtonAria:"Description for Screen Readers",secondButtonTextColor:"#4CAF50",backgroundColor:"#323232",pos:"bottom-left",duration:5e3,customClass:"",onActionClick:function(a){a.style.opacity=0},onSecondButtonClick:function(a){},onClose:function(a){}};a.show=function(d){var e=c(!0,b,d);a.current&&(a.current.style.opacity=0,setTimeout(function(){var a=this.parentElement;a&& +// possible null if too many/fast Snackbars +a.removeChild(this)}.bind(a.current),500)),a.snackbar=document.createElement("div"),a.snackbar.className="snackbar-container "+e.customClass,a.snackbar.style.width=e.width;var f=document.createElement("p");if(f.style.margin=0,f.style.padding=0,f.style.color=e.textColor,f.style.fontSize="14px",f.style.fontWeight=300,f.style.lineHeight="1em",f.innerHTML=e.text,a.snackbar.appendChild(f),a.snackbar.style.background=e.backgroundColor,e.showSecondButton){var g=document.createElement("button");g.className="action",g.innerHTML=e.secondButtonText,g.setAttribute("aria-label",e.secondButtonAria),g.style.color=e.secondButtonTextColor,g.addEventListener("click",function(){e.onSecondButtonClick(a.snackbar)}),a.snackbar.appendChild(g)}if(e.showAction){var h=document.createElement("button");h.className="action",h.innerHTML=e.actionText,h.setAttribute("aria-label",e.actionTextAria),h.style.color=e.actionTextColor,h.addEventListener("click",function(){e.onActionClick(a.snackbar)}),a.snackbar.appendChild(h)}e.duration&&setTimeout(function(){a.current===this&&(a.current.style.opacity=0, +// When natural remove event occurs let's move the snackbar to its origins +a.current.style.top="-100px",a.current.style.bottom="-100px")}.bind(a.snackbar),e.duration),e.alertScreenReader&&a.snackbar.setAttribute("role","alert"),a.snackbar.addEventListener("transitionend",function(b,c){"opacity"===b.propertyName&&"0"===this.style.opacity&&("function"==typeof e.onClose&&e.onClose(this),this.parentElement.removeChild(this),a.current===this&&(a.current=null))}.bind(a.snackbar)),a.current=a.snackbar,document.body.appendChild(a.snackbar);getComputedStyle(a.snackbar).bottom,getComputedStyle(a.snackbar).top;a.snackbar.style.opacity=1,a.snackbar.className="snackbar-container "+e.customClass+" snackbar-pos "+e.pos},a.close=function(){a.current&&(a.current.style.opacity=0)}; +// Pure JS Extend +// http://gomakethings.com/vanilla-javascript-version-of-jquery-extend/ +var c=function(){var a={},b=!1,d=0,e=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(b=arguments[0],d++);for(var f=function(d){for(var e in d)Object.prototype.hasOwnProperty.call(d,e)&&(b&&"[object Object]"===Object.prototype.toString.call(d[e])?a[e]=c(!0,a[e],d[e]):a[e]=d[e])};d - -