mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-08-24 23:02:24 +02:00
1146 lines
42 KiB
Python
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 |