Files
rengine/web/startScan/views.py
T
2024-08-31 12:59:52 +05:30

1146 lines
42 KiB
Python

import markdown
from celery import group
from weasyprint import HTML, CSS
from datetime import datetime
from django.contrib import messages
from django.db.models import Count, Case, When, IntegerField
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.template.loader import get_template
from django.urls import reverse
from django.utils import timezone
from django_celery_beat.models import (ClockedSchedule, IntervalSchedule, PeriodicTask)
from rolepermissions.decorators import has_permission_decorator
from reNgine.celery import app
from reNgine.charts import *
from reNgine.common_func import *
from reNgine.definitions import ABORTED_TASK, SUCCESS_TASK
from reNgine.tasks import create_scan_activity, initiate_scan, run_command
from scanEngine.models import EngineType
from startScan.models import *
from targetApp.models import *
def scan_history(request, slug):
host = ScanHistory.objects.filter(domain__project__slug=slug).order_by('-start_scan_date')
context = {'scan_history_active': 'active', "scan_history": host}
return render(request, 'startScan/history.html', context)
def subscan_history(request, slug):
subscans = SubScan.objects.filter(scan_history__domain__project__slug=slug).order_by('-start_scan_date')
context = {'scan_history_active': 'active', "subscans": subscans}
return render(request, 'startScan/subscan_history.html', context)
def detail_scan(request, id, slug):
ctx = {}
# Get scan objects
scan = get_object_or_404(ScanHistory, id=id)
domain_id = scan.domain.id
scan_engines = EngineType.objects.order_by('engine_name').all()
recent_scans = ScanHistory.objects.filter(domain__id=domain_id)
last_scans = (
ScanHistory.objects
.filter(domain__id=domain_id)
.filter(tasks__overlap=['subdomain_discovery'])
.filter(id__lte=id)
.filter(scan_status=2)
)
# Get all kind of objects associated with our ScanHistory object
emails = Email.objects.filter(emails__in=[scan])
employees = Employee.objects.filter(employees__in=[scan])
subdomains = Subdomain.objects.filter(scan_history=scan)
endpoints = EndPoint.objects.filter(scan_history=scan)
vulns = Vulnerability.objects.filter(scan_history=scan)
vulns_tags = VulnerabilityTags.objects.filter(vuln_tags__in=vulns)
ip_addresses = IpAddress.objects.filter(ip_addresses__in=subdomains)
geo_isos = CountryISO.objects.filter(ipaddress__in=ip_addresses)
scan_activity = ScanActivity.objects.filter(scan_of__id=id).order_by('time')
cves = CveId.objects.filter(cve_ids__in=vulns)
cwes = CweId.objects.filter(cwe_ids__in=vulns)
# HTTP statuses
http_statuses = (
subdomains
.exclude(http_status=0)
.values('http_status')
.annotate(Count('http_status'))
)
# CVEs / CWes
common_cves = (
cves
.annotate(nused=Count('cve_ids'))
.order_by('-nused')
.values('name', 'nused')
[:10]
)
common_cwes = (
cwes
.annotate(nused=Count('cwe_ids'))
.order_by('-nused')
.values('name', 'nused')
[:10]
)
# Tags
common_tags = (
vulns_tags
.annotate(nused=Count('vuln_tags'))
.order_by('-nused')
.values('name', 'nused')
[:7]
)
# Countries
asset_countries = (
geo_isos
.annotate(count=Count('iso'))
.order_by('-count')
)
# Subdomains
subdomain_count = (
subdomains
.values('name')
.distinct()
.count()
)
alive_count = (
subdomains
.values('name')
.distinct()
.filter(http_status__exact=200)
.count()
)
important_count = (
subdomains
.values('name')
.distinct()
.filter(is_important=True)
.count()
)
# Endpoints
endpoint_count = (
endpoints
.values('http_url')
.distinct()
.count()
)
endpoint_alive_count = (
endpoints
.filter(http_status__exact=200) # TODO: use is_alive() func as it's more precise
.values('http_url')
.distinct()
.count()
)
# Vulnerabilities
common_vulns = (
vulns
.exclude(severity=0)
.values('name', 'severity')
.annotate(count=Count('name'))
.order_by('-count')
[:10]
)
info_count = vulns.filter(severity=0).count()
low_count = vulns.filter(severity=1).count()
medium_count = vulns.filter(severity=2).count()
high_count = vulns.filter(severity=3).count()
critical_count = vulns.filter(severity=4).count()
unknown_count = vulns.filter(severity=-1).count()
total_count = vulns.count()
total_count_ignore_info = vulns.exclude(severity=0).count()
# Emails
exposed_count = emails.exclude(password__isnull=True).count()
# Build render context
ctx = {
'scan_history_id': id,
'history': scan,
'scan_activity': scan_activity,
'subdomain_count': subdomain_count,
'alive_count': alive_count,
'important_count': important_count,
'endpoint_count': endpoint_count,
'endpoint_alive_count': endpoint_alive_count,
'info_count': info_count,
'low_count': low_count,
'medium_count': medium_count,
'high_count': high_count,
'critical_count': critical_count,
'unknown_count': unknown_count,
'total_vulnerability_count': total_count,
'total_vul_ignore_info_count': total_count_ignore_info,
'vulnerability_list': vulns.order_by('-severity').all(),
'scan_history_active': 'active',
'scan_engines': scan_engines,
'exposed_count': exposed_count,
'email_count': emails.count(),
'employees_count': employees.count(),
'most_recent_scans': recent_scans.order_by('-start_scan_date')[:1],
'http_status_breakdown': http_statuses,
'most_common_cve': common_cves,
'most_common_cwe': common_cwes,
'most_common_tags': common_tags,
'most_common_vulnerability': common_vulns,
'asset_countries': asset_countries,
}
# Find number of matched GF patterns
if scan.used_gf_patterns:
count_gf = {}
for gf in scan.used_gf_patterns.split(','):
count_gf[gf] = (
endpoints
.filter(matched_gf_patterns__icontains=gf)
.count()
)
ctx['matched_gf_count'] = count_gf
# Find last scan for this domain
if last_scans.count() > 1:
last_scan = last_scans.order_by('-start_scan_date')[1]
ctx['last_scan'] = last_scan
return render(request, 'startScan/detail_scan.html', ctx)
def all_subdomains(request, slug):
subdomains = Subdomain.objects.filter(target_domain__project__slug=slug)
scan_engines = EngineType.objects.order_by('engine_name').all()
alive_subdomains = subdomains.filter(http_status__exact=200) # TODO: replace this with is_alive() function
important_subdomains = (
subdomains
.filter(is_important=True)
.values('name')
.distinct()
.count()
)
context = {
'scan_history_id': id,
'scan_history_active': 'active',
'scan_engines': scan_engines,
'subdomain_count': subdomains.values('name').distinct().count(),
'alive_count': alive_subdomains.values('name').distinct().count(),
'important_count': important_subdomains
}
return render(request, 'startScan/subdomains.html', context)
def detail_vuln_scan(request, slug, id=None):
if id:
history = get_object_or_404(ScanHistory, id=id)
history.filter(domain__project__slug=slug)
context = {'scan_history_id': id, 'history': history}
else:
context = {'vuln_scan_active': 'true'}
return render(request, 'startScan/vulnerabilities.html', context)
def all_endpoints(request, slug):
context = {
'scan_history_active': 'active'
}
return render(request, 'startScan/endpoints.html', context)
@has_permission_decorator(PERM_INITATE_SCANS_SUBSCANS, redirect_url=FOUR_OH_FOUR_URL)
def start_scan_ui(request, slug, domain_id):
domain = get_object_or_404(Domain, id=domain_id)
if request.method == "POST":
# Get imported and out-of-scope subdomains
subdomains_in = request.POST['importSubdomainTextArea'].split()
subdomains_in = [s.rstrip() for s in subdomains_in if s]
subdomains_out = request.POST['outOfScopeSubdomainTextarea'].split()
subdomains_out = [s.rstrip() for s in subdomains_out if s]
starting_point_path = request.POST['startingPointPath'].strip()
excluded_paths = request.POST['excludedPaths'] # string separated by ,
# split excluded paths by ,
excluded_paths = [path.strip() for path in excluded_paths.split(',')]
# Get engine type
engine_id = request.POST['scan_mode']
# Create ScanHistory object
scan_history_id = create_scan_object(
host_id=domain_id,
engine_id=engine_id,
initiated_by_id=request.user.id
)
scan = ScanHistory.objects.get(pk=scan_history_id)
# Start the celery task
kwargs = {
'scan_history_id': scan.id,
'domain_id': domain.id,
'engine_id': engine_id,
'scan_type': LIVE_SCAN,
'results_dir': '/usr/src/scan_results',
'imported_subdomains': subdomains_in,
'out_of_scope_subdomains': subdomains_out,
'starting_point_path': starting_point_path,
'excluded_paths': excluded_paths,
'initiated_by_id': request.user.id
}
initiate_scan.apply_async(kwargs=kwargs)
scan.save()
# Send start notif
messages.add_message(
request,
messages.INFO,
f'Scan Started for {domain.name}')
return HttpResponseRedirect(reverse('scan_history', kwargs={'slug': slug}))
# GET request
is_rescan = request.GET.get('rescan', 'false').lower() == 'true'
history_id = request.GET.get('history_id', None)
# default values
subdomains_in = []
subdomains_out = []
starting_point_path = None
excluded_paths = []
selected_engine_id = None
if is_rescan and history_id:
previous_scan = get_object_or_404(ScanHistory, id=history_id)
selected_engine_id = getattr(previous_scan.scan_type, 'id', None)
subdomains_in = getattr(previous_scan, 'cfg_imported_subdomains', None)
subdomains_out = getattr(previous_scan, 'cfg_out_of_scope_subdomains', None)
starting_point_path = getattr(previous_scan, 'cfg_starting_point_path', None)
excluded_paths = getattr(previous_scan, 'cfg_excluded_paths', None)
engines = EngineType.objects.order_by('engine_name')
custom_engines_count = (
EngineType.objects
.filter(default_engine=False)
.count()
)
excluded_paths = ','.join(DEFAULT_EXCLUDED_PATHS) if not excluded_paths else ','.join(excluded_paths)
# context values
context = {
'scan_history_active': 'active',
'domain': domain,
'engines': engines,
'custom_engines_count': custom_engines_count,
'excluded_paths': excluded_paths,
'subdomains_in': subdomains_in,
'subdomains_out': subdomains_out,
'starting_point_path': starting_point_path,
'selected_engine_id': selected_engine_id,
}
return render(request, 'startScan/start_scan_ui.html', context)
@has_permission_decorator(PERM_INITATE_SCANS_SUBSCANS, redirect_url=FOUR_OH_FOUR_URL)
def start_multiple_scan(request, slug):
# domain = get_object_or_404(Domain, id=host_id)
if request.method == "POST":
if request.POST.get('scan_mode', 0):
# if scan mode is available, then start the scan
# get engine type
engine_id = request.POST['scan_mode']
list_of_domain_ids = request.POST['domain_ids']
subdomains_in = request.POST['importSubdomainTextArea'].split()
subdomains_in = [s.rstrip() for s in subdomains_in if s]
subdomains_out = request.POST['outOfScopeSubdomainTextarea'].split()
subdomains_out = [s.rstrip() for s in subdomains_out if s]
starting_point_path = request.POST['startingPointPath'].strip()
excluded_paths = request.POST['excludedPaths'] # string separated by ,
# split excluded paths by ,
excluded_paths = [path.strip() for path in excluded_paths.split(',')]
grouped_scans = []
for domain_id in list_of_domain_ids.split(","):
# Start the celery task
scan_history_id = create_scan_object(
host_id=domain_id,
engine_id=engine_id,
initiated_by_id=request.user.id
)
# domain = get_object_or_404(Domain, id=domain_id)
kwargs = {
'scan_history_id': scan_history_id,
'domain_id': domain_id,
'engine_id': engine_id,
'scan_type': LIVE_SCAN,
'results_dir': '/usr/src/scan_results',
'initiated_by_id': request.user.id,
'imported_subdomains': subdomains_in,
'out_of_scope_subdomains': subdomains_out,
'starting_point_path': starting_point_path,
'excluded_paths': excluded_paths,
}
_scan_task = initiate_scan.si(**kwargs)
grouped_scans.append(_scan_task)
celery_group = group(grouped_scans)
celery_group.apply_async()
# Send start notif
messages.add_message(
request,
messages.INFO,
'Scan Started for multiple targets')
return HttpResponseRedirect(reverse('scan_history', kwargs={'slug': slug}))
else:
# this else condition will have post request from the scan page
# containing all the targets id
list_of_domain_name = []
list_of_domain_id = []
for key, value in request.POST.items():
if key != "list_target_table_length" and key != "csrfmiddlewaretoken":
domain = get_object_or_404(Domain, id=value)
list_of_domain_name.append(domain.name)
list_of_domain_id.append(value)
domain_ids = ",".join(list_of_domain_id)
# GET request
engines = EngineType.objects
custom_engine_count = (
engines
.filter(default_engine=False)
.count()
)
excluded_paths = ','.join(DEFAULT_EXCLUDED_PATHS)
context = {
'scan_history_active': 'active',
'engines': engines,
'domain_list': list_of_domain_name,
'domain_ids': domain_ids,
'custom_engine_count': custom_engine_count,
'excluded_paths': excluded_paths
}
return render(request, 'startScan/start_multiple_scan_ui.html', context)
def export_subdomains(request, scan_id):
subdomain_list = Subdomain.objects.filter(scan_history__id=scan_id)
scan = ScanHistory.objects.get(id=scan_id)
response_body = ""
for domain in subdomain_list:
response_body += response_body + domain.name + "\n"
scan_start_date_str = str(scan.start_scan_date.date())
domain_name = scan.domain.name
response = HttpResponse(response_body, content_type='text/plain')
response['Content-Disposition'] = (
f'attachment; filename="subdomains_{domain_name}_{scan_start_date_str}.txt"'
)
return response
def export_endpoints(request, scan_id):
endpoint_list = EndPoint.objects.filter(scan_history__id=scan_id)
scan = ScanHistory.objects.get(id=scan_id)
response_body = ""
for endpoint in endpoint_list:
response_body += endpoint.http_url + "\n"
scan_start_date_str = str(scan.start_scan_date.date())
domain_name = scan.domain.name
response = HttpResponse(response_body, content_type='text/plain')
response['Content-Disposition'] = (
f'attachment; filename="endpoints_{domain_name}_{scan_start_date_str}.txt"'
)
return response
def export_urls(request, scan_id):
urls_list = Subdomain.objects.filter(scan_history__id=scan_id)
scan = ScanHistory.objects.get(id=scan_id)
response_body = ""
for url in urls_list:
if url.http_url:
response_body += response_body + url.http_url + "\n"
scan_start_date_str = str(scan.start_scan_date.date())
domain_name = scan.domain.name
response = HttpResponse(response_body, content_type='text/plain')
response['Content-Disposition'] = (
f'attachment; filename="urls_{domain_name}_{scan_start_date_str}.txt"'
)
return response
@has_permission_decorator(PERM_MODIFY_SCAN_RESULTS, redirect_url=FOUR_OH_FOUR_URL)
def delete_scan(request, id):
obj = get_object_or_404(ScanHistory, id=id)
if request.method == "POST":
delete_dir = obj.results_dir
run_command('rm -rf ' + delete_dir)
obj.delete()
messageData = {'status': 'true'}
messages.add_message(
request,
messages.INFO,
'Scan history successfully deleted!'
)
else:
messageData = {'status': 'false'}
messages.add_message(
request,
messages.INFO,
'Oops! something went wrong!'
)
return JsonResponse(messageData)
@has_permission_decorator(PERM_INITATE_SCANS_SUBSCANS, redirect_url=FOUR_OH_FOUR_URL)
def stop_scan(request, id):
if request.method == "POST":
scan = get_object_or_404(ScanHistory, id=id)
try:
for task_id in scan.celery_ids:
app.control.revoke(task_id, terminate=True, signal='SIGKILL')
# after celery task is stopped, update the scan status
scan.scan_status = ABORTED_TASK
scan.save()
tasks = (
ScanActivity.objects
.filter(scan_of=scan)
.filter(status=RUNNING_TASK)
.order_by('-pk')
)
for task in tasks:
app.control.revoke(task.celery_id, terminate=True, signal='SIGKILL')
task.status = ABORTED_TASK
task.time = timezone.now()
task.save()
create_scan_activity(scan.id, "Scan aborted", ABORTED_TASK)
response = {'status': True}
messages.add_message(
request,
messages.INFO,
'Scan successfully stopped!'
)
except Exception as e:
logger.error(e)
response = {'status': False}
messages.add_message(
request,
messages.ERROR,
f'Scan failed to stop ! Error: {str(e)}'
)
return JsonResponse(response)
return scan_history(request)
@has_permission_decorator(PERM_INITATE_SCANS_SUBSCANS, redirect_url=FOUR_OH_FOUR_URL)
def stop_scans(request, slug):
if request.method == "POST":
for key, value in request.POST.items():
if key == 'scan_history_table_length' or key == 'csrfmiddlewaretoken':
continue
scan = get_object_or_404(ScanHistory, id=value)
try:
for task_id in scan.celery_ids:
app.control.revoke(task_id, terminate=True, signal='SIGKILL')
tasks = (
ScanActivity.objects
.filter(scan_of=scan)
.filter(status=RUNNING_TASK)
.order_by('-pk')
)
for task in tasks:
app.control.revoke(task.celery_id, terminate=True, signal='SIGKILL')
task.status = ABORTED_TASK
task.time = timezone.now()
task.save()
create_scan_activity(scan.id, "Scan aborted", ABORTED_TASK)
messages.add_message(
request,
messages.INFO,
'Multiple scans successfully stopped!'
)
except Exception as e:
logger.error(e)
messages.add_message(
request,
messages.ERROR,
f'Scans failed to stop ! Error: {str(e)}'
)
return HttpResponseRedirect(reverse('scan_history', kwargs={'slug': slug}))
@has_permission_decorator(PERM_INITATE_SCANS_SUBSCANS, redirect_url=FOUR_OH_FOUR_URL)
def schedule_scan(request, host_id, slug):
domain = Domain.objects.get(id=host_id)
if request.method == "POST":
scheduled_mode = request.POST['scheduled_mode']
engine_type = int(request.POST['scan_mode'])
# Get imported and out-of-scope subdomains
subdomains_in = request.POST['importSubdomainTextArea'].split()
subdomains_in = [s.rstrip() for s in subdomains_in if s]
subdomains_out = request.POST['outOfScopeSubdomainTextarea'].split()
subdomains_out = [s.rstrip() for s in subdomains_out if s]
starting_point_path = request.POST['startingPointPath'].strip()
excluded_paths = request.POST['excludedPaths'] # string separated by ,
# split excluded paths by ,
excluded_paths = [path.strip() for path in excluded_paths.split(',')]
# Get engine type
engine = get_object_or_404(EngineType, id=engine_type)
timestr = str(datetime.strftime(timezone.now(), '%Y_%m_%d_%H_%M_%S'))
task_name = f'{engine.engine_name} for {domain.name}: {timestr}'
if scheduled_mode == 'periodic':
frequency_value = int(request.POST['frequency'])
frequency_type = request.POST['frequency_type']
if frequency_type == 'minutes':
period = IntervalSchedule.MINUTES
elif frequency_type == 'hours':
period = IntervalSchedule.HOURS
elif frequency_type == 'days':
period = IntervalSchedule.DAYS
elif frequency_type == 'weeks':
period = IntervalSchedule.DAYS
frequency_value *= 7
elif frequency_type == 'months':
period = IntervalSchedule.DAYS
frequency_value *= 30
schedule, _ = IntervalSchedule.objects.get_or_create(
every=frequency_value,
period=period)
kwargs = {
'domain_id': host_id,
'engine_id': engine.id,
'scan_history_id': 1,
'scan_type': SCHEDULED_SCAN,
'imported_subdomains': subdomains_in,
'out_of_scope_subdomains': subdomains_out,
'starting_point_path': starting_point_path,
'excluded_paths': excluded_paths,
'initiated_by_id': request.user.id
}
PeriodicTask.objects.create(
interval=schedule,
name=task_name,
task='initiate_scan',
kwargs=json.dumps(kwargs)
)
elif scheduled_mode == 'clocked':
schedule_time = request.POST['scheduled_time']
clock, _ = ClockedSchedule.objects.get_or_create(
clocked_time=schedule_time)
kwargs = {
'scan_history_id': 0,
'domain_id': host_id,
'engine_id': engine.id,
'scan_type': SCHEDULED_SCAN,
'imported_subdomains': subdomains_in,
'out_of_scope_subdomains': subdomains_out,
'starting_point_path': starting_point_path,
'excluded_paths': excluded_paths,
'initiated_by_id': request.user.id
}
PeriodicTask.objects.create(
clocked=clock,
one_off=True,
name=task_name,
task='initiate_scan',
kwargs=json.dumps(kwargs)
)
messages.add_message(
request,
messages.INFO,
f'Scan Scheduled for {domain.name}'
)
return HttpResponseRedirect(reverse('scheduled_scan_view', kwargs={'slug': slug}))
# GET request
engines = EngineType.objects
custom_engine_count = (
engines
.filter(default_engine=False)
.count()
)
excluded_paths = ','.join(DEFAULT_EXCLUDED_PATHS)
context = {
'scan_history_active': 'active',
'domain': domain,
'engines': engines,
'custom_engine_count': custom_engine_count,
'excluded_paths': excluded_paths
}
return render(request, 'startScan/schedule_scan_ui.html', context)
def scheduled_scan_view(request, slug):
scheduled_tasks = (
PeriodicTask.objects
.all()
.exclude(name='celery.backend_cleanup')
)
context = {
'scheduled_scan_active': 'active',
'scheduled_tasks': scheduled_tasks,
}
return render(request, 'startScan/schedule_scan_list.html', context)
@has_permission_decorator(PERM_MODIFY_SCAN_RESULTS, redirect_url=FOUR_OH_FOUR_URL)
def delete_scheduled_task(request, id):
task_object = get_object_or_404(PeriodicTask, id=id)
if request.method == "POST":
task_object.delete()
messageData = {'status': 'true'}
messages.add_message(
request,
messages.INFO,
'Scheduled Scan successfully deleted!')
else:
messageData = {'status': 'false'}
messages.add_message(
request,
messages.INFO,
'Oops! something went wrong!')
return JsonResponse(messageData)
@has_permission_decorator(PERM_MODIFY_SCAN_RESULTS, redirect_url=FOUR_OH_FOUR_URL)
def delete_scheduled_scans(request, slug):
if request.method == "POST":
for key, value in request.POST.items():
if 'task' in key or key == 'csrfmiddlewaretoken':
continue
try:
scan = get_object_or_404(PeriodicTask, id=value)
scan.delete()
except Exception as e:
logger.error(e)
messages.add_message(
request,
messages.INFO,
'Multiple scheduled scans successfully deleted!')
return HttpResponseRedirect(reverse('scheduled_scan_view', kwargs={'slug': slug}))
@has_permission_decorator(PERM_MODIFY_SCAN_RESULTS, redirect_url=FOUR_OH_FOUR_URL)
def change_scheduled_task_status(request, id):
if request.method == 'POST':
task = PeriodicTask.objects.get(id=id)
task.enabled = not task.enabled
task.save()
return HttpResponse('')
def change_vuln_status(request, id):
if request.method == 'POST':
vuln = Vulnerability.objects.get(id=id)
vuln.open_status = not vuln.open_status
vuln.save()
return HttpResponse('')
@has_permission_decorator(PERM_MODIFY_SYSTEM_CONFIGURATIONS, redirect_url=FOUR_OH_FOUR_URL)
def delete_all_scan_results(request):
if request.method == 'POST':
ScanHistory.objects.all().delete()
messageData = {'status': 'true'}
messages.add_message(
request,
messages.INFO,
'All Scan History successfully deleted!')
return JsonResponse(messageData)
@has_permission_decorator(PERM_MODIFY_SYSTEM_CONFIGURATIONS, redirect_url=FOUR_OH_FOUR_URL)
def delete_all_screenshots(request):
if request.method == 'POST':
run_command('rm -rf /usr/src/scan_results/*')
messageData = {'status': 'true'}
messages.add_message(
request,
messages.INFO,
'Screenshots successfully deleted!')
return JsonResponse(messageData)
def visualise(request, id):
scan = ScanHistory.objects.get(id=id)
context = {
'scan_id': id,
'scan_history': scan,
}
return render(request, 'startScan/visualise.html', context)
@has_permission_decorator(PERM_INITATE_SCANS_SUBSCANS, redirect_url=FOUR_OH_FOUR_URL)
def start_organization_scan(request, id, slug):
organization = get_object_or_404(Organization, id=id)
if request.method == "POST":
engine_id = request.POST['scan_mode']
subdomains_in = request.POST['importSubdomainTextArea'].split()
subdomains_in = [s.rstrip() for s in subdomains_in if s]
subdomains_out = request.POST['outOfScopeSubdomainTextarea'].split()
subdomains_out = [s.rstrip() for s in subdomains_out if s]
starting_point_path = request.POST['startingPointPath'].strip()
excluded_paths = request.POST['excludedPaths'] # string separated by ,
# split excluded paths by ,
excluded_paths = [path.strip() for path in excluded_paths.split(',')]
# Start Celery task for each organization's domains
for domain in organization.get_domains():
scan_history_id = create_scan_object(
host_id=domain.id,
engine_id=engine_id,
initiated_by_id=request.user.id
)
scan = ScanHistory.objects.get(pk=scan_history_id)
kwargs = {
'scan_history_id': scan.id,
'domain_id': domain.id,
'engine_id': engine_id,
'scan_type': LIVE_SCAN,
'results_dir': '/usr/src/scan_results',
'initiated_by_id': request.user.id,
'imported_subdomains': subdomains_in,
'out_of_scope_subdomains': subdomains_out,
'starting_point_path': starting_point_path,
'excluded_paths': excluded_paths,
}
initiate_scan.apply_async(kwargs=kwargs)
scan.save()
# Send start notif
ndomains = len(organization.get_domains())
messages.add_message(
request,
messages.INFO,
f'Scan Started for {ndomains} domains in organization {organization.name}')
return HttpResponseRedirect(reverse('scan_history', kwargs={'slug': slug}))
# GET request
engine = EngineType.objects.order_by('engine_name')
custom_engine_count = EngineType.objects.filter(default_engine=False).count()
domain_list = organization.get_domains()
excluded_paths = ','.join(DEFAULT_EXCLUDED_PATHS)
context = {
'organization_data_active': 'true',
'list_organization_li': 'active',
'organization': organization,
'engines': engine,
'domain_list': domain_list,
'custom_engine_count': custom_engine_count,
'excluded_paths': excluded_paths
}
return render(request, 'organization/start_scan.html', context)
@has_permission_decorator(PERM_INITATE_SCANS_SUBSCANS, redirect_url=FOUR_OH_FOUR_URL)
def schedule_organization_scan(request, slug, id):
organization =Organization.objects.get(id=id)
if request.method == "POST":
engine_type = int(request.POST['scan_mode'])
engine = get_object_or_404(EngineType, id=engine_type)
# post vars
scheduled_mode = request.POST['scheduled_mode']
subdomains_in = request.POST['importSubdomainTextArea'].split()
subdomains_in = [s.rstrip() for s in subdomains_in if s]
subdomains_out = request.POST['outOfScopeSubdomainTextarea'].split()
subdomains_out = [s.rstrip() for s in subdomains_out if s]
starting_point_path = request.POST['startingPointPath'].strip()
excluded_paths = request.POST['excludedPaths'] # string separated by ,
# split excluded paths by ,
excluded_paths = [path.strip() for path in excluded_paths.split(',')]
for domain in organization.get_domains():
timestr = str(datetime.strftime(timezone.now(), '%Y_%m_%d_%H_%M_%S'))
task_name = f'{engine.engine_name} for {domain.name}: {timestr}'
# Period task
if scheduled_mode == 'periodic':
frequency_value = int(request.POST['frequency'])
frequency_type = request.POST['frequency_type']
if frequency_type == 'minutes':
period = IntervalSchedule.MINUTES
elif frequency_type == 'hours':
period = IntervalSchedule.HOURS
elif frequency_type == 'days':
period = IntervalSchedule.DAYS
elif frequency_type == 'weeks':
period = IntervalSchedule.DAYS
frequency_value *= 7
elif frequency_type == 'months':
period = IntervalSchedule.DAYS
frequency_value *= 30
schedule, _ = IntervalSchedule.objects.get_or_create(
every=frequency_value,
period=period
)
_kwargs = json.dumps({
'domain_id': domain.id,
'engine_id': engine.id,
'scan_history_id': 0,
'scan_type': SCHEDULED_SCAN,
'initiated_by_id': request.user.id,
'imported_subdomains': subdomains_in,
'out_of_scope_subdomains': subdomains_out,
'starting_point_path': starting_point_path,
'excluded_paths': excluded_paths,
})
PeriodicTask.objects.create(
interval=schedule,
name=task_name,
task='initiate_scan',
kwargs=_kwargs
)
# Clocked task
elif scheduled_mode == 'clocked':
schedule_time = request.POST['scheduled_time']
clock, _ = ClockedSchedule.objects.get_or_create(
clocked_time=schedule_time
)
_kwargs = json.dumps({
'domain_id': domain.id,
'engine_id': engine.id,
'scan_history_id': 0,
'scan_type': LIVE_SCAN,
'initiated_by_id': request.user.id,
'imported_subdomains': subdomains_in,
'out_of_scope_subdomains': subdomains_out,
'starting_point_path': starting_point_path,
'excluded_paths': excluded_paths,
})
PeriodicTask.objects.create(clocked=clock,
one_off=True,
name=task_name,
task='initiate_scan',
kwargs=_kwargs
)
# Send start notif
ndomains = len(organization.get_domains())
messages.add_message(
request,
messages.INFO,
f'Scan started for {ndomains} domains in organization {organization.name}'
)
return HttpResponseRedirect(reverse('scheduled_scan_view', kwargs={'slug': slug}))
# GET request
engine = EngineType.objects
custom_engine_count = EngineType.objects.filter(default_engine=False).count()
excluded_paths = ','.join(DEFAULT_EXCLUDED_PATHS)
context = {
'scan_history_active': 'active',
'organization': organization,
'domain_list': organization.get_domains(),
'engines': engine,
'custom_engine_count': custom_engine_count,
'excluded_paths': excluded_paths
}
return render(request, 'organization/schedule_scan_ui.html', context)
@has_permission_decorator(PERM_MODIFY_SCAN_RESULTS, redirect_url=FOUR_OH_FOUR_URL)
def delete_scans(request, slug):
if request.method == "POST":
for key, value in request.POST.items():
if key == 'scan_history_table_length' or key == 'csrfmiddlewaretoken':
continue
scan = get_object_or_404(ScanHistory, id=value)
delete_dir = scan.results_dir
run_command('rm -rf ' + delete_dir)
scan.delete()
messages.add_message(
request,
messages.INFO,
'Multiple scans successfully deleted!')
return HttpResponseRedirect(reverse('scan_history', kwargs={'slug': slug}))
@has_permission_decorator(PERM_MODIFY_SCAN_REPORT, redirect_url=FOUR_OH_FOUR_URL)
def customize_report(request, id):
scan = ScanHistory.objects.get(id=id)
context = {
'scan_id': id,
'scan_history': scan,
}
return render(request, 'startScan/customize_report.html', context)
@has_permission_decorator(PERM_MODIFY_SCAN_REPORT, redirect_url=FOUR_OH_FOUR_URL)
def create_report(request, id):
primary_color = '#FFB74D'
secondary_color = '#212121'
# get report type
report_type = request.GET['report_type'] if 'report_type' in request.GET else 'full'
report_template = request.GET['report_template'] if 'report_template' in request.GET else 'default'
is_ignore_info_vuln = True if 'ignore_info_vuln' in request.GET else False
if report_type == 'recon':
show_recon = True
show_vuln = False
report_name = 'Reconnaissance Report'
elif report_type == 'vulnerability':
show_recon = False
show_vuln = True
report_name = 'Vulnerability Report'
else:
# default
show_recon = True
show_vuln = True
report_name = 'Full Scan Report'
scan = ScanHistory.objects.get(id=id)
vulns = (
Vulnerability.objects
.filter(scan_history=scan)
.order_by('-severity')
) if not is_ignore_info_vuln else (
Vulnerability.objects
.filter(scan_history=scan)
.exclude(severity=0)
.order_by('-severity')
)
unique_vulns = (
Vulnerability.objects
.filter(scan_history=scan)
.values("name", "severity")
.annotate(count=Count('name'))
.order_by('-severity', '-count')
) if not is_ignore_info_vuln else (
Vulnerability.objects
.filter(scan_history=scan)
.exclude(severity=0)
.values("name", "severity")
.annotate(count=Count('name'))
.order_by('-severity', '-count')
)
subdomains = (
Subdomain.objects
.filter(scan_history=scan)
.order_by('-content_length')
)
subdomain_alive_count = (
Subdomain.objects
.filter(scan_history__id=id)
.values('name')
.distinct()
.filter(http_status__exact=200)
.count()
)
interesting_subdomains = get_interesting_subdomains(scan_history=id)
interesting_subdomains = interesting_subdomains.annotate(
sort_order=Case(
When(http_status__gte=200, http_status__lt=300, then=1),
When(http_status__gte=300, http_status__lt=400, then=2),
When(http_status__gte=400, http_status__lt=500, then=3),
default=4,
output_field=IntegerField(),
)
).order_by('sort_order', 'http_status')
subdomains = subdomains.annotate(
sort_order=Case(
When(http_status__gte=200, http_status__lt=300, then=1),
When(http_status__gte=300, http_status__lt=400, then=2),
When(http_status__gte=400, http_status__lt=500, then=3),
default=4,
output_field=IntegerField(),
)
).order_by('sort_order', 'http_status')
ip_addresses = (
IpAddress.objects
.filter(ip_addresses__in=subdomains)
.distinct()
)
data = {
'scan_object': scan,
'unique_vulnerabilities': unique_vulns,
'all_vulnerabilities': vulns,
'all_vulnerabilities_count': vulns.count(),
'subdomain_alive_count': subdomain_alive_count,
'interesting_subdomains': interesting_subdomains,
'subdomains': subdomains,
'ip_addresses': ip_addresses,
'show_recon': show_recon,
'show_vuln': show_vuln,
'report_name': report_name,
'is_ignore_info_vuln': is_ignore_info_vuln,
}
# Get report related config
vuln_report_query = VulnerabilityReportSetting.objects.all()
if vuln_report_query.exists():
report = vuln_report_query[0]
data['company_name'] = report.company_name
data['company_address'] = report.company_address
data['company_email'] = report.company_email
data['company_website'] = report.company_website
data['show_rengine_banner'] = report.show_rengine_banner
data['show_footer'] = report.show_footer
data['footer_text'] = report.footer_text
data['show_executive_summary'] = report.show_executive_summary
# Replace executive_summary_description with template syntax
description = report.executive_summary_description
description = description.replace('{scan_date}', scan.start_scan_date.strftime('%d %B, %Y'))
description = description.replace('{company_name}', report.company_name)
description = description.replace('{target_name}', scan.domain.name)
description = description.replace('{subdomain_count}', str(subdomains.count()))
description = description.replace('{vulnerability_count}', str(vulns.count()))
description = description.replace('{critical_count}', str(vulns.filter(severity=4).count()))
description = description.replace('{high_count}', str(vulns.filter(severity=3).count()))
description = description.replace('{medium_count}', str(vulns.filter(severity=2).count()))
description = description.replace('{low_count}', str(vulns.filter(severity=1).count()))
description = description.replace('{info_count}', str(vulns.filter(severity=0).count()))
description = description.replace('{unknown_count}', str(vulns.filter(severity=-1).count()))
if scan.domain.description:
description = description.replace('{target_description}', scan.domain.description)
# Convert to Markdown
data['executive_summary_description'] = markdown.markdown(description)
primary_color = report.primary_color
secondary_color = report.secondary_color
data['primary_color'] = primary_color
data['secondary_color'] = secondary_color
data['subdomain_http_status_chart'] = generate_subdomain_chart_by_http_status(subdomains)
data['vulns_severity_chart'] = generate_vulnerability_chart_by_severity(vulns) if vulns else ''
if report_template == 'modern':
template = get_template('report/modern.html')
else:
template = get_template('report/default.html')
html = template.render(data)
pdf = HTML(string=html).write_pdf()
# pdf = HTML(string=html).write_pdf(stylesheets=[CSS(string='@page { size: A4; margin: 0; }')])
if 'download' in request.GET:
response = HttpResponse(pdf, content_type='application/octet-stream')
else:
response = HttpResponse(pdf, content_type='application/pdf')
return response