Files
rengine/web/startScan/models.py
T

681 lines
23 KiB
Python

from urllib.parse import urlparse
from django.apps import apps
from django.contrib.auth.models import User
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils import timezone
from reNgine.definitions import (CELERY_TASK_STATUSES,
NUCLEI_REVERSE_SEVERITY_MAP)
from reNgine.utilities import *
from scanEngine.models import EngineType
from targetApp.models import Domain
class hybrid_property:
def __init__(self, func):
self.func = func
self.name = func.__name__
self.exp = None
def __get__(self, instance, owner):
if instance is None:
return self
return self.func(instance)
def __set__(self, instance, value):
pass
def expression(self, exp):
self.exp = exp
return self
class ScanHistory(models.Model):
id = models.AutoField(primary_key=True)
start_scan_date = models.DateTimeField()
scan_status = models.IntegerField(choices=CELERY_TASK_STATUSES, default=-1)
results_dir = models.CharField(max_length=100, blank=True)
domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
scan_type = models.ForeignKey(EngineType, on_delete=models.CASCADE)
celery_ids = ArrayField(models.CharField(max_length=100), blank=True, default=list)
tasks = ArrayField(models.CharField(max_length=200), null=True)
stop_scan_date = models.DateTimeField(null=True, blank=True)
used_gf_patterns = models.CharField(max_length=500, null=True, blank=True)
error_message = models.CharField(max_length=300, blank=True, null=True)
emails = models.ManyToManyField('Email', related_name='emails', blank=True)
employees = models.ManyToManyField('Employee', related_name='employees', blank=True)
buckets = models.ManyToManyField('S3Bucket', related_name='buckets', blank=True)
dorks = models.ManyToManyField('Dork', related_name='dorks', blank=True)
initiated_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='initiated_scans', blank=True, null=True)
aborted_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='aborted_scans')
# scan related configs, prefix config fields with cfg_
cfg_out_of_scope_subdomains = ArrayField(
models.CharField(max_length=200),
blank=True,
null=True,
default=list
)
cfg_starting_point_path = models.CharField(max_length=200, blank=True, null=True)
cfg_excluded_paths = ArrayField(
models.CharField(max_length=200),
blank=True,
null=True,
default=list
)
cfg_imported_subdomains = ArrayField(
models.CharField(max_length=200),
blank=True,
null=True,
default=list
)
def __str__(self):
return self.domain.name
def get_subdomain_count(self):
return Subdomain.objects.filter(scan_history__id=self.id).count()
def get_subdomain_change_count(self):
last_scan = (
ScanHistory.objects
.filter(id=self.id)
.filter(tasks__overlap=['subdomain_discovery'])
.order_by('-start_scan_date')
)
scanned_host_q1 = (
Subdomain.objects
.filter(target_domain__id=self.domain.id)
.exclude(scan_history__id=last_scan[0].id)
.values('name')
)
scanned_host_q2 = (
Subdomain.objects
.filter(scan_history__id=last_scan[0].id)
.values('name')
)
new_subdomains = scanned_host_q2.difference(scanned_host_q1).count()
removed_subdomains = scanned_host_q1.difference(scanned_host_q2).count()
return [new_subdomains, removed_subdomains]
def get_endpoint_count(self):
return (
EndPoint.objects
.filter(scan_history__id=self.id)
.count()
)
def get_vulnerability_count(self):
return (
Vulnerability.objects
.filter(scan_history__id=self.id)
.count()
)
def get_unknown_vulnerability_count(self):
return (
Vulnerability.objects
.filter(scan_history__id=self.id)
.filter(severity=-1)
.count()
)
def get_info_vulnerability_count(self):
return (
Vulnerability.objects
.filter(scan_history__id=self.id)
.filter(severity=0)
.count()
)
def get_low_vulnerability_count(self):
return (
Vulnerability.objects
.filter(scan_history__id=self.id)
.filter(severity=1)
.count()
)
def get_medium_vulnerability_count(self):
return (
Vulnerability.objects
.filter(scan_history__id=self.id)
.filter(severity=2)
.count()
)
def get_high_vulnerability_count(self):
return (
Vulnerability.objects
.filter(scan_history__id=self.id)
.filter(severity=3)
.count()
)
def get_critical_vulnerability_count(self):
return (
Vulnerability.objects
.filter(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 = len(self.tasks) if self.tasks else 0
steps_done = len(self.scanactivity_set.all())
if steps_done and number_of_steps:
return round((number_of_steps / (steps_done)) * 100, 2)
def get_completed_ago(self):
if self.stop_scan_date:
return self.get_time_ago(self.stop_scan_date)
def get_total_scan_time_in_sec(self):
if self.stop_scan_date:
return (self.stop_scan_date - self.start_scan_date).seconds
def get_elapsed_time(self):
return self.get_time_ago(self.start_scan_date)
def get_time_ago(self, time):
duration = timezone.now() - time
days, seconds = duration.days, duration.seconds
hours = days * 24 + seconds // 3600
minutes = (seconds % 3600) // 60
seconds = seconds % 60
if not hours and not minutes:
return f'{seconds} seconds'
elif not hours:
return f'{minutes} minutes'
elif not minutes:
return f'{hours} hours'
return f'{hours} hours {minutes} minutes'
class Subdomain(models.Model):
# TODO: Add endpoint property instead of replicating endpoint fields here
id = models.AutoField(primary_key=True)
scan_history = models.ForeignKey(ScanHistory, on_delete=models.CASCADE, null=True, blank=True)
target_domain = models.ForeignKey(Domain, on_delete=models.CASCADE, null=True, blank=True)
name = models.CharField(max_length=1000)
is_imported_subdomain = models.BooleanField(default=False)
is_important = models.BooleanField(default=False, null=True, blank=True)
http_url = models.CharField(max_length=10000, null=True, blank=True)
screenshot_path = models.CharField(max_length=1000, null=True, blank=True)
http_header_path = models.CharField(max_length=1000, null=True, blank=True)
discovered_date = models.DateTimeField(blank=True, null=True)
cname = models.CharField(max_length=5000, blank=True, null=True)
is_cdn = models.BooleanField(default=False, blank=True, null=True)
cdn_name = models.CharField(max_length=200, blank=True, null=True)
http_status = models.IntegerField(default=0)
content_type = models.CharField(max_length=100, null=True, blank=True)
response_time = models.FloatField(null=True, blank=True)
webserver = models.CharField(max_length=1000, blank=True, null=True)
content_length = models.IntegerField(default=0, blank=True, null=True)
page_title = models.CharField(max_length=1000, blank=True, null=True)
technologies = models.ManyToManyField('Technology', related_name='technologies', blank=True)
ip_addresses = models.ManyToManyField('IPAddress', related_name='ip_addresses', blank=True)
directories = models.ManyToManyField('DirectoryScan', related_name='directories', blank=True)
waf = models.ManyToManyField('Waf', related_name='waf', blank=True)
attack_surface = models.TextField(null=True, blank=True)
def __str__(self):
return str(self.name)
@property
def get_endpoint_count(self):
endpoints = EndPoint.objects.filter(subdomain__name=self.name)
if self.scan_history:
endpoints = endpoints.filter(scan_history=self.scan_history)
return endpoints.count()
@property
def get_info_count(self):
return (
self.get_vulnerabilities
.filter(severity=0)
.count()
)
@property
def get_low_count(self):
return (
self.get_vulnerabilities
.filter(severity=1)
.count()
)
@property
def get_medium_count(self):
return (
self.get_vulnerabilities
.filter(severity=2)
.count()
)
@property
def get_high_count(self):
return (
self.get_vulnerabilities
.filter(severity=3)
.count()
)
@property
def get_critical_count(self):
return (
self.get_vulnerabilities
.filter(severity=4)
.count()
)
@property
def get_total_vulnerability_count(self):
return self.get_vulnerabilities.count()
@property
def get_vulnerabilities(self):
vulns = Vulnerability.objects.filter(subdomain__name=self.name)
if self.scan_history:
vulns = vulns.filter(scan_history=self.scan_history)
return vulns
@property
def get_vulnerabilities_without_info(self):
vulns = Vulnerability.objects.filter(subdomain__name=self.name).exclude(severity=0)
if self.scan_history:
vulns = vulns.filter(scan_history=self.scan_history)
return vulns
@property
def get_directories_count(self):
subdomains = (
Subdomain.objects
.filter(id=self.id)
)
dirscan = (
DirectoryScan.objects
.filter(directories__in=subdomains)
)
return (
DirectoryFile.objects
.filter(directory_files__in=dirscan)
.distinct()
.count()
)
@property
def get_todos(self):
TodoNote = apps.get_model('recon_note', 'TodoNote')
notes = TodoNote.objects
if self.scan_history:
notes = notes.filter(scan_history=self.scan_history)
notes = notes.filter(subdomain__id=self.id)
return notes.values()
@property
def get_subscan_count(self):
return (
SubScan.objects
.filter(subdomain__id=self.id)
.distinct()
.count()
)
class SubScan(models.Model):
id = models.AutoField(primary_key=True)
type = models.CharField(max_length=100, blank=True, null=True)
start_scan_date = models.DateTimeField()
status = models.IntegerField()
celery_ids = ArrayField(models.CharField(max_length=100), blank=True, default=list)
scan_history = models.ForeignKey(ScanHistory, on_delete=models.CASCADE)
subdomain = models.ForeignKey(Subdomain, on_delete=models.CASCADE)
stop_scan_date = models.DateTimeField(null=True, blank=True)
error_message = models.CharField(max_length=300, blank=True, null=True)
engine = models.ForeignKey(EngineType, on_delete=models.CASCADE, blank=True, null=True)
subdomain_subscan_ids = models.ManyToManyField('Subdomain', related_name='subdomain_subscan_ids', blank=True)
def get_completed_ago(self):
if self.stop_scan_date:
return get_time_taken(timezone.now(), self.stop_scan_date)
def get_total_time_taken(self):
if self.stop_scan_date:
return get_time_taken(self.stop_scan_date, self.start_scan_date)
def get_elapsed_time(self):
return get_time_taken(timezone.now(), self.start_scan_date)
def get_task_name_str(self):
taskmap = {
'subdomain_discovery': 'Subdomain discovery',
'dir_file_fuzz': 'Directory and File fuzzing',
'port_scan': 'Port Scan',
'fetch_url': 'Fetch URLs',
'vulnerability_scan': 'Vulnerability Scan',
'screenshot': 'Screenshot',
'waf_detection': 'Waf Detection',
'osint': 'Open-Source Intelligence'
}
return taskmap.get(self.type, 'Unknown')
class EndPoint(models.Model):
id = models.AutoField(primary_key=True)
scan_history = models.ForeignKey(ScanHistory, on_delete=models.CASCADE, null=True, blank=True)
target_domain = models.ForeignKey(
Domain, on_delete=models.CASCADE, null=True, blank=True)
subdomain = models.ForeignKey(
Subdomain,
on_delete=models.CASCADE,
null=True,
blank=True)
source = models.CharField(max_length=200, null=True, blank=True)
http_url = models.CharField(max_length=30000)
content_length = models.IntegerField(default=0, null=True, blank=True)
page_title = models.CharField(max_length=30000, null=True, blank=True)
http_status = models.IntegerField(default=0, null=True, blank=True)
content_type = models.CharField(max_length=100, null=True, blank=True)
discovered_date = models.DateTimeField(blank=True, null=True)
response_time = models.FloatField(null=True, blank=True)
webserver = models.CharField(max_length=1000, blank=True, null=True)
is_default = models.BooleanField(null=True, blank=True, default=False)
matched_gf_patterns = models.CharField(max_length=10000, null=True, blank=True)
techs = models.ManyToManyField('Technology', related_name='techs', null=True, blank=True)
# used for subscans
endpoint_subscan_ids = models.ManyToManyField('SubScan', related_name='endpoint_subscan_ids', blank=True)
def __str__(self):
return self.http_url
@hybrid_property
def is_alive(self):
return self.http_status and (0 < self.http_status < 500) and self.http_status != 404
class VulnerabilityTags(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class VulnerabilityReference(models.Model):
id = models.AutoField(primary_key=True)
url = models.CharField(max_length=5000)
def __str__(self):
return self.url
class CveId(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class CweId(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class GPTVulnerabilityReport(models.Model):
url_path = models.CharField(max_length=2000)
title = models.CharField(max_length=2500)
description = models.TextField(null=True, blank=True)
impact = models.TextField(null=True, blank=True)
remediation = models.TextField(null=True, blank=True)
references = models.ManyToManyField('VulnerabilityReference', related_name='report_reference', blank=True)
def __str__(self):
return self.title
class Vulnerability(models.Model):
id = models.AutoField(primary_key=True)
scan_history = models.ForeignKey(ScanHistory, on_delete=models.CASCADE, null=True, blank=True)
source = models.CharField(max_length=200, null=True, blank=True)
subdomain = models.ForeignKey(
Subdomain,
on_delete=models.CASCADE,
null=True,
blank=True)
endpoint = models.ForeignKey(
EndPoint,
on_delete=models.CASCADE,
blank=True,
null=True)
target_domain = models.ForeignKey(
Domain, on_delete=models.CASCADE, null=True, blank=True)
template = models.CharField(max_length=100, null=True, blank=True)
template_url = models.CharField(max_length=2500, null=True, blank=True)
template_id = models.CharField(max_length=200, null=True, blank=True)
matcher_name = models.CharField(max_length=500, null=True, blank=True)
name = models.CharField(max_length=2500)
severity = models.IntegerField()
description = models.TextField(null=True, blank=True)
impact = models.TextField(null=True, blank=True)
remediation = models.TextField(null=True, blank=True)
extracted_results = ArrayField(
models.CharField(max_length=5000), blank=True, null=True
)
tags = models.ManyToManyField('VulnerabilityTags', related_name='vuln_tags', blank=True)
references = models.ManyToManyField('VulnerabilityReference', related_name='vuln_reference', blank=True)
cve_ids = models.ManyToManyField('CveId', related_name='cve_ids', blank=True)
cwe_ids = models.ManyToManyField('CweId', related_name='cwe_ids', blank=True)
cvss_metrics = models.CharField(max_length=500, null=True, blank=True)
cvss_score = models.FloatField(null=True, blank=True, default=None)
curl_command = models.CharField(max_length=15000, null=True, blank=True)
type = models.CharField(max_length=100, null=True, blank=True)
http_url = models.CharField(max_length=10000, null=True)
discovered_date = models.DateTimeField(null=True)
open_status = models.BooleanField(null=True, blank=True, default=True)
hackerone_report_id = models.CharField(max_length=50, null=True, blank=True)
request = models.TextField(blank=True, null=True)
response = models.TextField(blank=True, null=True)
is_gpt_used = models.BooleanField(null=True, blank=True, default=False)
# used for subscans
vuln_subscan_ids = models.ManyToManyField('SubScan', related_name='vuln_subscan_ids', blank=True)
def __str__(self):
cve_str = ', '.join(f'`{cve.name}`' for cve in self.cve_ids.all())
severity = NUCLEI_REVERSE_SEVERITY_MAP[self.severity]
return f'{self.http_url} | `{severity.upper()}` | `{self.name}` | `{cve_str}`'
def get_severity(self):
return self.severity
def get_cve_str(self):
return ', '.join(f'`{cve.name}`' for cve in self.cve_ids.all())
def get_cwe_str(self):
return ', '.join(f'`{cwe.name}`' for cwe in self.cwe_ids.all())
def get_tags_str(self):
return ', '.join(f'`{tag.name}`' for tag in self.tags.all())
def get_refs_str(self):
return '•' + '\n• '.join(f'`{ref.url}`' for ref in self.references.all())
def get_path(self):
return urlparse(self.http_url).path
class ScanActivity(models.Model):
id = models.AutoField(primary_key=True)
scan_of = models.ForeignKey(ScanHistory, on_delete=models.CASCADE, blank=True, null=True)
title = models.CharField(max_length=1000)
name = models.CharField(max_length=1000)
time = models.DateTimeField()
status = models.IntegerField()
error_message = models.CharField(max_length=300, blank=True, null=True)
traceback = models.TextField(blank=True, null=True)
celery_id = models.CharField(max_length=100, blank=True, null=True)
def __str__(self):
return str(self.title)
class Command(models.Model):
id = models.AutoField(primary_key=True)
scan_history = models.ForeignKey(ScanHistory, on_delete=models.CASCADE, blank=True, null=True)
activity = models.ForeignKey(ScanActivity, on_delete=models.CASCADE, blank=True, null=True)
command = models.TextField(blank=True, null=True)
return_code = models.IntegerField(blank=True, null=True)
output = models.TextField(blank=True, null=True)
time = models.DateTimeField()
def __str__(self):
return str(self.command)
class Waf(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=500)
manufacturer = models.CharField(max_length=500, blank=True, null=True)
def __str__(self):
return str(self.name)
class Technology(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=500, blank=True, null=True)
def __str__(self):
return str(self.name)
class CountryISO(models.Model):
id = models.AutoField(primary_key=True)
iso = models.CharField(max_length=10, blank=True)
name = models.CharField(max_length=100, blank=True)
def __str__(self):
return str(self.name)
class IpAddress(models.Model):
id = models.AutoField(primary_key=True)
address = models.CharField(max_length=100, blank=True, null=True)
is_cdn = models.BooleanField(default=False)
ports = models.ManyToManyField('Port', related_name='ports')
geo_iso = models.ForeignKey(
CountryISO, on_delete=models.CASCADE, null=True, blank=True)
version = models.IntegerField(blank=True, null=True)
is_private = models.BooleanField(default=False)
reverse_pointer = models.CharField(max_length=100, blank=True, null=True)
# this is used for querying which ip was discovered during subcan
ip_subscan_ids = models.ManyToManyField('SubScan', related_name='ip_subscan_ids')
def __str__(self):
return str(self.address)
class Port(models.Model):
id = models.AutoField(primary_key=True)
number = models.IntegerField(default=0)
service_name = models.CharField(max_length=100, blank=True, null=True)
description = models.CharField(max_length=1000, blank=True, null=True)
is_uncommon = models.BooleanField(default=False)
def __str__(self):
return str(self.service_name)
class DirectoryFile(models.Model):
id = models.AutoField(primary_key=True)
length = models.IntegerField(default=0)
lines = models.IntegerField(default=0)
http_status = models.IntegerField(default=0)
words = models.IntegerField(default=0)
name = models.CharField(max_length=500, blank=True, null=True)
url = models.CharField(max_length=5000, blank=True, null=True)
content_type = models.CharField(max_length=100, blank=True, null=True)
def __str__(self):
return str(self.name)
class DirectoryScan(models.Model):
id = models.AutoField(primary_key=True)
command_line = models.CharField(max_length=5000, blank=True, null=True)
directory_files = models.ManyToManyField('DirectoryFile', related_name='directory_files', blank=True)
scanned_date = models.DateTimeField(null=True)
# this is used for querying which ip was discovered during subcan
dir_subscan_ids = models.ManyToManyField('SubScan', related_name='dir_subscan_ids', blank=True)
class MetaFinderDocument(models.Model):
id = models.AutoField(primary_key=True)
scan_history = models.ForeignKey(ScanHistory, on_delete=models.CASCADE, null=True, blank=True)
target_domain = models.ForeignKey(
Domain, on_delete=models.CASCADE, null=True, blank=True)
subdomain = models.ForeignKey(
Subdomain,
on_delete=models.CASCADE,
null=True,
blank=True)
doc_name = models.CharField(max_length=1000, null=True, blank=True)
url = models.CharField(max_length=10000, null=True, blank=True)
title = models.CharField(max_length=1000, null=True, blank=True)
author = models.CharField(max_length=1000, null=True, blank=True)
producer = models.CharField(max_length=1000, null=True, blank=True)
creator = models.CharField(max_length=1000, null=True, blank=True)
os = models.CharField(max_length=1000, null=True, blank=True)
http_status = models.IntegerField(default=0, null=True, blank=True)
creation_date = models.CharField(max_length=1000, blank=True, null=True)
modified_date = models.CharField(max_length=1000, blank=True, null=True)
class Email(models.Model):
id = models.AutoField(primary_key=True)
address = models.CharField(max_length=200, blank=True, null=True)
password = models.CharField(max_length=200, blank=True, null=True)
class Employee(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=1000, null=True, blank=True)
designation = models.CharField(max_length=1000, null=True, blank=True)
class Dork(models.Model):
id = models.AutoField(primary_key=True)
type = models.CharField(max_length=500, null=True, blank=True)
url = models.CharField(max_length=10000, null=True, blank=True)
class S3Bucket(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=500, null=True, blank=True)
region = models.CharField(max_length=500, null=True, blank=True)
provider = models.CharField(max_length=100, null=True, blank=True)
owner_id = models.CharField(max_length=250, null=True, blank=True)
owner_display_name = models.CharField(max_length=250, null=True, blank=True)
perm_auth_users_read = models.IntegerField(default=0)
perm_auth_users_write = models.IntegerField(default=0)
perm_auth_users_read_acl = models.IntegerField(default=0)
perm_auth_users_write_acl = models.IntegerField(default=0)
perm_auth_users_full_control = models.IntegerField(default=0)
perm_all_users_read = models.IntegerField(default=0)
perm_all_users_write = models.IntegerField(default=0)
perm_all_users_read_acl = models.IntegerField(default=0)
perm_all_users_write_acl = models.IntegerField(default=0)
perm_all_users_full_control = models.IntegerField(default=0)
num_objects = models.IntegerField(default=0)
size = models.IntegerField(default=0)