mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-08-24 06:42:24 +02:00
3156 lines
96 KiB
Python
3156 lines
96 KiB
Python
import re
|
|
import socket
|
|
import logging
|
|
import requests
|
|
import validators
|
|
import requests
|
|
|
|
from ipaddress import IPv4Network
|
|
from django.db.models import CharField, Count, F, Q, Value
|
|
from django.utils import timezone
|
|
from packaging import version
|
|
from django.template.defaultfilters import slugify
|
|
from datetime import datetime
|
|
from rest_framework import viewsets, status
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_204_NO_CONTENT, HTTP_202_ACCEPTED
|
|
from rest_framework.decorators import action
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.core.cache import cache
|
|
|
|
|
|
from dashboard.models import *
|
|
from recon_note.models import *
|
|
from reNgine.celery import app
|
|
from reNgine.common_func import *
|
|
from reNgine.database_utils import *
|
|
from reNgine.definitions import ABORTED_TASK
|
|
from reNgine.tasks import *
|
|
from reNgine.llm import *
|
|
from reNgine.utilities import is_safe_path
|
|
from scanEngine.models import *
|
|
from startScan.models import *
|
|
from startScan.models import EndPoint
|
|
from targetApp.models import *
|
|
from api.shared_api_tasks import import_hackerone_programs_task, sync_bookmarked_programs_task
|
|
from .serializers import *
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ToggleBugBountyModeView(APIView):
|
|
"""
|
|
This class manages the user bug bounty mode
|
|
"""
|
|
def post(self, request, *args, **kwargs):
|
|
user_preferences = get_object_or_404(UserPreferences, user=request.user)
|
|
user_preferences.bug_bounty_mode = not user_preferences.bug_bounty_mode
|
|
user_preferences.save()
|
|
return Response({
|
|
'bug_bounty_mode': user_preferences.bug_bounty_mode
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
|
class HackerOneProgramViewSet(viewsets.ViewSet):
|
|
"""
|
|
This class manages the HackerOne Program model,
|
|
provides basic fetching of programs and caching
|
|
"""
|
|
CACHE_KEY = 'hackerone_programs'
|
|
CACHE_TIMEOUT = 60 * 30 # 30 minutes
|
|
PROGRAM_CACHE_KEY = 'hackerone_program_{}'
|
|
|
|
API_BASE = 'https://api.hackerone.com/v1/hackers'
|
|
|
|
ALLOWED_ASSET_TYPES = ["WILDCARD", "DOMAIN", "IP_ADDRESS", "CIDR", "URL"]
|
|
|
|
def list(self, request):
|
|
try:
|
|
sort_by = request.query_params.get('sort_by', 'age')
|
|
sort_order = request.query_params.get('sort_order', 'desc')
|
|
|
|
programs = self.get_cached_programs()
|
|
|
|
if sort_by == 'name':
|
|
programs = sorted(programs, key=lambda x: x['attributes']['name'].lower(),
|
|
reverse=(sort_order.lower() == 'desc'))
|
|
elif sort_by == 'reports':
|
|
programs = sorted(programs, key=lambda x: x['attributes'].get('number_of_reports_for_user', 0),
|
|
reverse=(sort_order.lower() == 'desc'))
|
|
elif sort_by == 'age':
|
|
programs = sorted(programs,
|
|
key=lambda x: datetime.strptime(x['attributes'].get('started_accepting_at', '1970-01-01T00:00:00.000Z'), '%Y-%m-%dT%H:%M:%S.%fZ'),
|
|
reverse=(sort_order.lower() == 'desc')
|
|
)
|
|
|
|
serializer = HackerOneProgramSerializer(programs, many=True)
|
|
return Response(serializer.data)
|
|
except Exception as e:
|
|
return self.handle_exception(e)
|
|
|
|
def get_api_credentials(self):
|
|
try:
|
|
api_key = HackerOneAPIKey.objects.first()
|
|
if not api_key:
|
|
raise ObjectDoesNotExist("HackerOne API credentials not found")
|
|
return api_key.username, api_key.key
|
|
except ObjectDoesNotExist:
|
|
raise Exception("HackerOne API credentials not configured")
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def bookmarked_programs(self, request):
|
|
try:
|
|
# do not cache bookmarked programs due to the user specific nature
|
|
programs = self.fetch_programs_from_hackerone()
|
|
bookmarked = [p for p in programs if p['attributes']['bookmarked']]
|
|
serializer = HackerOneProgramSerializer(bookmarked, many=True)
|
|
return Response(serializer.data)
|
|
except Exception as e:
|
|
return self.handle_exception(e)
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def bounty_programs(self, request):
|
|
try:
|
|
programs = self.get_cached_programs()
|
|
bounty_programs = [p for p in programs if p['attributes']['offers_bounties']]
|
|
serializer = HackerOneProgramSerializer(bounty_programs, many=True)
|
|
return Response(serializer.data)
|
|
except Exception as e:
|
|
return self.handle_exception(e)
|
|
|
|
def get_cached_programs(self):
|
|
programs = cache.get(self.CACHE_KEY)
|
|
if programs is None:
|
|
programs = self.fetch_programs_from_hackerone()
|
|
cache.set(self.CACHE_KEY, programs, self.CACHE_TIMEOUT)
|
|
return programs
|
|
|
|
def fetch_programs_from_hackerone(self):
|
|
url = f'{self.API_BASE}/programs?page[size]=100'
|
|
headers = {'Accept': 'application/json'}
|
|
all_programs = []
|
|
try:
|
|
username, api_key = self.get_api_credentials()
|
|
except Exception as e:
|
|
raise Exception("API credentials error: " + str(e))
|
|
|
|
while url:
|
|
response = requests.get(
|
|
url,
|
|
headers=headers,
|
|
auth=(username, api_key)
|
|
)
|
|
|
|
if response.status_code == 401:
|
|
raise Exception("Invalid API credentials")
|
|
elif response.status_code != 200:
|
|
raise Exception(f"HackerOne API request failed with status code {response.status_code}")
|
|
|
|
data = response.json()
|
|
all_programs.extend(data['data'])
|
|
|
|
url = data['links'].get('next')
|
|
|
|
return all_programs
|
|
|
|
@action(detail=False, methods=['post'])
|
|
def refresh_cache(self, request):
|
|
try:
|
|
programs = self.fetch_programs_from_hackerone()
|
|
cache.set(self.CACHE_KEY, programs, self.CACHE_TIMEOUT)
|
|
return Response({"status": "Cache refreshed successfully"})
|
|
except Exception as e:
|
|
return self.handle_exception(e)
|
|
|
|
@action(detail=True, methods=['get'])
|
|
def program_details(self, request, pk=None):
|
|
try:
|
|
program_handle = pk
|
|
cache_key = self.PROGRAM_CACHE_KEY.format(program_handle)
|
|
program_details = cache.get(cache_key)
|
|
|
|
if program_details is None:
|
|
program_details = self.fetch_program_details_from_hackerone(program_handle)
|
|
if program_details:
|
|
cache.set(cache_key, program_details, self.CACHE_TIMEOUT)
|
|
|
|
if program_details:
|
|
filtered_scopes = [
|
|
scope for scope in program_details.get('relationships', {}).get('structured_scopes', {}).get('data', [])
|
|
if scope.get('attributes', {}).get('asset_type') in self.ALLOWED_ASSET_TYPES
|
|
]
|
|
|
|
program_details['relationships']['structured_scopes']['data'] = filtered_scopes
|
|
|
|
return Response(program_details)
|
|
else:
|
|
return Response({"error": "Program not found"}, status=status.HTTP_404_NOT_FOUND)
|
|
except Exception as e:
|
|
return self.handle_exception(e)
|
|
|
|
def fetch_program_details_from_hackerone(self, program_handle):
|
|
url = f'{self.API_BASE}/programs/{program_handle}'
|
|
headers = {'Accept': 'application/json'}
|
|
try:
|
|
username, api_key = self.get_api_credentials()
|
|
except Exception as e:
|
|
raise Exception("API credentials error: " + str(e))
|
|
|
|
response = requests.get(
|
|
url,
|
|
headers=headers,
|
|
auth=(username, api_key)
|
|
)
|
|
|
|
if response.status_code == 401:
|
|
raise Exception("Invalid API credentials")
|
|
elif response.status_code == 200:
|
|
return response.json()
|
|
else:
|
|
return None
|
|
|
|
@action(detail=False, methods=['post'])
|
|
def import_programs(self, request):
|
|
try:
|
|
project_slug = request.query_params.get('project_slug')
|
|
if not project_slug:
|
|
return Response({"error": "Project slug is required"}, status=status.HTTP_400_BAD_REQUEST)
|
|
handles = request.data.get('handles', [])
|
|
|
|
if not handles:
|
|
return Response({"error": "No program handles provided"}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
import_hackerone_programs_task.delay(handles, project_slug)
|
|
|
|
create_inappnotification(
|
|
title="HackerOne Program Import Started",
|
|
description=f"Import process for {len(handles)} program(s) has begun.",
|
|
notification_type=PROJECT_LEVEL_NOTIFICATION,
|
|
project_slug=project_slug,
|
|
icon="mdi-download",
|
|
status='info'
|
|
)
|
|
|
|
return Response({"message": f"Import process for {len(handles)} program(s) has begun."}, status=status.HTTP_202_ACCEPTED)
|
|
except Exception as e:
|
|
return self.handle_exception(e)
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def sync_bookmarked(self, request):
|
|
try:
|
|
project_slug = request.query_params.get('project_slug')
|
|
if not project_slug:
|
|
return Response({"error": "Project slug is required"}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
sync_bookmarked_programs_task.delay(project_slug)
|
|
|
|
create_inappnotification(
|
|
title="HackerOne Bookmarked Programs Sync Started",
|
|
description="Sync process for bookmarked programs has begun.",
|
|
notification_type=PROJECT_LEVEL_NOTIFICATION,
|
|
project_slug=project_slug,
|
|
icon="mdi-sync",
|
|
status='info'
|
|
)
|
|
|
|
return Response({"message": "Sync process for bookmarked programs has begun."}, status=status.HTTP_202_ACCEPTED)
|
|
except Exception as e:
|
|
return self.handle_exception(e)
|
|
|
|
def handle_exception(self, exc):
|
|
if isinstance(exc, ObjectDoesNotExist):
|
|
return Response({"error": "HackerOne API credentials not configured"}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
|
elif str(exc) == "Invalid API credentials":
|
|
return Response({"error": "Invalid HackerOne API credentials"}, status=status.HTTP_401_UNAUTHORIZED)
|
|
else:
|
|
return Response({"error": str(exc)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
class InAppNotificationManagerViewSet(viewsets.ModelViewSet):
|
|
"""
|
|
This class manages the notification model, provided CRUD operation on notif model
|
|
such as read notif, clear all, fetch all notifications etc
|
|
"""
|
|
serializer_class = InAppNotificationSerializer
|
|
pagination_class = None
|
|
|
|
def get_queryset(self):
|
|
# we will see later if user based notif is needed
|
|
# return InAppNotification.objects.filter(user=self.request.user)
|
|
project_slug = self.request.query_params.get('project_slug')
|
|
queryset = InAppNotification.objects.all()
|
|
if project_slug:
|
|
queryset = queryset.filter(
|
|
Q(project__slug=project_slug) | Q(notification_type='system')
|
|
)
|
|
return queryset.order_by('-created_at')
|
|
|
|
@action(detail=False, methods=['post'])
|
|
def mark_all_read(self, request):
|
|
# marks all notification read
|
|
project_slug = self.request.query_params.get('project_slug')
|
|
queryset = self.get_queryset()
|
|
|
|
if project_slug:
|
|
queryset = queryset.filter(
|
|
Q(project__slug=project_slug) | Q(notification_type='system')
|
|
)
|
|
queryset.update(is_read=True)
|
|
return Response(status=HTTP_204_NO_CONTENT)
|
|
|
|
@action(detail=True, methods=['post'])
|
|
def mark_read(self, request, pk=None):
|
|
# mark individual notification read when cliked
|
|
notification = self.get_object()
|
|
notification.is_read = True
|
|
notification.save()
|
|
return Response(status=HTTP_204_NO_CONTENT)
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def unread_count(self, request):
|
|
# this fetches the count for unread notif mainly for the badge
|
|
project_slug = self.request.query_params.get('project_slug')
|
|
queryset = self.get_queryset()
|
|
if project_slug:
|
|
queryset = queryset.filter(
|
|
Q(project__slug=project_slug) | Q(notification_type='system')
|
|
)
|
|
count = queryset.filter(is_read=False).count()
|
|
return Response({'count': count})
|
|
|
|
@action(detail=False, methods=['post'])
|
|
def clear_all(self, request):
|
|
# when clicked on the clear button this must be called to clear all notif
|
|
project_slug = self.request.query_params.get('project_slug')
|
|
queryset = self.get_queryset()
|
|
if project_slug:
|
|
queryset = queryset.filter(
|
|
Q(project__slug=project_slug) | Q(notification_type='system')
|
|
)
|
|
queryset.delete()
|
|
return Response(status=HTTP_204_NO_CONTENT)
|
|
|
|
|
|
class OllamaManager(APIView):
|
|
def get(self, request):
|
|
"""
|
|
API to download Ollama Models
|
|
sends a POST request to download the model
|
|
"""
|
|
req = self.request
|
|
model_name = req.query_params.get('model')
|
|
response = {
|
|
'status': False
|
|
}
|
|
try:
|
|
pull_model_api = f'{OLLAMA_INSTANCE}/api/pull'
|
|
_response = requests.post(
|
|
pull_model_api,
|
|
json={
|
|
'name': model_name,
|
|
'stream': False
|
|
}
|
|
).json()
|
|
if _response.get('error'):
|
|
response['status'] = False
|
|
response['error'] = _response.get('error')
|
|
else:
|
|
response['status'] = True
|
|
except Exception as e:
|
|
response['error'] = str(e)
|
|
return Response(response)
|
|
|
|
def delete(self, request):
|
|
req = self.request
|
|
model_name = req.query_params.get('model')
|
|
delete_model_api = f'{OLLAMA_INSTANCE}/api/delete'
|
|
response = {
|
|
'status': False
|
|
}
|
|
try:
|
|
_response = requests.delete(
|
|
delete_model_api,
|
|
json={
|
|
'name': model_name
|
|
}
|
|
).json()
|
|
if _response.get('error'):
|
|
response['status'] = False
|
|
response['error'] = _response.get('error')
|
|
else:
|
|
response['status'] = True
|
|
except Exception as e:
|
|
response['error'] = str(e)
|
|
return Response(response)
|
|
|
|
def put(self, request):
|
|
req = self.request
|
|
model_name = req.query_params.get('model')
|
|
# check if model_name is in DEFAULT_GPT_MODELS
|
|
response = {
|
|
'status': False
|
|
}
|
|
use_ollama = True
|
|
if any(model['name'] == model_name for model in DEFAULT_GPT_MODELS):
|
|
use_ollama = False
|
|
try:
|
|
OllamaSettings.objects.update_or_create(
|
|
defaults={
|
|
'selected_model': model_name,
|
|
'use_ollama': use_ollama
|
|
},
|
|
id=1
|
|
)
|
|
response['status'] = True
|
|
except Exception as e:
|
|
response['error'] = str(e)
|
|
return Response(response)
|
|
|
|
|
|
class GPTAttackSuggestion(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
subdomain_id = req.query_params.get('subdomain_id')
|
|
if not subdomain_id:
|
|
return Response({
|
|
'status': False,
|
|
'error': 'Missing GET param Subdomain `subdomain_id`'
|
|
})
|
|
try:
|
|
subdomain = Subdomain.objects.get(id=subdomain_id)
|
|
except Exception as e:
|
|
return Response({
|
|
'status': False,
|
|
'error': 'Subdomain not found with id ' + subdomain_id
|
|
})
|
|
if subdomain.attack_surface:
|
|
return Response({
|
|
'status': True,
|
|
'subdomain_name': subdomain.name,
|
|
'description': subdomain.attack_surface
|
|
})
|
|
ip_addrs = subdomain.ip_addresses.all()
|
|
open_ports_str = ''
|
|
for ip in ip_addrs:
|
|
ports = ip.ports.all()
|
|
for port in ports:
|
|
open_ports_str += f'{port.number}/{port.service_name}, '
|
|
tech_used = ''
|
|
for tech in subdomain.technologies.all():
|
|
tech_used += f'{tech.name}, '
|
|
llm_input = f'''
|
|
Subdomain Name: {subdomain.name}
|
|
Subdomain Page Title: {subdomain.page_title}
|
|
Open Ports: {open_ports_str}
|
|
HTTP Status: {subdomain.http_status}
|
|
Technologies Used: {tech_used}
|
|
Content type: {subdomain.content_type}
|
|
Web Server: {subdomain.webserver}
|
|
Page Content Length: {subdomain.content_length}
|
|
'''
|
|
llm_input = re.sub(r'\t', '', llm_input)
|
|
gpt = LLMAttackSuggestionGenerator(logger)
|
|
response = gpt.get_attack_suggestion(llm_input)
|
|
response['subdomain_name'] = subdomain.name
|
|
if response.get('status'):
|
|
subdomain.attack_surface = response.get('description')
|
|
subdomain.save()
|
|
return Response(response)
|
|
|
|
|
|
class LLMVulnerabilityReportGenerator(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
vulnerability_id = req.query_params.get('id')
|
|
if not vulnerability_id:
|
|
return Response({
|
|
'status': False,
|
|
'error': 'Missing GET param Vulnerability `id`'
|
|
})
|
|
task = llm_vulnerability_description.apply_async(args=(vulnerability_id,))
|
|
response = task.wait()
|
|
return Response(response)
|
|
|
|
|
|
class CreateProjectApi(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
project_name = req.query_params.get('name')
|
|
slug = slugify(project_name)
|
|
insert_date = timezone.now()
|
|
|
|
try:
|
|
project = Project.objects.create(
|
|
name=project_name,
|
|
slug=slug,
|
|
insert_date =insert_date
|
|
)
|
|
response = {
|
|
'status': True,
|
|
'project_name': project_name
|
|
}
|
|
return Response(response)
|
|
except Exception as e:
|
|
response = {
|
|
'status': False,
|
|
'error': str(e)
|
|
}
|
|
return Response(response, status=HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
class QueryInterestingSubdomains(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
domain_id = req.query_params.get('target_id')
|
|
|
|
if scan_id:
|
|
queryset = get_interesting_subdomains(scan_history=scan_id)
|
|
elif domain_id:
|
|
queryset = get_interesting_subdomains(domain_id=domain_id)
|
|
else:
|
|
queryset = get_interesting_subdomains()
|
|
|
|
queryset = queryset.distinct('name')
|
|
|
|
return Response(InterestingSubdomainSerializer(queryset, many=True).data)
|
|
|
|
|
|
class ListTargetsDatatableViewSet(viewsets.ModelViewSet):
|
|
queryset = Domain.objects.all()
|
|
serializer_class = DomainSerializer
|
|
|
|
def get_queryset(self):
|
|
slug = self.request.GET.get('slug', None)
|
|
if slug:
|
|
self.queryset = self.queryset.filter(project__slug=slug)
|
|
return self.queryset
|
|
|
|
def filter_queryset(self, qs):
|
|
qs = self.queryset.filter()
|
|
search_value = self.request.GET.get(u'search[value]', None)
|
|
_order_col = self.request.GET.get(u'order[0][column]', None)
|
|
_order_direction = self.request.GET.get(u'order[0][dir]', None)
|
|
if search_value or _order_col or _order_direction:
|
|
order_col = 'id'
|
|
if _order_col == '2':
|
|
order_col = 'name'
|
|
elif _order_col == '4':
|
|
order_col = 'insert_date'
|
|
elif _order_col == '5':
|
|
order_col = 'start_scan_date'
|
|
if _order_direction == 'desc':
|
|
return qs.order_by(F('start_scan_date').desc(nulls_last=True))
|
|
return qs.order_by(F('start_scan_date').asc(nulls_last=True))
|
|
|
|
|
|
if _order_direction == 'desc':
|
|
order_col = f'-{order_col}'
|
|
|
|
qs = self.queryset.filter(
|
|
Q(name__icontains=search_value) |
|
|
Q(description__icontains=search_value) |
|
|
Q(domains__name__icontains=search_value)
|
|
)
|
|
return qs.order_by(order_col)
|
|
|
|
return qs.order_by('-id')
|
|
|
|
|
|
|
|
class WafDetector(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
url= req.query_params.get('url')
|
|
response = {}
|
|
response['status'] = False
|
|
|
|
# validate url as a first step to avoid command injection
|
|
if not (validators.url(url) or validators.domain(url)):
|
|
response['message'] = 'Invalid Domain/URL provided!'
|
|
return Response(response)
|
|
|
|
wafw00f_command = f'wafw00f {url}'
|
|
_, output = run_command(wafw00f_command, remove_ansi_sequence=True)
|
|
regex = r"behind (.*?) WAF"
|
|
group = re.search(regex, output)
|
|
if group:
|
|
response['status'] = True
|
|
response['results'] = group.group(1)
|
|
else:
|
|
response['message'] = 'Could not detect any WAF!'
|
|
|
|
return Response(response)
|
|
|
|
|
|
class SearchHistoryView(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
|
|
response = {}
|
|
response['status'] = False
|
|
|
|
scan_history = SearchHistory.objects.all().order_by('-id')[:5]
|
|
|
|
if scan_history:
|
|
response['status'] = True
|
|
response['results'] = SearchHistorySerializer(scan_history, many=True).data
|
|
|
|
return Response(response)
|
|
|
|
|
|
class UniversalSearch(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
query = req.query_params.get('query')
|
|
|
|
response = {}
|
|
response['status'] = False
|
|
|
|
if not query:
|
|
response['message'] = 'No query parameter provided!'
|
|
return Response(response)
|
|
|
|
response['results'] = {}
|
|
|
|
# search history to be saved
|
|
SearchHistory.objects.get_or_create(
|
|
query=query
|
|
)
|
|
|
|
# lookup query in subdomain
|
|
subdomain = Subdomain.objects.filter(
|
|
Q(name__icontains=query) |
|
|
Q(cname__icontains=query) |
|
|
Q(page_title__icontains=query) |
|
|
Q(http_url__icontains=query)
|
|
).distinct('name')
|
|
subdomain_data = SubdomainSerializer(subdomain, many=True).data
|
|
response['results']['subdomains'] = subdomain_data
|
|
|
|
endpoint = EndPoint.objects.filter(
|
|
Q(http_url__icontains=query) |
|
|
Q(page_title__icontains=query)
|
|
).distinct('http_url')
|
|
endpoint_data = EndpointSerializer(endpoint, many=True).data
|
|
response['results']['endpoints'] = endpoint_data
|
|
|
|
vulnerability = Vulnerability.objects.filter(
|
|
Q(http_url__icontains=query) |
|
|
Q(name__icontains=query) |
|
|
Q(description__icontains=query)
|
|
).distinct()
|
|
vulnerability_data = VulnerabilitySerializer(vulnerability, many=True).data
|
|
response['results']['vulnerabilities'] = vulnerability_data
|
|
|
|
response['results']['others'] = {}
|
|
|
|
if subdomain_data or endpoint_data or vulnerability_data:
|
|
response['status'] = True
|
|
|
|
return Response(response)
|
|
|
|
|
|
class FetchMostCommonVulnerability(APIView):
|
|
def post(self, request):
|
|
req = self.request
|
|
data = req.data
|
|
|
|
try:
|
|
limit = data.get('limit', 20)
|
|
project_slug = data.get('slug')
|
|
scan_history_id = data.get('scan_history_id')
|
|
target_id = data.get('target_id')
|
|
is_ignore_info = data.get('ignore_info', False)
|
|
|
|
response = {}
|
|
response['status'] = False
|
|
|
|
if project_slug:
|
|
project = Project.objects.get(slug=project_slug)
|
|
vulnerabilities = Vulnerability.objects.filter(target_domain__project=project)
|
|
else:
|
|
vulnerabilities = Vulnerability.objects.all()
|
|
|
|
|
|
if scan_history_id:
|
|
vuln_query = (
|
|
vulnerabilities
|
|
.filter(scan_history__id=scan_history_id)
|
|
.values("name", "severity")
|
|
)
|
|
if is_ignore_info:
|
|
most_common_vulnerabilities = (
|
|
vuln_query
|
|
.exclude(severity=0)
|
|
.annotate(count=Count('name'))
|
|
.order_by("-count")[:limit]
|
|
)
|
|
else:
|
|
most_common_vulnerabilities = (
|
|
vuln_query
|
|
.annotate(count=Count('name'))
|
|
.order_by("-count")[:limit]
|
|
)
|
|
|
|
elif target_id:
|
|
vuln_query = vulnerabilities.filter(target_domain__id=target_id).values("name", "severity")
|
|
if is_ignore_info:
|
|
most_common_vulnerabilities = (
|
|
vuln_query
|
|
.exclude(severity=0)
|
|
.annotate(count=Count('name'))
|
|
.order_by("-count")[:limit]
|
|
)
|
|
else:
|
|
most_common_vulnerabilities = (
|
|
vuln_query
|
|
.annotate(count=Count('name'))
|
|
.order_by("-count")[:limit]
|
|
)
|
|
|
|
else:
|
|
vuln_query = vulnerabilities.values("name", "severity")
|
|
if is_ignore_info:
|
|
most_common_vulnerabilities = (
|
|
vuln_query.exclude(severity=0)
|
|
.annotate(count=Count('name'))
|
|
.order_by("-count")[:limit]
|
|
)
|
|
else:
|
|
most_common_vulnerabilities = (
|
|
vuln_query.annotate(count=Count('name'))
|
|
.order_by("-count")[:limit]
|
|
)
|
|
|
|
|
|
most_common_vulnerabilities = [vuln for vuln in most_common_vulnerabilities]
|
|
|
|
if most_common_vulnerabilities:
|
|
response['status'] = True
|
|
response['result'] = most_common_vulnerabilities
|
|
except Exception as e:
|
|
print(str(e))
|
|
response = {}
|
|
|
|
return Response(response)
|
|
|
|
|
|
class FetchMostVulnerable(APIView):
|
|
def post(self, request):
|
|
req = self.request
|
|
data = req.data
|
|
|
|
project_slug = data.get('slug')
|
|
scan_history_id = data.get('scan_history_id')
|
|
target_id = data.get('target_id')
|
|
limit = data.get('limit', 20)
|
|
is_ignore_info = data.get('ignore_info', False)
|
|
|
|
response = {}
|
|
response['status'] = False
|
|
|
|
if project_slug:
|
|
project = Project.objects.get(slug=project_slug)
|
|
subdomains = Subdomain.objects.filter(target_domain__project=project)
|
|
domains = Domain.objects.filter(project=project)
|
|
else:
|
|
subdomains = Subdomain.objects.all()
|
|
domains = Domain.objects.all()
|
|
|
|
if scan_history_id:
|
|
subdomain_query = subdomains.filter(scan_history__id=scan_history_id)
|
|
if is_ignore_info:
|
|
most_vulnerable_subdomains = (
|
|
subdomain_query
|
|
.annotate(
|
|
vuln_count=Count('vulnerability__name', filter=~Q(vulnerability__severity=0))
|
|
)
|
|
.order_by('-vuln_count')
|
|
.exclude(vuln_count=0)[:limit]
|
|
)
|
|
else:
|
|
most_vulnerable_subdomains = (
|
|
subdomain_query
|
|
.annotate(vuln_count=Count('vulnerability__name'))
|
|
.order_by('-vuln_count')
|
|
.exclude(vuln_count=0)[:limit]
|
|
)
|
|
|
|
if most_vulnerable_subdomains:
|
|
response['status'] = True
|
|
response['result'] = (
|
|
SubdomainSerializer(
|
|
most_vulnerable_subdomains,
|
|
many=True)
|
|
.data
|
|
)
|
|
|
|
elif target_id:
|
|
subdomain_query = subdomains.filter(target_domain__id=target_id)
|
|
if is_ignore_info:
|
|
most_vulnerable_subdomains = (
|
|
subdomain_query
|
|
.annotate(vuln_count=Count('vulnerability__name', filter=~Q(vulnerability__severity=0)))
|
|
.order_by('-vuln_count')
|
|
.exclude(vuln_count=0)[:limit]
|
|
)
|
|
else:
|
|
most_vulnerable_subdomains = (
|
|
subdomain_query
|
|
.annotate(vuln_count=Count('vulnerability__name'))
|
|
.order_by('-vuln_count')
|
|
.exclude(vuln_count=0)[:limit]
|
|
)
|
|
|
|
if most_vulnerable_subdomains:
|
|
response['status'] = True
|
|
response['result'] = (
|
|
SubdomainSerializer(
|
|
most_vulnerable_subdomains,
|
|
many=True)
|
|
.data
|
|
)
|
|
else:
|
|
if is_ignore_info:
|
|
most_vulnerable_targets = (
|
|
domains
|
|
.annotate(vuln_count=Count('subdomain__vulnerability__name', filter=~Q(subdomain__vulnerability__severity=0)))
|
|
.order_by('-vuln_count')
|
|
.exclude(vuln_count=0)[:limit]
|
|
)
|
|
else:
|
|
most_vulnerable_targets = (
|
|
domains
|
|
.annotate(vuln_count=Count('subdomain__vulnerability__name'))
|
|
.order_by('-vuln_count')
|
|
.exclude(vuln_count=0)[:limit]
|
|
)
|
|
|
|
if most_vulnerable_targets:
|
|
response['status'] = True
|
|
response['result'] = (
|
|
DomainSerializer(
|
|
most_vulnerable_targets,
|
|
many=True)
|
|
.data
|
|
)
|
|
|
|
return Response(response)
|
|
|
|
|
|
class CVEDetails(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
|
|
cve_id = req.query_params.get('cve_id')
|
|
|
|
if not cve_id:
|
|
return Response({'status': False, 'message': 'CVE ID not provided'})
|
|
|
|
response = requests.get('https://cve.circl.lu/api/cve/' + cve_id)
|
|
|
|
if response.status_code != 200:
|
|
return Response({'status': False, 'message': 'Unknown Error Occured!'})
|
|
|
|
if not response.json():
|
|
return Response({'status': False, 'message': 'CVE ID does not exists.'})
|
|
|
|
return Response({'status': True, 'result': response.json()})
|
|
|
|
|
|
class AddReconNote(APIView):
|
|
def post(self, request):
|
|
req = self.request
|
|
data = req.data
|
|
|
|
subdomain_id = data.get('subdomain_id')
|
|
title = data.get('title')
|
|
description = data.get('description')
|
|
project = data.get('project')
|
|
|
|
try:
|
|
project = Project.objects.get(slug=project)
|
|
note = TodoNote()
|
|
note.title = title
|
|
note.description = description
|
|
|
|
# get scan history for subdomain_id
|
|
if subdomain_id:
|
|
subdomain = Subdomain.objects.get(id=subdomain_id)
|
|
note.subdomain = subdomain
|
|
|
|
# also get scan history
|
|
scan_history_id = subdomain.scan_history.id
|
|
scan_history = ScanHistory.objects.get(id=scan_history_id)
|
|
note.scan_history = scan_history
|
|
|
|
note.project = project
|
|
note.save()
|
|
response = {'status': True}
|
|
except Exception as e:
|
|
response = {'status': False, 'message': str(e)}
|
|
|
|
return Response(response)
|
|
|
|
|
|
class ToggleSubdomainImportantStatus(APIView):
|
|
def post(self, request):
|
|
req = self.request
|
|
data = req.data
|
|
|
|
subdomain_id = data.get('subdomain_id')
|
|
|
|
response = {'status': False, 'message': 'No subdomain_id provided'}
|
|
|
|
name = Subdomain.objects.get(id=subdomain_id)
|
|
name.is_important = not name.is_important
|
|
name.save()
|
|
|
|
response = {'status': True}
|
|
|
|
return Response(response)
|
|
|
|
|
|
class AddTarget(APIView):
|
|
def post(self, request):
|
|
req = self.request
|
|
data = req.data
|
|
h1_team_handle = data.get('h1_team_handle')
|
|
description = data.get('description')
|
|
domain_name = data.get('domain_name')
|
|
# remove wild card from domain
|
|
domain_name = domain_name.replace('*', '')
|
|
# if domain_name begins with . remove that
|
|
if domain_name.startswith('.'):
|
|
domain_name = domain_name[1:]
|
|
organization_name = data.get('organization')
|
|
slug = data.get('slug')
|
|
|
|
# Validate domain name
|
|
if not validators.domain(domain_name):
|
|
return Response({'status': False, 'message': 'Invalid domain or IP'})
|
|
|
|
status = bulk_import_targets(
|
|
targets=[{
|
|
'name': domain_name,
|
|
'description': description,
|
|
}],
|
|
organization_name=organization_name,
|
|
h1_team_handle=h1_team_handle,
|
|
project_slug=slug
|
|
)
|
|
|
|
if status:
|
|
return Response({
|
|
'status': True,
|
|
'message': 'Domain successfully added as target !',
|
|
'domain_name': domain_name,
|
|
# 'domain_id': domain.id
|
|
})
|
|
return Response({
|
|
'status': False,
|
|
'message': 'Failed to add as target !'
|
|
})
|
|
|
|
|
|
class FetchSubscanResults(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
# data = req.data
|
|
subscan_id = req.query_params.get('subscan_id')
|
|
subscan = SubScan.objects.filter(id=subscan_id)
|
|
if not subscan.exists():
|
|
return Response({
|
|
'status': False,
|
|
'error': f'Subscan {subscan_id} does not exist'
|
|
})
|
|
|
|
subscan_data = SubScanResultSerializer(subscan.first(), many=False).data
|
|
task_name = subscan_data['type']
|
|
subscan_results = []
|
|
|
|
if task_name == 'port_scan':
|
|
ips_in_subscan = IpAddress.objects.filter(ip_subscan_ids__in=subscan)
|
|
subscan_results = IpSerializer(ips_in_subscan, many=True).data
|
|
|
|
elif task_name == 'vulnerability_scan':
|
|
vulns_in_subscan = Vulnerability.objects.filter(vuln_subscan_ids__in=subscan)
|
|
subscan_results = VulnerabilitySerializer(vulns_in_subscan, many=True).data
|
|
|
|
elif task_name == 'fetch_url':
|
|
endpoints_in_subscan = EndPoint.objects.filter(endpoint_subscan_ids__in=subscan)
|
|
subscan_results = EndpointSerializer(endpoints_in_subscan, many=True).data
|
|
|
|
elif task_name == 'dir_file_fuzz':
|
|
dirs_in_subscan = DirectoryScan.objects.filter(dir_subscan_ids__in=subscan)
|
|
subscan_results = DirectoryScanSerializer(dirs_in_subscan, many=True).data
|
|
|
|
elif task_name == 'subdomain_discovery':
|
|
subdomains_in_subscan = Subdomain.objects.filter(subdomain_subscan_ids__in=subscan)
|
|
subscan_results = SubdomainSerializer(subdomains_in_subscan, many=True).data
|
|
|
|
elif task_name == 'screenshot':
|
|
subdomains_in_subscan = Subdomain.objects.filter(subdomain_subscan_ids__in=subscan, screenshot_path__isnull=False)
|
|
subscan_results = SubdomainSerializer(subdomains_in_subscan, many=True).data
|
|
|
|
logger.info(subscan_data)
|
|
logger.info(subscan_results)
|
|
|
|
return Response({'subscan': subscan_data, 'result': subscan_results})
|
|
|
|
|
|
class ListSubScans(APIView):
|
|
def post(self, request):
|
|
req = self.request
|
|
data = req.data
|
|
subdomain_id = data.get('subdomain_id', None)
|
|
scan_history = data.get('scan_history_id', None)
|
|
domain_id = data.get('domain_id', None)
|
|
response = {}
|
|
response['status'] = False
|
|
|
|
if subdomain_id:
|
|
subscans = (
|
|
SubScan.objects
|
|
.filter(subdomain__id=subdomain_id)
|
|
.order_by('-stop_scan_date')
|
|
)
|
|
results = SubScanSerializer(subscans, many=True).data
|
|
if subscans:
|
|
response['status'] = True
|
|
response['results'] = results
|
|
|
|
elif scan_history:
|
|
subscans = (
|
|
SubScan.objects
|
|
.filter(scan_history__id=scan_history)
|
|
.order_by('-stop_scan_date')
|
|
)
|
|
results = SubScanSerializer(subscans, many=True).data
|
|
if subscans:
|
|
response['status'] = True
|
|
response['results'] = results
|
|
|
|
elif domain_id:
|
|
scan_history = ScanHistory.objects.filter(domain__id=domain_id)
|
|
subscans = (
|
|
SubScan.objects
|
|
.filter(scan_history__in=scan_history)
|
|
.order_by('-stop_scan_date')
|
|
)
|
|
results = SubScanSerializer(subscans, many=True).data
|
|
if subscans:
|
|
response['status'] = True
|
|
response['results'] = results
|
|
|
|
return Response(response)
|
|
|
|
|
|
class DeleteMultipleRows(APIView):
|
|
def post(self, request):
|
|
req = self.request
|
|
data = req.data
|
|
|
|
try:
|
|
if data['type'] == 'subscan':
|
|
for row in data['rows']:
|
|
SubScan.objects.get(id=row).delete()
|
|
elif data['type'] == 'organization':
|
|
for row in data['rows']:
|
|
Organization.objects.get(id=row).delete()
|
|
response = True
|
|
except Exception as e:
|
|
response = False
|
|
|
|
return Response({'status': response})
|
|
|
|
|
|
class StopScan(APIView):
|
|
def post(self, request):
|
|
req = self.request
|
|
data = req.data
|
|
scan_ids = data.get('scan_ids', [])
|
|
subscan_ids = data.get('subscan_ids', [])
|
|
|
|
scan_ids = [int(id) for id in scan_ids]
|
|
subscan_ids = [int(id) for id in subscan_ids]
|
|
|
|
response = {'status': False}
|
|
|
|
def abort_scan(scan):
|
|
response = {}
|
|
logger.info(f'Aborting scan History')
|
|
try:
|
|
logger.info(f"Setting scan {scan} status to ABORTED_TASK")
|
|
task_ids = scan.celery_ids
|
|
scan.scan_status = ABORTED_TASK
|
|
scan.stop_scan_date = timezone.now()
|
|
scan.aborted_by = request.user
|
|
scan.save()
|
|
for task_id in task_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:
|
|
task.status = ABORTED_TASK
|
|
task.time = timezone.now()
|
|
task.save()
|
|
|
|
create_scan_activity(
|
|
scan.id,
|
|
"Scan aborted",
|
|
ABORTED_TASK
|
|
)
|
|
response['status'] = True
|
|
except Exception as e:
|
|
logger.error(e)
|
|
response = {'status': False, 'message': str(e)}
|
|
|
|
return response
|
|
|
|
def abort_subscan(subscan):
|
|
response = {}
|
|
logger.info(f'Aborting subscan')
|
|
try:
|
|
logger.info(f"Setting scan {subscan} status to ABORTED_TASK")
|
|
task_ids = subscan.celery_ids
|
|
|
|
for task_id in task_ids:
|
|
app.control.revoke(task_id, terminate=True, signal='SIGKILL')
|
|
|
|
subscan.status = ABORTED_TASK
|
|
subscan.stop_scan_date = timezone.now()
|
|
subscan.save()
|
|
create_scan_activity(
|
|
subscan.scan_history.id,
|
|
f'Subscan aborted',
|
|
ABORTED_TASK
|
|
)
|
|
response['status'] = True
|
|
except Exception as e:
|
|
logger.error(e)
|
|
response = {'status': False, 'message': str(e)}
|
|
|
|
return response
|
|
|
|
for scan_id in scan_ids:
|
|
try:
|
|
scan = ScanHistory.objects.get(id=scan_id)
|
|
# if scan is already successful or aborted then do nothing
|
|
if scan.scan_status == SUCCESS_TASK or scan.scan_status == ABORTED_TASK:
|
|
continue
|
|
response = abort_scan(scan)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
response = {'status': False, 'message': str(e)}
|
|
|
|
for subscan_id in subscan_ids:
|
|
try:
|
|
subscan = SubScan.objects.get(id=subscan_id)
|
|
if subscan.scan_status == SUCCESS_TASK or subscan.scan_status == ABORTED_TASK:
|
|
continue
|
|
response = abort_subscan(subscan)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
response = {'status': False, 'message': str(e)}
|
|
|
|
return Response(response)
|
|
|
|
|
|
class InitiateSubTask(APIView):
|
|
def post(self, request):
|
|
req = self.request
|
|
data = req.data
|
|
engine_id = data.get('engine_id')
|
|
scan_types = data['tasks']
|
|
for subdomain_id in data['subdomain_ids']:
|
|
logger.info(f'Running subscans {scan_types} on subdomain "{subdomain_id}" ...')
|
|
for stype in scan_types:
|
|
ctx = {
|
|
'scan_history_id': None,
|
|
'subdomain_id': subdomain_id,
|
|
'scan_type': stype,
|
|
'engine_id': engine_id
|
|
}
|
|
initiate_subscan.apply_async(kwargs=ctx)
|
|
return Response({'status': True})
|
|
|
|
|
|
class DeleteSubdomain(APIView):
|
|
def post(self, request):
|
|
req = self.request
|
|
for id in req.data['subdomain_ids']:
|
|
Subdomain.objects.get(id=id).delete()
|
|
return Response({'status': True})
|
|
|
|
|
|
class DeleteVulnerability(APIView):
|
|
def post(self, request):
|
|
req = self.request
|
|
for id in req.data['vulnerability_ids']:
|
|
Vulnerability.objects.get(id=id).delete()
|
|
return Response({'status': True})
|
|
|
|
|
|
class ListInterestingKeywords(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
keywords = get_lookup_keywords()
|
|
return Response(keywords)
|
|
|
|
|
|
class RengineUpdateCheck(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
github_api = \
|
|
'https://api.github.com/repos/yogeshojha/rengine/releases'
|
|
response = requests.get(github_api).json()
|
|
if 'message' in response:
|
|
return Response({'status': False, 'message': 'RateLimited'})
|
|
|
|
return_response = {}
|
|
|
|
# get current version_number
|
|
# remove quotes from current_version
|
|
current_version = RENGINE_CURRENT_VERSION
|
|
|
|
# for consistency remove v from both if exists
|
|
latest_version = re.search(r'v(\d+\.)?(\d+\.)?(\*|\d+)',
|
|
((response[0]['name'
|
|
])[1:] if response[0]['name'][0] == 'v'
|
|
else response[0]['name']))
|
|
|
|
latest_version = latest_version.group(0) if latest_version else None
|
|
|
|
if not latest_version:
|
|
latest_version = re.search(r'(\d+\.)?(\d+\.)?(\*|\d+)',
|
|
((response[0]['name'
|
|
])[1:] if response[0]['name'][0]
|
|
== 'v' else response[0]['name']))
|
|
if latest_version:
|
|
latest_version = latest_version.group(0)
|
|
|
|
return_response['status'] = True
|
|
return_response['latest_version'] = latest_version
|
|
return_response['current_version'] = current_version
|
|
is_version_update_available = version.parse(current_version) < version.parse(latest_version)
|
|
|
|
# if is_version_update_available then we should create inapp notification
|
|
create_inappnotification(
|
|
title='reNgine Update Available',
|
|
description=f'Update to version {latest_version} is available',
|
|
notification_type=SYSTEM_LEVEL_NOTIFICATION,
|
|
project_slug=None,
|
|
icon='mdi-update',
|
|
redirect_link='https://github.com/yogeshojha/rengine/releases',
|
|
open_in_new_tab=True
|
|
)
|
|
|
|
return_response['update_available'] = is_version_update_available
|
|
if is_version_update_available:
|
|
return_response['changelog'] = response[0]['body']
|
|
|
|
return Response(return_response)
|
|
|
|
|
|
class UninstallTool(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
tool_id = req.query_params.get('tool_id')
|
|
tool_name = req.query_params.get('name')
|
|
|
|
if tool_id:
|
|
tool = InstalledExternalTool.objects.get(id=tool_id)
|
|
elif tool_name:
|
|
tool = InstalledExternalTool.objects.get(name=tool_name)
|
|
|
|
|
|
if tool.is_default:
|
|
return Response({'status': False, 'message': 'Default tools can not be uninstalled'})
|
|
|
|
# check install instructions, if it is installed using go, then remove from go bin path,
|
|
# else try to remove from github clone path
|
|
|
|
# getting tool name is tricky!
|
|
|
|
if 'go install' in tool.install_command:
|
|
tool_name = tool.install_command.split('/')[-1].split('@')[0]
|
|
uninstall_command = 'rm /go/bin/' + tool_name
|
|
elif 'git clone' in tool.install_command:
|
|
tool_name = tool.install_command[:-1] if tool.install_command[-1] == '/' else tool.install_command
|
|
tool_name = tool_name.split('/')[-1]
|
|
uninstall_command = 'rm -rf ' + tool.github_clone_path
|
|
else:
|
|
return Response({'status': False, 'message': 'Cannot uninstall tool!'})
|
|
|
|
run_command(uninstall_command)
|
|
run_command.apply_async(args=(uninstall_command,))
|
|
|
|
tool.delete()
|
|
|
|
return Response({'status': True, 'message': 'Uninstall Tool Success'})
|
|
|
|
|
|
class UpdateTool(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
tool_id = req.query_params.get('tool_id')
|
|
tool_name = req.query_params.get('name')
|
|
|
|
if tool_id:
|
|
tool = InstalledExternalTool.objects.get(id=tool_id)
|
|
elif tool_name:
|
|
tool = InstalledExternalTool.objects.get(name=tool_name)
|
|
|
|
# if git clone was used for installation, then we must use git pull inside project directory,
|
|
# otherwise use the same command as given
|
|
|
|
update_command = tool.update_command.lower()
|
|
|
|
if not update_command:
|
|
return Response({'status': False, 'message': tool.name + 'has missing update command! Cannot update the tool.'})
|
|
elif update_command == 'git pull':
|
|
tool_name = tool.install_command[:-1] if tool.install_command[-1] == '/' else tool.install_command
|
|
tool_name = tool_name.split('/')[-1]
|
|
update_command = 'cd /usr/src/github/' + tool_name + ' && git pull && cd -'
|
|
|
|
|
|
try:
|
|
run_command(update_command, shell=True)
|
|
run_command.apply_async(args=[update_command], kwargs={'shell': True})
|
|
return Response({'status': True, 'message': tool.name + ' updated successfully.'})
|
|
except Exception as e:
|
|
logger.error(str(e))
|
|
return Response({'status': False, 'message': str(e)})
|
|
|
|
class GetExternalToolCurrentVersion(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
# toolname is also the command
|
|
tool_id = req.query_params.get('tool_id')
|
|
tool_name = req.query_params.get('name')
|
|
# can supply either tool id or tool_name
|
|
|
|
tool = None
|
|
|
|
if tool_id:
|
|
if not InstalledExternalTool.objects.filter(id=tool_id).exists():
|
|
return Response({'status': False, 'message': 'Tool Not found'})
|
|
tool = InstalledExternalTool.objects.get(id=tool_id)
|
|
elif tool_name:
|
|
if not InstalledExternalTool.objects.filter(name=tool_name).exists():
|
|
return Response({'status': False, 'message': 'Tool Not found'})
|
|
tool = InstalledExternalTool.objects.get(name=tool_name)
|
|
|
|
if not tool.version_lookup_command:
|
|
return Response({'status': False, 'message': 'Version Lookup command not provided.'})
|
|
|
|
version_number = None
|
|
_, stdout = run_command(tool.version_lookup_command)
|
|
if tool.version_match_regex:
|
|
version_number = re.search(re.compile(tool.version_match_regex), str(stdout))
|
|
else:
|
|
version_match_regex = r'(?i:v)?(\d+(?:\.\d+){2,})'
|
|
version_number = re.search(version_match_regex, str(stdout))
|
|
if not version_number:
|
|
return Response({'status': False, 'message': 'Invalid version lookup command.'})
|
|
|
|
return Response({'status': True, 'version_number': version_number.group(0), 'tool_name': tool.name})
|
|
|
|
|
|
|
|
class GithubToolCheckGetLatestRelease(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
|
|
tool_id = req.query_params.get('tool_id')
|
|
tool_name = req.query_params.get('name')
|
|
|
|
if not InstalledExternalTool.objects.filter(id=tool_id).exists():
|
|
return Response({'status': False, 'message': 'Tool Not found'})
|
|
|
|
if tool_id:
|
|
tool = InstalledExternalTool.objects.get(id=tool_id)
|
|
elif tool_name:
|
|
tool = InstalledExternalTool.objects.get(name=tool_name)
|
|
|
|
if not tool.github_url:
|
|
return Response({'status': False, 'message': 'Github URL is not provided, Cannot check updates'})
|
|
|
|
# if tool_github_url has https://github.com/ remove and also remove trailing /
|
|
tool_github_url = tool.github_url.replace('http://github.com/', '').replace('https://github.com/', '')
|
|
tool_github_url = remove_lead_and_trail_slash(tool_github_url)
|
|
github_api = f'https://api.github.com/repos/{tool_github_url}/releases'
|
|
response = requests.get(github_api).json()
|
|
# check if api rate limit exceeded
|
|
if 'message' in response and response['message'] == 'RateLimited':
|
|
return Response({'status': False, 'message': 'RateLimited'})
|
|
elif 'message' in response and response['message'] == 'Not Found':
|
|
return Response({'status': False, 'message': 'Not Found'})
|
|
elif not response:
|
|
return Response({'status': False, 'message': 'Not Found'})
|
|
|
|
# only send latest release
|
|
response = response[0]
|
|
|
|
api_response = {
|
|
'status': True,
|
|
'url': response['url'],
|
|
'id': response['id'],
|
|
'name': response['name'],
|
|
'changelog': response['body'],
|
|
}
|
|
return Response(api_response)
|
|
|
|
|
|
class ScanStatus(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
slug = self.request.GET.get('project', None)
|
|
# main tasks
|
|
recently_completed_scans = (
|
|
ScanHistory.objects
|
|
.filter(domain__project__slug=slug)
|
|
.order_by('-start_scan_date')
|
|
.filter(Q(scan_status=0) | Q(scan_status=2) | Q(scan_status=3))[:10]
|
|
)
|
|
current_scans = (
|
|
ScanHistory.objects
|
|
.filter(domain__project__slug=slug)
|
|
.order_by('-start_scan_date')
|
|
.filter(scan_status=1)
|
|
)
|
|
pending_scans = (
|
|
ScanHistory.objects
|
|
.filter(domain__project__slug=slug)
|
|
.filter(scan_status=-1)
|
|
)
|
|
|
|
# subtasks
|
|
recently_completed_tasks = (
|
|
SubScan.objects
|
|
.filter(scan_history__domain__project__slug=slug)
|
|
.order_by('-start_scan_date')
|
|
.filter(Q(status=0) | Q(status=2) | Q(status=3))[:15]
|
|
)
|
|
current_tasks = (
|
|
SubScan.objects
|
|
.filter(scan_history__domain__project__slug=slug)
|
|
.order_by('-start_scan_date')
|
|
.filter(status=1)
|
|
)
|
|
pending_tasks = (
|
|
SubScan.objects
|
|
.filter(scan_history__domain__project__slug=slug)
|
|
.filter(status=-1)
|
|
)
|
|
response = {
|
|
'scans': {
|
|
'pending': ScanHistorySerializer(pending_scans, many=True).data,
|
|
'scanning': ScanHistorySerializer(current_scans, many=True).data,
|
|
'completed': ScanHistorySerializer(recently_completed_scans, many=True).data
|
|
},
|
|
'tasks': {
|
|
'pending': SubScanSerializer(pending_tasks, many=True).data,
|
|
'running': SubScanSerializer(current_tasks, many=True).data,
|
|
'completed': SubScanSerializer(recently_completed_tasks, many=True).data
|
|
}
|
|
}
|
|
return Response(response)
|
|
|
|
|
|
class Whois(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
target = req.query_params.get('target')
|
|
if not target:
|
|
return Response({'status': False, 'message': 'Target IP/Domain required!'})
|
|
if not (validators.domain(target) or validators.ipv4(target) or validators.ipv6(target)):
|
|
print(f'Ip address or domain "{target}" did not pass validator.')
|
|
return Response({'status': False, 'message': 'Invalid domain or IP'})
|
|
is_force_update = req.query_params.get('is_reload')
|
|
is_force_update = True if is_force_update and 'true' == is_force_update.lower() else False
|
|
task = query_whois.apply_async(args=(target,is_force_update))
|
|
response = task.wait()
|
|
return Response(response)
|
|
|
|
|
|
class ReverseWhois(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
lookup_keyword = req.query_params.get('lookup_keyword')
|
|
task = query_reverse_whois.apply_async(args=(lookup_keyword,))
|
|
response = task.wait()
|
|
return Response(response)
|
|
|
|
|
|
class DomainIPHistory(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
domain = req.query_params.get('domain')
|
|
task = query_ip_history.apply_async(args=(domain,))
|
|
response = task.wait()
|
|
return Response(response)
|
|
|
|
|
|
class CMSDetector(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
url = req.query_params.get('url')
|
|
#save_db = True if 'save_db' in req.query_params else False
|
|
response = {'status': False}
|
|
|
|
if not (validators.url(url) or validators.domain(url)):
|
|
response['message'] = 'Invalid Domain/URL provided!'
|
|
return Response(response)
|
|
|
|
try:
|
|
# response = get_cms_details(url)
|
|
response = {}
|
|
cms_detector_command = f'python3 /usr/src/github/CMSeeK/cmseek.py'
|
|
cms_detector_command += ' --random-agent --batch --follow-redirect'
|
|
cms_detector_command += f' -u {url}'
|
|
|
|
_, output = run_command(cms_detector_command, remove_ansi_sequence=True)
|
|
|
|
response['message'] = 'Could not detect CMS!'
|
|
|
|
parsed_url = urlparse(url)
|
|
|
|
domain_name = parsed_url.hostname
|
|
port = parsed_url.port
|
|
|
|
find_dir = domain_name
|
|
|
|
if port:
|
|
find_dir += '_{}'.format(port)
|
|
# look for result path in output
|
|
path_regex = r"Result: (\/usr\/src[^\"\s]*)"
|
|
match = re.search(path_regex, output)
|
|
if match:
|
|
cms_json_path = match.group(1)
|
|
if os.path.isfile(cms_json_path):
|
|
cms_file_content = json.loads(open(cms_json_path, 'r').read())
|
|
if not cms_file_content.get('cms_id'):
|
|
return response
|
|
response = {}
|
|
response = cms_file_content
|
|
response['status'] = True
|
|
try:
|
|
# remove results
|
|
cms_dir_path = os.path.dirname(cms_json_path)
|
|
shutil.rmtree(cms_dir_path)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
return Response(response)
|
|
return Response(response)
|
|
except Exception as e:
|
|
response = {'status': False, 'message': str(e)}
|
|
return Response(response)
|
|
|
|
|
|
class IPToDomain(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
ip_address = req.query_params.get('ip_address')
|
|
if not ip_address:
|
|
return Response({
|
|
'status': False,
|
|
'message': 'IP Address Required'
|
|
})
|
|
try:
|
|
logger.info(f'Resolving IP address {ip_address} ...')
|
|
resolved_ips = []
|
|
for ip in IPv4Network(ip_address, False):
|
|
domains = []
|
|
ips = []
|
|
try:
|
|
(domain, domains, ips) = socket.gethostbyaddr(str(ip))
|
|
except socket.herror:
|
|
logger.info(f'No PTR record for {ip_address}')
|
|
domain = str(ip)
|
|
if domain not in domains:
|
|
domains.append(domain)
|
|
resolved_ips.append({'ip': str(ip),'domain': domain, 'domains': domains, 'ips': ips})
|
|
response = {
|
|
'status': True,
|
|
'orig': ip_address,
|
|
'ip_address': resolved_ips,
|
|
}
|
|
except Exception as e:
|
|
logger.exception(e)
|
|
response = {
|
|
'status': False,
|
|
'ip_address': ip_address,
|
|
'message': f'Exception {e}'
|
|
}
|
|
finally:
|
|
return Response(response)
|
|
|
|
|
|
class VulnerabilityReport(APIView):
|
|
def get(self, request):
|
|
req = self.request
|
|
vulnerability_id = req.query_params.get('vulnerability_id')
|
|
return Response({"status": send_hackerone_report(vulnerability_id)})
|
|
|
|
|
|
class GetFileContents(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
name = req.query_params.get('name')
|
|
|
|
response = {}
|
|
response['status'] = False
|
|
|
|
if 'nuclei_config' in req.query_params:
|
|
path = "/root/.config/nuclei/config.yaml"
|
|
if not os.path.exists(path):
|
|
run_command(f'touch {path}')
|
|
response['message'] = 'File Created!'
|
|
f = open(path, "r")
|
|
response['status'] = True
|
|
response['content'] = f.read()
|
|
return Response(response)
|
|
|
|
if 'subfinder_config' in req.query_params:
|
|
path = "/root/.config/subfinder/config.yaml"
|
|
if not os.path.exists(path):
|
|
run_command(f'touch {path}')
|
|
response['message'] = 'File Created!'
|
|
f = open(path, "r")
|
|
response['status'] = True
|
|
response['content'] = f.read()
|
|
return Response(response)
|
|
|
|
if 'naabu_config' in req.query_params:
|
|
path = "/root/.config/naabu/config.yaml"
|
|
if not os.path.exists(path):
|
|
run_command(f'touch {path}')
|
|
response['message'] = 'File Created!'
|
|
f = open(path, "r")
|
|
response['status'] = True
|
|
response['content'] = f.read()
|
|
return Response(response)
|
|
|
|
if 'theharvester_config' in req.query_params:
|
|
path = "/usr/src/github/theHarvester/api-keys.yaml"
|
|
if not os.path.exists(path):
|
|
run_command(f'touch {path}')
|
|
response['message'] = 'File Created!'
|
|
f = open(path, "r")
|
|
response['status'] = True
|
|
response['content'] = f.read()
|
|
return Response(response)
|
|
|
|
if 'amass_config' in req.query_params:
|
|
path = "/root/.config/amass.ini"
|
|
if not os.path.exists(path):
|
|
run_command(f'touch {path}')
|
|
response['message'] = 'File Created!'
|
|
f = open(path, "r")
|
|
response['status'] = True
|
|
response['content'] = f.read()
|
|
return Response(response)
|
|
|
|
if 'gf_pattern' in req.query_params:
|
|
basedir = '/root/.gf'
|
|
path = f'/root/.gf/{name}.json'
|
|
if is_safe_path(basedir, path) and os.path.exists(path):
|
|
content = open(path, "r").read()
|
|
response['status'] = True
|
|
response['content'] = content
|
|
else:
|
|
response['message'] = "Invalid path!"
|
|
response['status'] = False
|
|
return Response(response)
|
|
|
|
|
|
if 'nuclei_template' in req.query_params:
|
|
safe_dir = '/root/nuclei-templates'
|
|
path = f'/root/nuclei-templates/{name}'
|
|
if is_safe_path(safe_dir, path) and os.path.exists(path):
|
|
content = open(path.format(name), "r").read()
|
|
response['status'] = True
|
|
response['content'] = content
|
|
else:
|
|
response['message'] = 'Invalid Path!'
|
|
response['status'] = False
|
|
return Response(response)
|
|
|
|
response['message'] = 'Invalid Query Params'
|
|
return Response(response)
|
|
|
|
|
|
class ListTodoNotes(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
notes = TodoNote.objects.all().order_by('-id')
|
|
scan_id = req.query_params.get('scan_id')
|
|
project = req.query_params.get('project')
|
|
if project:
|
|
notes = notes.filter(project__slug=project)
|
|
target_id = req.query_params.get('target_id')
|
|
todo_id = req.query_params.get('todo_id')
|
|
subdomain_id = req.query_params.get('subdomain_id')
|
|
if target_id:
|
|
notes = notes.filter(scan_history__in=ScanHistory.objects.filter(domain__id=target_id))
|
|
elif scan_id:
|
|
notes = notes.filter(scan_history__id=scan_id)
|
|
if todo_id:
|
|
notes = notes.filter(id=todo_id)
|
|
if subdomain_id:
|
|
notes = notes.filter(subdomain__id=subdomain_id)
|
|
notes = ReconNoteSerializer(notes, many=True)
|
|
return Response({'notes': notes.data})
|
|
|
|
|
|
class ListScanHistory(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
scan_history = ScanHistory.objects.all().order_by('-start_scan_date')
|
|
project = req.query_params.get('project')
|
|
if project:
|
|
scan_history = scan_history.filter(domain__project__slug=project)
|
|
scan_history = ScanHistorySerializer(scan_history, many=True)
|
|
return Response(scan_history.data)
|
|
|
|
|
|
class ListEngines(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
engines = EngineType.objects.order_by('engine_name').all()
|
|
engine_serializer = EngineSerializer(engines, many=True)
|
|
return Response({'engines': engine_serializer.data})
|
|
|
|
|
|
class ListOrganizations(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
organizations = Organization.objects.all()
|
|
organization_serializer = OrganizationSerializer(organizations, many=True)
|
|
return Response({'organizations': organization_serializer.data})
|
|
|
|
|
|
class ListTargetsInOrganization(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
organization_id = req.query_params.get('organization_id')
|
|
organization = Organization.objects.filter(id=organization_id)
|
|
targets = Domain.objects.filter(domains__in=organization)
|
|
organization_serializer = OrganizationSerializer(organization, many=True)
|
|
targets_serializer = OrganizationTargetsSerializer(targets, many=True)
|
|
return Response({'organization': organization_serializer.data, 'domains': targets_serializer.data})
|
|
|
|
|
|
class ListTargetsWithoutOrganization(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
targets = Domain.objects.exclude(domains__in=Organization.objects.all())
|
|
targets_serializer = OrganizationTargetsSerializer(targets, many=True)
|
|
return Response({'domains': targets_serializer.data})
|
|
|
|
|
|
class VisualiseData(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
if scan_id:
|
|
mitch_data = ScanHistory.objects.filter(id=scan_id)
|
|
serializer = VisualiseDataSerializer(mitch_data, many=True)
|
|
return Response(serializer.data)
|
|
else:
|
|
return Response()
|
|
|
|
|
|
class ListTechnology(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
target_id = req.query_params.get('target_id')
|
|
|
|
if target_id:
|
|
tech = Technology.objects.filter(
|
|
technologies__in=Subdomain.objects.filter(
|
|
target_domain__id=target_id)).annotate(
|
|
count=Count('name')).order_by('-count')
|
|
serializer = TechnologyCountSerializer(tech, many=True)
|
|
return Response({"technologies": serializer.data})
|
|
elif scan_id:
|
|
tech = Technology.objects.filter(
|
|
technologies__in=Subdomain.objects.filter(
|
|
scan_history__id=scan_id)).annotate(
|
|
count=Count('name')).order_by('-count')
|
|
serializer = TechnologyCountSerializer(tech, many=True)
|
|
return Response({"technologies": serializer.data})
|
|
else:
|
|
tech = Technology.objects.filter(
|
|
technologies__in=Subdomain.objects.all()).annotate(
|
|
count=Count('name')).order_by('-count')
|
|
serializer = TechnologyCountSerializer(tech, many=True)
|
|
return Response({"technologies": serializer.data})
|
|
|
|
|
|
class ListDorkTypes(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
if scan_id:
|
|
dork = Dork.objects.filter(
|
|
dorks__in=ScanHistory.objects.filter(id=scan_id)
|
|
).values('type').annotate(count=Count('type')).order_by('-count')
|
|
serializer = DorkCountSerializer(dork, many=True)
|
|
return Response({"dorks": serializer.data})
|
|
else:
|
|
dork = Dork.objects.filter(
|
|
dorks__in=ScanHistory.objects.all()
|
|
).values('type').annotate(count=Count('type')).order_by('-count')
|
|
serializer = DorkCountSerializer(dork, many=True)
|
|
return Response({"dorks": serializer.data})
|
|
|
|
|
|
class ListEmails(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
if scan_id:
|
|
email = Email.objects.filter(
|
|
emails__in=ScanHistory.objects.filter(id=scan_id)).order_by('password')
|
|
serializer = EmailSerializer(email, many=True)
|
|
return Response({"emails": serializer.data})
|
|
|
|
|
|
class ListDorks(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
type = req.query_params.get('type')
|
|
if scan_id:
|
|
dork = Dork.objects.filter(
|
|
dorks__in=ScanHistory.objects.filter(id=scan_id))
|
|
else:
|
|
dork = Dork.objects.filter(
|
|
dorks__in=ScanHistory.objects.all())
|
|
if scan_id and type:
|
|
dork = dork.filter(type=type)
|
|
serializer = DorkSerializer(dork, many=True)
|
|
grouped_res = {}
|
|
for item in serializer.data:
|
|
item_type = item['type']
|
|
if item_type not in grouped_res:
|
|
grouped_res[item_type] = []
|
|
grouped_res[item_type].append(item)
|
|
return Response({"dorks": grouped_res})
|
|
|
|
|
|
class ListEmployees(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
if scan_id:
|
|
employee = Employee.objects.filter(
|
|
employees__in=ScanHistory.objects.filter(id=scan_id))
|
|
serializer = EmployeeSerializer(employee, many=True)
|
|
return Response({"employees": serializer.data})
|
|
|
|
|
|
class ListPorts(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
target_id = req.query_params.get('target_id')
|
|
ip_address = req.query_params.get('ip_address')
|
|
|
|
if target_id:
|
|
port = Port.objects.filter(
|
|
ports__in=IpAddress.objects.filter(
|
|
ip_addresses__in=Subdomain.objects.filter(
|
|
target_domain__id=target_id))).distinct()
|
|
elif scan_id:
|
|
port = Port.objects.filter(
|
|
ports__in=IpAddress.objects.filter(
|
|
ip_addresses__in=Subdomain.objects.filter(
|
|
scan_history__id=scan_id))).distinct()
|
|
else:
|
|
port = Port.objects.filter(
|
|
ports__in=IpAddress.objects.filter(
|
|
ip_addresses__in=Subdomain.objects.all())).distinct()
|
|
|
|
if ip_address:
|
|
port = port.filter(ports__address=ip_address).distinct()
|
|
|
|
serializer = PortSerializer(port, many=True)
|
|
return Response({"ports": serializer.data})
|
|
|
|
|
|
class ListSubdomains(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
project = req.query_params.get('project')
|
|
target_id = req.query_params.get('target_id')
|
|
ip_address = req.query_params.get('ip_address')
|
|
port = req.query_params.get('port')
|
|
tech = req.query_params.get('tech')
|
|
|
|
subdomains = Subdomain.objects.filter(target_domain__project__slug=project) if project else Subdomain.objects.all()
|
|
|
|
if scan_id:
|
|
subdomain_query = subdomains.filter(scan_history__id=scan_id).distinct('name')
|
|
elif target_id:
|
|
subdomain_query = subdomains.filter(target_domain__id=target_id).distinct('name')
|
|
else:
|
|
subdomain_query = subdomains.all().distinct('name')
|
|
|
|
if ip_address:
|
|
subdomain_query = subdomain_query.filter(ip_addresses__address=ip_address)
|
|
|
|
if tech:
|
|
subdomain_query = subdomain_query.filter(technologies__name=tech)
|
|
|
|
if port:
|
|
subdomain_query = subdomain_query.filter(
|
|
ip_addresses__in=IpAddress.objects.filter(
|
|
ports__in=Port.objects.filter(
|
|
number=port)))
|
|
|
|
if 'only_important' in req.query_params:
|
|
subdomain_query = subdomain_query.filter(is_important=True)
|
|
|
|
|
|
if 'no_lookup_interesting' in req.query_params:
|
|
serializer = OnlySubdomainNameSerializer(subdomain_query, many=True)
|
|
else:
|
|
serializer = SubdomainSerializer(subdomain_query, many=True)
|
|
return Response({"subdomains": serializer.data})
|
|
|
|
def post(self, req):
|
|
req = self.request
|
|
data = req.data
|
|
|
|
subdomain_ids = data.get('subdomain_ids')
|
|
|
|
subdomain_names = []
|
|
|
|
for id in subdomain_ids:
|
|
subdomain_names.append(Subdomain.objects.get(id=id).name)
|
|
|
|
if subdomain_names:
|
|
return Response({'status': True, "results": subdomain_names})
|
|
|
|
return Response({'status': False})
|
|
|
|
|
|
|
|
class ListOsintUsers(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
if scan_id:
|
|
documents = MetaFinderDocument.objects.filter(scan_history__id=scan_id).exclude(author__isnull=True).values('author').distinct()
|
|
serializer = MetafinderUserSerializer(documents, many=True)
|
|
return Response({"users": serializer.data})
|
|
|
|
|
|
class ListMetadata(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
if scan_id:
|
|
documents = MetaFinderDocument.objects.filter(scan_history__id=scan_id).distinct()
|
|
serializer = MetafinderDocumentSerializer(documents, many=True)
|
|
return Response({"metadata": serializer.data})
|
|
|
|
|
|
class ListIPs(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
target_id = req.query_params.get('target_id')
|
|
|
|
port = req.query_params.get('port')
|
|
|
|
if target_id:
|
|
ips = IpAddress.objects.filter(
|
|
ip_addresses__in=Subdomain.objects.filter(
|
|
target_domain__id=target_id)).distinct()
|
|
elif scan_id:
|
|
ips = IpAddress.objects.filter(
|
|
ip_addresses__in=Subdomain.objects.filter(
|
|
scan_history__id=scan_id)).distinct()
|
|
else:
|
|
ips = IpAddress.objects.filter(
|
|
ip_addresses__in=Subdomain.objects.all()).distinct()
|
|
|
|
if port:
|
|
ips = ips.filter(
|
|
ports__in=Port.objects.filter(
|
|
number=port)).distinct()
|
|
|
|
|
|
serializer = IpSerializer(ips, many=True)
|
|
return Response({"ips": serializer.data})
|
|
|
|
|
|
class IpAddressViewSet(viewsets.ModelViewSet):
|
|
queryset = Subdomain.objects.none()
|
|
serializer_class = IpSubdomainSerializer
|
|
|
|
def get_queryset(self):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
|
|
if scan_id:
|
|
self.queryset = Subdomain.objects.filter(
|
|
scan_history__id=scan_id).exclude(
|
|
ip_addresses__isnull=True).distinct()
|
|
else:
|
|
self.serializer_class = IpSerializer
|
|
self.queryset = IpAddress.objects.all()
|
|
return self.queryset
|
|
|
|
def paginate_queryset(self, queryset, view=None):
|
|
if 'no_page' in self.request.query_params:
|
|
return None
|
|
return self.paginator.paginate_queryset(
|
|
queryset, self.request, view=self)
|
|
|
|
|
|
class SubdomainsViewSet(viewsets.ModelViewSet):
|
|
queryset = Subdomain.objects.none()
|
|
serializer_class = SubdomainSerializer
|
|
|
|
def get_queryset(self):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
if scan_id:
|
|
if 'only_screenshot' in self.request.query_params:
|
|
return (
|
|
Subdomain.objects
|
|
.filter(scan_history__id=scan_id)
|
|
.exclude(screenshot_path__isnull=True))
|
|
return Subdomain.objects.filter(scan_history=scan_id)
|
|
|
|
def paginate_queryset(self, queryset, view=None):
|
|
if 'no_page' in self.request.query_params:
|
|
return None
|
|
return self.paginator.paginate_queryset(
|
|
queryset, self.request, view=self)
|
|
|
|
|
|
class SubdomainChangesViewSet(viewsets.ModelViewSet):
|
|
'''
|
|
This viewset will return the Subdomain changes
|
|
To get the new subdomains, we will look for ScanHistory with
|
|
subdomain_discovery = True and the status of the last scan has to be
|
|
successful and calculate difference
|
|
'''
|
|
queryset = Subdomain.objects.none()
|
|
serializer_class = SubdomainChangesSerializer
|
|
|
|
def get_queryset(self):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
changes = req.query_params.get('changes')
|
|
domain_id = ScanHistory.objects.filter(id=scan_id)[0].domain.id
|
|
scan_history_query = (
|
|
ScanHistory.objects
|
|
.filter(domain=domain_id)
|
|
.filter(tasks__overlap=['subdomain_discovery'])
|
|
.filter(id__lte=scan_id)
|
|
.exclude(Q(scan_status=-1) | Q(scan_status=1))
|
|
)
|
|
if scan_history_query.count() > 1:
|
|
last_scan = scan_history_query.order_by('-start_scan_date')[1]
|
|
scanned_host_q1 = (
|
|
Subdomain.objects
|
|
.filter(scan_history__id=scan_id)
|
|
.values('name')
|
|
)
|
|
scanned_host_q2 = (
|
|
Subdomain.objects
|
|
.filter(scan_history__id=last_scan.id)
|
|
.values('name')
|
|
)
|
|
added_subdomain = scanned_host_q1.difference(scanned_host_q2)
|
|
removed_subdomains = scanned_host_q2.difference(scanned_host_q1)
|
|
if changes == 'added':
|
|
return (
|
|
Subdomain.objects
|
|
.filter(scan_history=scan_id)
|
|
.filter(name__in=added_subdomain)
|
|
.annotate(
|
|
change=Value('added', output_field=CharField())
|
|
)
|
|
)
|
|
elif changes == 'removed':
|
|
return (
|
|
Subdomain.objects
|
|
.filter(scan_history=last_scan)
|
|
.filter(name__in=removed_subdomains)
|
|
.annotate(
|
|
change=Value('removed', output_field=CharField())
|
|
)
|
|
)
|
|
else:
|
|
added_subdomain = (
|
|
Subdomain.objects
|
|
.filter(scan_history=scan_id)
|
|
.filter(name__in=added_subdomain)
|
|
.annotate(
|
|
change=Value('added', output_field=CharField())
|
|
)
|
|
)
|
|
removed_subdomains = (
|
|
Subdomain.objects
|
|
.filter(scan_history=last_scan)
|
|
.filter(name__in=removed_subdomains)
|
|
.annotate(
|
|
change=Value('removed', output_field=CharField())
|
|
)
|
|
)
|
|
changes = added_subdomain.union(removed_subdomains)
|
|
return changes
|
|
return self.queryset
|
|
|
|
def paginate_queryset(self, queryset, view=None):
|
|
if 'no_page' in self.request.query_params:
|
|
return None
|
|
return self.paginator.paginate_queryset(
|
|
queryset, self.request, view=self)
|
|
|
|
|
|
class EndPointChangesViewSet(viewsets.ModelViewSet):
|
|
'''
|
|
This viewset will return the EndPoint changes
|
|
'''
|
|
queryset = EndPoint.objects.none()
|
|
serializer_class = EndPointChangesSerializer
|
|
|
|
def get_queryset(self):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
changes = req.query_params.get('changes')
|
|
domain_id = ScanHistory.objects.filter(id=scan_id).first().domain.id
|
|
scan_history = (
|
|
ScanHistory.objects
|
|
.filter(domain=domain_id)
|
|
.filter(tasks__overlap=['fetch_url'])
|
|
.filter(id__lte=scan_id)
|
|
.filter(scan_status=2)
|
|
)
|
|
if scan_history.count() > 1:
|
|
last_scan = scan_history.order_by('-start_scan_date')[1]
|
|
scanned_host_q1 = (
|
|
EndPoint.objects
|
|
.filter(scan_history__id=scan_id)
|
|
.values('http_url')
|
|
)
|
|
scanned_host_q2 = (
|
|
EndPoint.objects
|
|
.filter(scan_history__id=last_scan.id)
|
|
.values('http_url')
|
|
)
|
|
added_endpoints = scanned_host_q1.difference(scanned_host_q2)
|
|
removed_endpoints = scanned_host_q2.difference(scanned_host_q1)
|
|
if changes == 'added':
|
|
return (
|
|
EndPoint.objects
|
|
.filter(scan_history=scan_id)
|
|
.filter(http_url__in=added_endpoints)
|
|
.annotate(change=Value('added', output_field=CharField()))
|
|
)
|
|
elif changes == 'removed':
|
|
return (
|
|
EndPoint.objects
|
|
.filter(scan_history=last_scan)
|
|
.filter(http_url__in=removed_endpoints)
|
|
.annotate(change=Value('removed', output_field=CharField()))
|
|
)
|
|
else:
|
|
added_endpoints = (
|
|
EndPoint.objects
|
|
.filter(scan_history=scan_id)
|
|
.filter(http_url__in=added_endpoints)
|
|
.annotate(change=Value('added', output_field=CharField()))
|
|
)
|
|
removed_endpoints = (
|
|
EndPoint.objects
|
|
.filter(scan_history=last_scan)
|
|
.filter(http_url__in=removed_endpoints)
|
|
.annotate(change=Value('removed', output_field=CharField()))
|
|
)
|
|
changes = added_endpoints.union(removed_endpoints)
|
|
return changes
|
|
return self.queryset
|
|
|
|
def paginate_queryset(self, queryset, view=None):
|
|
if 'no_page' in self.request.query_params:
|
|
return None
|
|
return self.paginator.paginate_queryset(
|
|
queryset, self.request, view=self)
|
|
|
|
|
|
class InterestingSubdomainViewSet(viewsets.ModelViewSet):
|
|
queryset = Subdomain.objects.none()
|
|
serializer_class = SubdomainSerializer
|
|
|
|
def get_queryset(self):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
domain_id = req.query_params.get('target_id')
|
|
|
|
if 'only_subdomains' in self.request.query_params:
|
|
self.serializer_class = InterestingSubdomainSerializer
|
|
|
|
if scan_id:
|
|
self.queryset = get_interesting_subdomains(scan_history=scan_id)
|
|
elif domain_id:
|
|
self.queryset = get_interesting_subdomains(domain_id=domain_id)
|
|
else:
|
|
self.queryset = get_interesting_subdomains()
|
|
|
|
return self.queryset
|
|
|
|
def filter_queryset(self, qs):
|
|
qs = self.queryset.filter()
|
|
search_value = self.request.GET.get(u'search[value]', None)
|
|
_order_col = self.request.GET.get(u'order[0][column]', None)
|
|
_order_direction = self.request.GET.get(u'order[0][dir]', None)
|
|
order_col = 'content_length'
|
|
if _order_col == '0':
|
|
order_col = 'name'
|
|
elif _order_col == '1':
|
|
order_col = 'page_title'
|
|
elif _order_col == '2':
|
|
order_col = 'http_status'
|
|
elif _order_col == '3':
|
|
order_col = 'content_length'
|
|
|
|
if _order_direction == 'desc':
|
|
order_col = f'-{order_col}'
|
|
|
|
if search_value:
|
|
qs = self.queryset.filter(
|
|
Q(name__icontains=search_value) |
|
|
Q(page_title__icontains=search_value) |
|
|
Q(http_status__icontains=search_value)
|
|
)
|
|
return qs.order_by(order_col)
|
|
|
|
def paginate_queryset(self, queryset, view=None):
|
|
if 'no_page' in self.request.query_params:
|
|
return None
|
|
return self.paginator.paginate_queryset(
|
|
queryset, self.request, view=self)
|
|
|
|
|
|
class InterestingEndpointViewSet(viewsets.ModelViewSet):
|
|
queryset = EndPoint.objects.none()
|
|
serializer_class = EndpointSerializer
|
|
|
|
def get_queryset(self):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
target_id = req.query_params.get('target_id')
|
|
if 'only_endpoints' in self.request.query_params:
|
|
self.serializer_class = InterestingEndPointSerializer
|
|
if scan_id:
|
|
return get_interesting_endpoints(scan_history=scan_id)
|
|
elif target_id:
|
|
return get_interesting_endpoints(target=target_id)
|
|
else:
|
|
return get_interesting_endpoints()
|
|
|
|
def paginate_queryset(self, queryset, view=None):
|
|
if 'no_page' in self.request.query_params:
|
|
return None
|
|
return self.paginator.paginate_queryset(
|
|
queryset, self.request, view=self)
|
|
|
|
|
|
class SubdomainDatatableViewSet(viewsets.ModelViewSet):
|
|
queryset = Subdomain.objects.none()
|
|
serializer_class = SubdomainSerializer
|
|
|
|
def get_queryset(self):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
target_id = req.query_params.get('target_id')
|
|
url_query = req.query_params.get('query_param')
|
|
ip_address = req.query_params.get('ip_address')
|
|
name = req.query_params.get('name')
|
|
project = req.query_params.get('project')
|
|
|
|
subdomains = Subdomain.objects.filter(target_domain__project__slug=project)
|
|
|
|
if 'is_important' in req.query_params:
|
|
subdomains = subdomains.filter(is_important=True)
|
|
|
|
if target_id:
|
|
self.queryset = (
|
|
subdomains
|
|
.filter(target_domain__id=target_id)
|
|
.distinct()
|
|
)
|
|
elif url_query:
|
|
self.queryset = (
|
|
subdomains
|
|
.filter(Q(target_domain__name=url_query))
|
|
.distinct()
|
|
)
|
|
elif scan_id:
|
|
self.queryset = (
|
|
subdomains
|
|
.filter(scan_history__id=scan_id)
|
|
.distinct()
|
|
)
|
|
else:
|
|
self.queryset = subdomains.distinct()
|
|
|
|
if 'only_directory' in req.query_params:
|
|
self.queryset = self.queryset.exclude(directories__isnull=True)
|
|
|
|
if ip_address:
|
|
self.queryset = self.queryset.filter(ip_addresses__address__icontains=ip_address)
|
|
|
|
if name:
|
|
self.queryset = self.queryset.filter(name=name)
|
|
|
|
return self.queryset
|
|
|
|
def filter_queryset(self, qs):
|
|
qs = self.queryset.filter()
|
|
search_value = self.request.GET.get(u'search[value]', None)
|
|
_order_col = self.request.GET.get(u'order[0][column]', None)
|
|
_order_direction = self.request.GET.get(u'order[0][dir]', None)
|
|
order_col = 'content_length'
|
|
if _order_col == '0':
|
|
order_col = 'checked'
|
|
elif _order_col == '1':
|
|
order_col = 'name'
|
|
elif _order_col == '4':
|
|
order_col = 'http_status'
|
|
elif _order_col == '5':
|
|
order_col = 'page_title'
|
|
elif _order_col == '8':
|
|
order_col = 'content_length'
|
|
elif _order_col == '10':
|
|
order_col = 'response_time'
|
|
if _order_direction == 'desc':
|
|
order_col = f'-{order_col}'
|
|
# if the search query is separated by = means, it is a specific lookup
|
|
# divide the search query into two half and lookup
|
|
if search_value:
|
|
operators = ['=', '&', '|', '>', '<', '!']
|
|
if any(x in search_value for x in operators):
|
|
if '&' in search_value:
|
|
complex_query = search_value.split('&')
|
|
for query in complex_query:
|
|
if query.strip():
|
|
qs = qs & self.special_lookup(query.strip())
|
|
elif '|' in search_value:
|
|
qs = Subdomain.objects.none()
|
|
complex_query = search_value.split('|')
|
|
for query in complex_query:
|
|
if query.strip():
|
|
qs = self.special_lookup(query.strip()) | qs
|
|
else:
|
|
qs = self.special_lookup(search_value)
|
|
else:
|
|
qs = self.general_lookup(search_value)
|
|
return qs.order_by(order_col)
|
|
|
|
def general_lookup(self, search_value):
|
|
qs = self.queryset.filter(
|
|
Q(name__icontains=search_value) |
|
|
Q(cname__icontains=search_value) |
|
|
Q(http_status__icontains=search_value) |
|
|
Q(page_title__icontains=search_value) |
|
|
Q(http_url__icontains=search_value) |
|
|
Q(technologies__name__icontains=search_value) |
|
|
Q(webserver__icontains=search_value) |
|
|
Q(ip_addresses__address__icontains=search_value) |
|
|
Q(ip_addresses__ports__number__icontains=search_value) |
|
|
Q(ip_addresses__ports__service_name__icontains=search_value) |
|
|
Q(ip_addresses__ports__description__icontains=search_value)
|
|
)
|
|
|
|
if 'only_directory' in self.request.query_params:
|
|
qs = qs | self.queryset.filter(
|
|
Q(directories__directory_files__name__icontains=search_value)
|
|
)
|
|
|
|
return qs
|
|
|
|
def special_lookup(self, search_value):
|
|
qs = self.queryset.filter()
|
|
if '=' in search_value:
|
|
search_param = search_value.split("=")
|
|
title = search_param[0].lower().strip()
|
|
content = search_param[1].lower().strip()
|
|
if 'name' in title:
|
|
qs = self.queryset.filter(name__icontains=content)
|
|
elif 'page_title' in title:
|
|
qs = self.queryset.filter(page_title__icontains=content)
|
|
elif 'http_url' in title:
|
|
qs = self.queryset.filter(http_url__icontains=content)
|
|
elif 'content_type' in title:
|
|
qs = self.queryset.filter(content_type__icontains=content)
|
|
elif 'cname' in title:
|
|
qs = self.queryset.filter(cname__icontains=content)
|
|
elif 'webserver' in title:
|
|
qs = self.queryset.filter(webserver__icontains=content)
|
|
elif 'ip_addresses' in title:
|
|
qs = self.queryset.filter(
|
|
ip_addresses__address__icontains=content)
|
|
elif 'is_important' in title:
|
|
if 'true' in content.lower():
|
|
qs = self.queryset.filter(is_important=True)
|
|
else:
|
|
qs = self.queryset.filter(is_important=False)
|
|
elif 'port' in title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(ip_addresses__ports__number__icontains=content)
|
|
|
|
|
self.queryset
|
|
.filter(ip_addresses__ports__service_name__icontains=content)
|
|
|
|
|
self.queryset
|
|
.filter(ip_addresses__ports__description__icontains=content)
|
|
)
|
|
elif 'technology' in title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(technologies__name__icontains=content)
|
|
)
|
|
elif 'http_status' in title:
|
|
try:
|
|
int_http_status = int(content)
|
|
qs = self.queryset.filter(http_status=int_http_status)
|
|
except Exception as e:
|
|
print(e)
|
|
elif 'content_length' in title:
|
|
try:
|
|
int_http_status = int(content)
|
|
qs = self.queryset.filter(content_length=int_http_status)
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
elif '>' in search_value:
|
|
search_param = search_value.split(">")
|
|
title = search_param[0].lower().strip()
|
|
content = search_param[1].lower().strip()
|
|
if 'http_status' in title:
|
|
try:
|
|
int_val = int(content)
|
|
qs = self.queryset.filter(http_status__gt=int_val)
|
|
except Exception as e:
|
|
print(e)
|
|
elif 'content_length' in title:
|
|
try:
|
|
int_val = int(content)
|
|
qs = self.queryset.filter(content_length__gt=int_val)
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
elif '<' in search_value:
|
|
search_param = search_value.split("<")
|
|
title = search_param[0].lower().strip()
|
|
content = search_param[1].lower().strip()
|
|
if 'http_status' in title:
|
|
try:
|
|
int_val = int(content)
|
|
qs = self.queryset.filter(http_status__lt=int_val)
|
|
except Exception as e:
|
|
print(e)
|
|
elif 'content_length' in title:
|
|
try:
|
|
int_val = int(content)
|
|
qs = self.queryset.filter(content_length__lt=int_val)
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
elif '!' in search_value:
|
|
search_param = search_value.split("!")
|
|
title = search_param[0].lower().strip()
|
|
content = search_param[1].lower().strip()
|
|
if 'name' in title:
|
|
qs = self.queryset.exclude(name__icontains=content)
|
|
elif 'page_title' in title:
|
|
qs = self.queryset.exclude(page_title__icontains=content)
|
|
elif 'http_url' in title:
|
|
qs = self.queryset.exclude(http_url__icontains=content)
|
|
elif 'content_type' in title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(content_type__icontains=content)
|
|
)
|
|
elif 'cname' in title:
|
|
qs = self.queryset.exclude(cname__icontains=content)
|
|
elif 'webserver' in title:
|
|
qs = self.queryset.exclude(webserver__icontains=content)
|
|
elif 'ip_addresses' in title:
|
|
qs = self.queryset.exclude(
|
|
ip_addresses__address__icontains=content)
|
|
elif 'port' in title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(ip_addresses__ports__number__icontains=content)
|
|
|
|
|
self.queryset
|
|
.exclude(ip_addresses__ports__service_name__icontains=content)
|
|
|
|
|
self.queryset
|
|
.exclude(ip_addresses__ports__description__icontains=content)
|
|
)
|
|
elif 'technology' in title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(technologies__name__icontains=content)
|
|
)
|
|
elif 'http_status' in title:
|
|
try:
|
|
int_http_status = int(content)
|
|
qs = self.queryset.exclude(http_status=int_http_status)
|
|
except Exception as e:
|
|
print(e)
|
|
elif 'content_length' in title:
|
|
try:
|
|
int_http_status = int(content)
|
|
qs = self.queryset.exclude(content_length=int_http_status)
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
return qs
|
|
|
|
|
|
class ListActivityLogsViewSet(viewsets.ModelViewSet):
|
|
serializer_class = CommandSerializer
|
|
queryset = Command.objects.none()
|
|
def get_queryset(self):
|
|
req = self.request
|
|
activity_id = req.query_params.get('activity_id')
|
|
self.queryset = Command.objects.filter(activity__id=activity_id)
|
|
return self.queryset
|
|
|
|
|
|
class ListScanLogsViewSet(viewsets.ModelViewSet):
|
|
serializer_class = CommandSerializer
|
|
queryset = Command.objects.none()
|
|
def get_queryset(self):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_id')
|
|
self.queryset = Command.objects.filter(scan_history__id=scan_id)
|
|
return self.queryset
|
|
|
|
|
|
class ListEndpoints(APIView):
|
|
def get(self, request, format=None):
|
|
req = self.request
|
|
|
|
scan_id = req.query_params.get('scan_id')
|
|
target_id = req.query_params.get('target_id')
|
|
subdomain_name = req.query_params.get('subdomain_name')
|
|
pattern = req.query_params.get('pattern')
|
|
|
|
if scan_id:
|
|
endpoints = (
|
|
EndPoint.objects
|
|
.filter(scan_history__id=scan_id)
|
|
)
|
|
elif target_id:
|
|
endpoints = (
|
|
EndPoint.objects
|
|
.filter(target_domain__id=target_id)
|
|
.distinct()
|
|
)
|
|
else:
|
|
endpoints = EndPoint.objects.all()
|
|
|
|
if subdomain_name:
|
|
endpoints = endpoints.filter(subdomain__name=subdomain_name)
|
|
|
|
if pattern:
|
|
endpoints = endpoints.filter(matched_gf_patterns__icontains=pattern)
|
|
|
|
if 'only_urls' in req.query_params:
|
|
endpoints_serializer = EndpointOnlyURLsSerializer(endpoints, many=True)
|
|
|
|
else:
|
|
endpoints_serializer = EndpointSerializer(endpoints, many=True)
|
|
|
|
return Response({'endpoints': endpoints_serializer.data})
|
|
|
|
|
|
class EndPointViewSet(viewsets.ModelViewSet):
|
|
queryset = EndPoint.objects.none()
|
|
serializer_class = EndpointSerializer
|
|
|
|
def get_queryset(self):
|
|
req = self.request
|
|
|
|
scan_id = req.query_params.get('scan_history')
|
|
target_id = req.query_params.get('target_id')
|
|
url_query = req.query_params.get('query_param')
|
|
subdomain_id = req.query_params.get('subdomain_id')
|
|
project = req.query_params.get('project')
|
|
|
|
endpoints_obj = EndPoint.objects.filter(scan_history__domain__project__slug=project)
|
|
|
|
gf_tag = req.query_params.get(
|
|
'gf_tag') if 'gf_tag' in req.query_params else None
|
|
|
|
if scan_id:
|
|
endpoints = (
|
|
endpoints_obj
|
|
.filter(scan_history__id=scan_id)
|
|
.distinct()
|
|
)
|
|
else:
|
|
endpoints = endpoints_obj.distinct()
|
|
|
|
if url_query:
|
|
endpoints = (
|
|
endpoints
|
|
.filter(Q(target_domain__name=url_query))
|
|
.distinct()
|
|
)
|
|
|
|
if gf_tag:
|
|
endpoints = endpoints.filter(matched_gf_patterns__icontains=gf_tag)
|
|
|
|
if target_id:
|
|
endpoints = endpoints.filter(target_domain__id=target_id)
|
|
|
|
if subdomain_id:
|
|
endpoints = endpoints.filter(subdomain__id=subdomain_id)
|
|
|
|
if 'only_urls' in req.query_params:
|
|
self.serializer_class = EndpointOnlyURLsSerializer
|
|
|
|
# Filter status code 404 and 0
|
|
# endpoints = (
|
|
# endpoints
|
|
# .exclude(http_status=0)
|
|
# .exclude(http_status=None)
|
|
# .exclude(http_status=404)
|
|
# )
|
|
|
|
self.queryset = endpoints
|
|
|
|
return self.queryset
|
|
|
|
def filter_queryset(self, qs):
|
|
qs = self.queryset.filter()
|
|
search_value = self.request.GET.get(u'search[value]', None)
|
|
_order_col = self.request.GET.get(u'order[0][column]', None)
|
|
_order_direction = self.request.GET.get(u'order[0][dir]', None)
|
|
if search_value or _order_col or _order_direction:
|
|
order_col = 'content_length'
|
|
if _order_col == '1':
|
|
order_col = 'http_url'
|
|
elif _order_col == '2':
|
|
order_col = 'http_status'
|
|
elif _order_col == '3':
|
|
order_col = 'page_title'
|
|
elif _order_col == '4':
|
|
order_col = 'matched_gf_patterns'
|
|
elif _order_col == '5':
|
|
order_col = 'content_type'
|
|
elif _order_col == '6':
|
|
order_col = 'content_length'
|
|
elif _order_col == '7':
|
|
order_col = 'techs'
|
|
elif _order_col == '8':
|
|
order_col = 'webserver'
|
|
elif _order_col == '9':
|
|
order_col = 'response_time'
|
|
if _order_direction == 'desc':
|
|
order_col = f'-{order_col}'
|
|
# if the search query is separated by = means, it is a specific lookup
|
|
# divide the search query into two half and lookup
|
|
if '=' in search_value or '&' in search_value or '|' in search_value or '>' in search_value or '<' in search_value or '!' in search_value:
|
|
if '&' in search_value:
|
|
complex_query = search_value.split('&')
|
|
for query in complex_query:
|
|
if query.strip():
|
|
qs = qs & self.special_lookup(query.strip())
|
|
elif '|' in search_value:
|
|
qs = Subdomain.objects.none()
|
|
complex_query = search_value.split('|')
|
|
for query in complex_query:
|
|
if query.strip():
|
|
qs = self.special_lookup(query.strip()) | qs
|
|
else:
|
|
qs = self.special_lookup(search_value)
|
|
else:
|
|
qs = self.general_lookup(search_value)
|
|
return qs.order_by(order_col)
|
|
return qs
|
|
|
|
def general_lookup(self, search_value):
|
|
return \
|
|
self.queryset.filter(Q(http_url__icontains=search_value) |
|
|
Q(page_title__icontains=search_value) |
|
|
Q(http_status__icontains=search_value) |
|
|
Q(content_type__icontains=search_value) |
|
|
Q(webserver__icontains=search_value) |
|
|
Q(techs__name__icontains=search_value) |
|
|
Q(content_type__icontains=search_value) |
|
|
Q(matched_gf_patterns__icontains=search_value))
|
|
|
|
def special_lookup(self, search_value):
|
|
qs = self.queryset.filter()
|
|
if '=' in search_value:
|
|
search_param = search_value.split("=")
|
|
lookup_title = search_param[0].lower().strip()
|
|
lookup_content = search_param[1].lower().strip()
|
|
if 'http_url' in lookup_title:
|
|
qs = self.queryset.filter(http_url__icontains=lookup_content)
|
|
elif 'page_title' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(page_title__icontains=lookup_content)
|
|
)
|
|
elif 'content_type' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(content_type__icontains=lookup_content)
|
|
)
|
|
elif 'webserver' in lookup_title:
|
|
qs = self.queryset.filter(webserver__icontains=lookup_content)
|
|
elif 'technology' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(techs__name__icontains=lookup_content)
|
|
)
|
|
elif 'gf_pattern' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(matched_gf_patterns__icontains=lookup_content)
|
|
)
|
|
elif 'http_status' in lookup_title:
|
|
try:
|
|
int_http_status = int(lookup_content)
|
|
qs = self.queryset.filter(http_status=int_http_status)
|
|
except Exception as e:
|
|
print(e)
|
|
elif 'content_length' in lookup_title:
|
|
try:
|
|
int_http_status = int(lookup_content)
|
|
qs = self.queryset.filter(content_length=int_http_status)
|
|
except Exception as e:
|
|
print(e)
|
|
elif '>' in search_value:
|
|
search_param = search_value.split(">")
|
|
lookup_title = search_param[0].lower().strip()
|
|
lookup_content = search_param[1].lower().strip()
|
|
if 'http_status' in lookup_title:
|
|
try:
|
|
int_val = int(lookup_content)
|
|
qs = (
|
|
self.queryset
|
|
.filter(http_status__gt=int_val)
|
|
)
|
|
except Exception as e:
|
|
print(e)
|
|
elif 'content_length' in lookup_title:
|
|
try:
|
|
int_val = int(lookup_content)
|
|
qs = self.queryset.filter(content_length__gt=int_val)
|
|
except Exception as e:
|
|
print(e)
|
|
elif '<' in search_value:
|
|
search_param = search_value.split("<")
|
|
lookup_title = search_param[0].lower().strip()
|
|
lookup_content = search_param[1].lower().strip()
|
|
if 'http_status' in lookup_title:
|
|
try:
|
|
int_val = int(lookup_content)
|
|
qs = self.queryset.filter(http_status__lt=int_val)
|
|
except Exception as e:
|
|
print(e)
|
|
elif 'content_length' in lookup_title:
|
|
try:
|
|
int_val = int(lookup_content)
|
|
qs = self.queryset.filter(content_length__lt=int_val)
|
|
except Exception as e:
|
|
print(e)
|
|
elif '!' in search_value:
|
|
search_param = search_value.split("!")
|
|
lookup_title = search_param[0].lower().strip()
|
|
lookup_content = search_param[1].lower().strip()
|
|
if 'http_url' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(http_url__icontains=lookup_content)
|
|
)
|
|
elif 'page_title' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(page_title__icontains=lookup_content)
|
|
)
|
|
elif 'content_type' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(content_type__icontains=lookup_content)
|
|
)
|
|
elif 'webserver' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(webserver__icontains=lookup_content)
|
|
)
|
|
elif 'technology' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(techs__name__icontains=lookup_content)
|
|
)
|
|
elif 'gf_pattern' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(matched_gf_patterns__icontains=lookup_content)
|
|
)
|
|
elif 'http_status' in lookup_title:
|
|
try:
|
|
int_http_status = int(lookup_content)
|
|
qs = self.queryset.exclude(http_status=int_http_status)
|
|
except Exception as e:
|
|
print(e)
|
|
elif 'content_length' in lookup_title:
|
|
try:
|
|
int_http_status = int(lookup_content)
|
|
qs = self.queryset.exclude(content_length=int_http_status)
|
|
except Exception as e:
|
|
print(e)
|
|
return qs
|
|
|
|
|
|
class DirectoryViewSet(viewsets.ModelViewSet):
|
|
queryset = DirectoryFile.objects.none()
|
|
serializer_class = DirectoryFileSerializer
|
|
|
|
def get_queryset(self):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_history')
|
|
subdomain_id = req.query_params.get('subdomain_id')
|
|
subdomains = None
|
|
if not (scan_id or subdomain_id):
|
|
return Response({
|
|
'status': False,
|
|
'message': 'Scan id or subdomain id must be provided.'
|
|
})
|
|
elif scan_id:
|
|
subdomains = Subdomain.objects.filter(scan_history__id=scan_id)
|
|
elif subdomain_id:
|
|
subdomains = Subdomain.objects.filter(id=subdomain_id)
|
|
dirs_scans = DirectoryScan.objects.filter(directories__in=subdomains)
|
|
qs = (
|
|
DirectoryFile.objects
|
|
.filter(directory_files__in=dirs_scans)
|
|
.distinct()
|
|
)
|
|
self.queryset = qs
|
|
return self.queryset
|
|
|
|
|
|
class VulnerabilityViewSet(viewsets.ModelViewSet):
|
|
queryset = Vulnerability.objects.none()
|
|
serializer_class = VulnerabilitySerializer
|
|
|
|
def get_queryset(self):
|
|
req = self.request
|
|
scan_id = req.query_params.get('scan_history')
|
|
target_id = req.query_params.get('target_id')
|
|
domain = req.query_params.get('domain')
|
|
severity = req.query_params.get('severity')
|
|
subdomain_id = req.query_params.get('subdomain_id')
|
|
subdomain_name = req.query_params.get('subdomain')
|
|
vulnerability_name = req.query_params.get('vulnerability_name')
|
|
slug = self.request.GET.get('project', None)
|
|
|
|
if slug:
|
|
vulnerabilities = Vulnerability.objects.filter(scan_history__domain__project__slug=slug)
|
|
else:
|
|
vulnerabilities = Vulnerability.objects.all()
|
|
|
|
if scan_id:
|
|
qs = (
|
|
vulnerabilities
|
|
.filter(scan_history__id=scan_id)
|
|
.distinct()
|
|
)
|
|
elif target_id:
|
|
qs = (
|
|
vulnerabilities
|
|
.filter(target_domain__id=target_id)
|
|
.distinct()
|
|
)
|
|
elif subdomain_name:
|
|
subdomains = Subdomain.objects.filter(name=subdomain_name)
|
|
qs = (
|
|
vulnerabilities
|
|
.filter(subdomain__in=subdomains)
|
|
.distinct()
|
|
)
|
|
else:
|
|
qs = vulnerabilities.distinct()
|
|
|
|
if domain:
|
|
qs = qs.filter(Q(target_domain__name=domain)).distinct()
|
|
if vulnerability_name:
|
|
qs = qs.filter(Q(name=vulnerability_name)).distinct()
|
|
if severity:
|
|
qs = qs.filter(severity=severity)
|
|
if subdomain_id:
|
|
qs = qs.filter(subdomain__id=subdomain_id)
|
|
self.queryset = qs
|
|
return self.queryset
|
|
|
|
def filter_queryset(self, qs):
|
|
qs = self.queryset.filter()
|
|
search_value = self.request.GET.get(u'search[value]', None)
|
|
_order_col = self.request.GET.get(u'order[0][column]', None)
|
|
_order_direction = self.request.GET.get(u'order[0][dir]', None)
|
|
if search_value or _order_col or _order_direction:
|
|
order_col = 'severity'
|
|
if _order_col == '1':
|
|
order_col = 'source'
|
|
elif _order_col == '3':
|
|
order_col = 'name'
|
|
elif _order_col == '7':
|
|
order_col = 'severity'
|
|
elif _order_col == '11':
|
|
order_col = 'http_url'
|
|
elif _order_col == '15':
|
|
order_col = 'open_status'
|
|
|
|
if _order_direction == 'desc':
|
|
order_col = f'-{order_col}'
|
|
# if the search query is separated by = means, it is a specific lookup
|
|
# divide the search query into two half and lookup
|
|
operators = ['=', '&', '|', '>', '<', '!']
|
|
if any(x in search_value for x in operators):
|
|
if '&' in search_value:
|
|
complex_query = search_value.split('&')
|
|
for query in complex_query:
|
|
if query.strip():
|
|
qs = qs & self.special_lookup(query.strip())
|
|
elif '|' in search_value:
|
|
qs = Subdomain.objects.none()
|
|
complex_query = search_value.split('|')
|
|
for query in complex_query:
|
|
if query.strip():
|
|
qs = self.special_lookup(query.strip()) | qs
|
|
else:
|
|
qs = self.special_lookup(search_value)
|
|
else:
|
|
qs = self.general_lookup(search_value)
|
|
return qs.order_by(order_col)
|
|
return qs.order_by('-severity')
|
|
|
|
def general_lookup(self, search_value):
|
|
qs = (
|
|
self.queryset
|
|
.filter(Q(http_url__icontains=search_value) |
|
|
Q(target_domain__name__icontains=search_value) |
|
|
Q(template__icontains=search_value) |
|
|
Q(template_id__icontains=search_value) |
|
|
Q(name__icontains=search_value) |
|
|
Q(severity__icontains=search_value) |
|
|
Q(description__icontains=search_value) |
|
|
Q(extracted_results__icontains=search_value) |
|
|
Q(references__url__icontains=search_value) |
|
|
Q(cve_ids__name__icontains=search_value) |
|
|
Q(cwe_ids__name__icontains=search_value) |
|
|
Q(cvss_metrics__icontains=search_value) |
|
|
Q(cvss_score__icontains=search_value) |
|
|
Q(type__icontains=search_value) |
|
|
Q(open_status__icontains=search_value) |
|
|
Q(hackerone_report_id__icontains=search_value) |
|
|
Q(tags__name__icontains=search_value))
|
|
)
|
|
return qs
|
|
|
|
def special_lookup(self, search_value):
|
|
qs = self.queryset.filter()
|
|
if '=' in search_value:
|
|
search_param = search_value.split("=")
|
|
lookup_title = search_param[0].lower().strip()
|
|
lookup_content = search_param[1].lower().strip()
|
|
if 'severity' in lookup_title:
|
|
severity_value = NUCLEI_SEVERITY_MAP.get(lookup_content, -1)
|
|
qs = (
|
|
self.queryset
|
|
.filter(severity=severity_value)
|
|
)
|
|
elif 'name' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(name__icontains=lookup_content)
|
|
)
|
|
elif 'http_url' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(http_url__icontains=lookup_content)
|
|
)
|
|
elif 'template' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(template__icontains=lookup_content)
|
|
)
|
|
elif 'template_id' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(template_id__icontains=lookup_content)
|
|
)
|
|
elif 'cve_id' in lookup_title or 'cve' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(cve_ids__name__icontains=lookup_content)
|
|
)
|
|
elif 'cwe_id' in lookup_title or 'cwe' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(cwe_ids__name__icontains=lookup_content)
|
|
)
|
|
elif 'cvss_metrics' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(cvss_metrics__icontains=lookup_content)
|
|
)
|
|
elif 'cvss_score' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(cvss_score__exact=lookup_content)
|
|
)
|
|
elif 'type' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(type__icontains=lookup_content)
|
|
)
|
|
elif 'tag' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(tags__name__icontains=lookup_content)
|
|
)
|
|
elif 'status' in lookup_title:
|
|
open_status = lookup_content == 'open'
|
|
qs = (
|
|
self.queryset
|
|
.filter(open_status=open_status)
|
|
)
|
|
elif 'description' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.filter(Q(description__icontains=lookup_content) |
|
|
Q(template__icontains=lookup_content) |
|
|
Q(extracted_results__icontains=lookup_content))
|
|
)
|
|
elif '!' in search_value:
|
|
search_param = search_value.split("!")
|
|
lookup_title = search_param[0].lower().strip()
|
|
lookup_content = search_param[1].lower().strip()
|
|
if 'severity' in lookup_title:
|
|
severity_value = NUCLEI_SEVERITY_MAP.get(lookup_title, -1)
|
|
qs = (
|
|
self.queryset
|
|
.exclude(severity=severity_value)
|
|
)
|
|
elif 'name' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(name__icontains=lookup_content)
|
|
)
|
|
elif 'http_url' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(http_url__icontains=lookup_content)
|
|
)
|
|
elif 'template' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(template__icontains=lookup_content)
|
|
)
|
|
elif 'template_id' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(template_id__icontains=lookup_content)
|
|
)
|
|
elif 'cve_id' in lookup_title or 'cve' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(cve_ids__icontains=lookup_content)
|
|
)
|
|
elif 'cwe_id' in lookup_title or 'cwe' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(cwe_ids__icontains=lookup_content)
|
|
)
|
|
elif 'cvss_metrics' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(cvss_metrics__icontains=lookup_content)
|
|
)
|
|
elif 'cvss_score' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(cvss_score__exact=lookup_content)
|
|
)
|
|
elif 'type' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(type__icontains=lookup_content)
|
|
)
|
|
elif 'tag' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(tags__icontains=lookup_content)
|
|
)
|
|
elif 'status' in lookup_title:
|
|
open_status = lookup_content == 'open'
|
|
qs = (
|
|
self.queryset
|
|
.exclude(open_status=open_status)
|
|
)
|
|
elif 'description' in lookup_title:
|
|
qs = (
|
|
self.queryset
|
|
.exclude(Q(description__icontains=lookup_content) |
|
|
Q(template__icontains=lookup_content) |
|
|
Q(extracted_results__icontains=lookup_content))
|
|
)
|
|
|
|
elif '>' in search_value:
|
|
search_param = search_value.split(">")
|
|
lookup_title = search_param[0].lower().strip()
|
|
lookup_content = search_param[1].lower().strip()
|
|
if 'cvss_score' in lookup_title:
|
|
try:
|
|
val = float(lookup_content)
|
|
qs = self.queryset.filter(cvss_score__gt=val)
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
elif '<' in search_value:
|
|
search_param = search_value.split("<")
|
|
lookup_title = search_param[0].lower().strip()
|
|
lookup_content = search_param[1].lower().strip()
|
|
if 'cvss_score' in lookup_title:
|
|
try:
|
|
val = int(lookup_content)
|
|
qs = self.queryset.filter(cvss_score__lt=val)
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
return qs
|