Files
rengine/web/startScan/models.py
T

658 lines
22 KiB
Python

from urllib.parse import urlparse
from django.apps import apps
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)
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)