def log_jira_generic_alert(title, description): create_notification(event='jira_update', title=title, description=description, icon='bullseye', source='JIRA')
def express_new_jira(request): if request.method == 'POST': jform = ExpressJIRAForm(request.POST, instance=JIRA_Instance()) if jform.is_valid(): try: jira_server = jform.cleaned_data.get('url').rstrip('/') jira_username = jform.cleaned_data.get('username') jira_password = jform.cleaned_data.get('password') try: jira = jira_helper.get_jira_connection_raw( jira_server, jira_username, jira_password) except Exception as e: logger.exception(e) # already logged in jira_helper return render(request, 'dojo/express_new_jira.html', {'jform': jform}) # authentication successful # Get the open and close keys issue_id = jform.cleaned_data.get('issue_key') key_url = jira_server + '/rest/api/latest/issue/' + issue_id + '/transitions?expand=transitions.fields' data = json.loads( requests.get(key_url, auth=(jira_username, jira_password)).text) for node in data['transitions']: if node['to']['name'] == 'To Do': open_key = int(node['to']['id']) if node['to']['name'] == 'Done': close_key = int(node['to']['id']) # Get the epic id name key_url = jira_server + '/rest/api/2/field' data = json.loads( requests.get(key_url, auth=(jira_username, jira_password)).text) for node in data: if 'Epic Name' in node['clauseNames']: epic_name = int(node['clauseNames'][0][3:-1]) break jira_instance = JIRA_Instance( username=jira_username, password=jira_password, url=jira_server, configuration_name=jform.cleaned_data.get( 'configuration_name'), info_mapping_severity='Lowest', low_mapping_severity='Low', medium_mapping_severity='Medium', high_mapping_severity='High', critical_mapping_severity='Highest', epic_name_id=epic_name, open_status_key=open_key, close_status_key=close_key, finding_text='', default_issue_type=jform.cleaned_data.get( 'default_issue_type')) jira_instance.save() messages.add_message( request, messages.SUCCESS, 'JIRA Configuration Successfully Created.', extra_tags='alert-success') create_notification( event='other', title='New addition of JIRA: %s' % jform.cleaned_data.get('configuration_name'), description='JIRA "%s" was added by %s' % (jform.cleaned_data.get('configuration_name'), request.user), url=request.build_absolute_uri(reverse('jira')), ) return HttpResponseRedirect(reverse('jira', )) except: messages.add_message( request, messages.ERROR, 'Unable to query other required fields. They must be entered manually.', extra_tags='alert-danger') return HttpResponseRedirect(reverse('add_jira', )) return render(request, 'dojo/express_new_jira.html', {'jform': jform}) else: jform = ExpressJIRAForm() add_breadcrumb(title="New Jira Configuration (Express)", top_level=False, request=request) return render(request, 'dojo/express_new_jira.html', {'jform': jform})
def webhook(request, secret=None): if not get_system_setting('enable_jira'): logger.debug('ignoring incoming webhook as JIRA is disabled.') raise Http404('JIRA disabled') elif not get_system_setting('enable_jira_web_hook'): logger.debug('ignoring incoming webhook as JIRA Webhook is disabled.') raise Http404('JIRA Webhook disabled') elif not get_system_setting('disable_jira_webhook_secret'): if not get_system_setting('jira_webhook_secret'): logger.warning( 'ignoring incoming webhook as JIRA Webhook secret is empty in Defect Dojo system settings.' ) raise PermissionDenied('JIRA Webhook secret cannot be empty') if secret != get_system_setting('jira_webhook_secret'): logger.warning('invalid secret provided to JIRA Webhook') raise PermissionDenied( 'invalid or no secret provided to JIRA Webhook') # if webhook secret is disabled in system_settings, we ignore the incoming secret, even if it doesn't match # example json bodies at the end of this file if request.content_type != 'application/json': return HttpResponseBadRequest("only application/json supported") if request.method == 'POST': try: parsed = json.loads(request.body.decode('utf-8')) if parsed.get('webhookEvent') == 'jira:issue_updated': # xml examples at the end of file jid = parsed['issue']['id'] jissue = get_object_or_404(JIRA_Issue, jira_id=jid) if jissue.finding: finding = jissue.finding jira_instance = jira_helper.get_jira_instance(finding) resolved = True resolution = parsed['issue']['fields']['resolution'] # "resolution":{ # "self":"http://www.testjira.com/rest/api/2/resolution/11", # "id":"11", # "description":"Cancelled by the customer.", # "name":"Cancelled" # }, # or # "resolution": null if resolution is None: resolved = False if finding.active == resolved: if finding.active: if jira_instance and resolution[ 'name'] in jira_instance.accepted_resolutions: finding.active = False finding.mitigated = None finding.is_Mitigated = False finding.false_p = False assignee = parsed['issue']['fields'].get( 'assignee') assignee_name = assignee[ 'name'] if assignee else None Risk_Acceptance.objects.create( accepted_by=assignee_name, owner=finding.reporter, ).accepted_findings.set([finding]) elif jira_instance and resolution[ 'name'] in jira_instance.false_positive_resolutions: finding.active = False finding.verified = False finding.mitigated = None finding.is_Mitigated = False finding.false_p = True finding.remove_from_any_risk_acceptance() else: # Mitigated by default as before now = timezone.now() finding.active = False finding.mitigated = now finding.is_Mitigated = True finding.endpoints.clear() finding.false_p = False finding.remove_from_any_risk_acceptance() else: # Reopen / Open Jira issue finding.active = True finding.mitigated = None finding.is_Mitigated = False finding.false_p = False ra_helper.remove_finding.from_any_risk_acceptance( finding) else: # Reopen / Open Jira issue finding.active = True finding.mitigated = None finding.is_Mitigated = False finding.false_p = False ra_helper.remove_finding.from_any_risk_acceptance( finding) finding.jira_issue.jira_change = timezone.now() finding.jira_issue.save() finding.save() elif jissue.engagement: # if parsed['issue']['fields']['resolution'] != None: # eng.active = False # eng.status = 'Completed' # eng.save() return HttpResponse('Update for engagement ignored') else: raise Http404( 'No finding or engagement found for this JIRA issue') if parsed.get('webhookEvent') == 'comment_created': """ example incoming requests from JIRA Server 8.14.0 { "timestamp":1610269967824, "webhookEvent":"comment_created", "comment":{ "self":"https://jira.host.com/rest/api/2/issue/115254/comment/466578", "id":"466578", "author":{ "self":"https://jira.host.com/rest/api/2/user?username=defect.dojo", "name":"defect.dojo", "key":"defect.dojo", # seems to be only present on JIRA Server, not on Cloud "avatarUrls":{ "48x48":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=48", "24x24":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=24", "16x16":"https://www.gravatar.com/avatar9637bfb970eff6176357df615f548f1c?d=mm&s=16", "32x32":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=32" }, "displayName":"Defect Dojo", "active":true, "timeZone":"Europe/Amsterdam" }, "body":"(Valentijn Scholten):test4", "updateAuthor":{ "self":"https://jira.host.com/rest/api/2/user?username=defect.dojo", "name":"defect.dojo", "key":"defect.dojo", "avatarUrls":{ "48x48":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=48", "24x24""https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=24", "16x16":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=16", "32x32":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=32" }, "displayName":"Defect Dojo", "active":true, "timeZone":"Europe/Amsterdam" }, "created":"2021-01-10T10:12:47.824+0100", "updated":"2021-01-10T10:12:47.824+0100" } } """ comment_text = parsed['comment']['body'] commentor = parsed['comment']['updateAuthor']['name'] commentor_display_name = parsed['comment']['updateAuthor'][ 'displayName'] # example: body['comment']['self'] = "http://www.testjira.com/jira_under_a_path/rest/api/2/issue/666/comment/456843" jid = parsed['comment']['self'].split('/')[-3] jissue = get_object_or_404(JIRA_Issue, jira_id=jid) logger.debug('jissue: %s', vars(jissue)) if jissue.finding: # logger.debug('finding: %s', vars(jissue.finding)) jira_usernames = JIRA_Instance.objects.values_list( 'username', flat=True) for jira_userid in jira_usernames: # logger.debug('incoming username: %s jira config username: %s', commentor.lower(), jira_userid.lower()) if jira_userid.lower() == commentor.lower(): logger.debug( 'skipping incoming JIRA comment as the user id of the comment in JIRA (%s) matches the JIRA username in Defect Dojo (%s)', commentor.lower(), jira_userid.lower()) return HttpResponse('') break finding = jissue.finding new_note = Notes() new_note.entry = '(%s (%s)): %s' % ( commentor_display_name, commentor, comment_text) new_note.author, created = User.objects.get_or_create( username='******') new_note.save() finding.notes.add(new_note) finding.jira_issue.jira_change = timezone.now() finding.jira_issue.save() finding.save() create_notification( event='other', title='JIRA incoming comment - %s' % (jissue.finding), url=reverse("view_finding", args=(jissue.finding.id, )), icon='check') elif jissue.engagement: return HttpResponse('Comment for engagement ignored') else: raise Http404( 'No finding or engagement found for this JIRA issue') if parsed.get('webhookEvent') not in [ 'comment_created', 'jira:issue_updated' ]: logger.info( 'Unrecognized JIRA webhook event received: {}'.format( parsed.get('webhookEvent'))) except Exception as e: if isinstance(e, Http404): logger.warning('404 error processing JIRA webhook') else: logger.exception(e) try: logger.debug('jira_webhook_body_parsed:') logger.debug(json.dumps(parsed, indent=4)) except: logger.debug('jira_webhook_body:') logger.debug(request.body.decode('utf-8')) # reraise to make sure we don't silently swallow things raise return HttpResponse('')
def add_findings(request, tid): test = Test.objects.get(id=tid) form_error = False jform = None form = AddFindingForm(initial={'date': timezone.now().date()}, req_resp=None) push_all_jira_issues = jira_helper.is_push_all_issues(test) use_jira = jira_helper.get_jira_project(test) is not None if request.method == 'POST': form = AddFindingForm(request.POST, req_resp=None) if (form['active'].value() is False or form['verified'].value() is False) \ and 'jiraform-push_to_jira' in request.POST: error = ValidationError( 'Findings must be active and verified to be pushed to JIRA', code='not_active_or_verified') if form['active'].value() is False: form.add_error('active', error) if form['verified'].value() is False: form.add_error('verified', error) messages.add_message( request, messages.ERROR, 'Findings must be active and verified to be pushed to JIRA', extra_tags='alert-danger') if form['severity'].value( ) == 'Info' and 'jiraform-push_to_jira' in request.POST: error = ValidationError( 'Findings with Informational severity cannot be pushed to JIRA.', code='info-severity-to-jira') if (form['active'].value() is False or form['false_p'].value() ) and form['duplicate'].value() is False: closing_disabled = Note_Type.objects.filter( is_mandatory=True, is_active=True).count() if closing_disabled != 0: error_inactive = ValidationError( 'Can not set a finding as inactive without adding all mandatory notes', code='inactive_without_mandatory_notes') error_false_p = ValidationError( 'Can not set a finding as false positive without adding all mandatory notes', code='false_p_without_mandatory_notes') if form['active'].value() is False: form.add_error('active', error_inactive) if form['false_p'].value(): form.add_error('false_p', error_false_p) messages.add_message( request, messages.ERROR, 'Can not set a finding as inactive or false positive without adding all mandatory notes', extra_tags='alert-danger') if use_jira: jform = JIRAFindingForm( request.POST, prefix='jiraform', push_all=push_all_jira_issues, jira_project=jira_helper.get_jira_project(test)) if form.is_valid() and (jform is None or jform.is_valid()): if jform: logger.debug('jform.jira_issue: %s', jform.cleaned_data.get('jira_issue')) logger.debug('jform.push_to_jira: %s', jform.cleaned_data.get('push_to_jira')) new_finding = form.save(commit=False) new_finding.test = test new_finding.reporter = request.user new_finding.numerical_severity = Finding.get_numerical_severity( new_finding.severity) if new_finding.false_p or new_finding.active is False: new_finding.mitigated = timezone.now() new_finding.mitigated_by = request.user new_finding.is_Mitigated = True create_template = new_finding.is_template # always false now since this will be deprecated soon in favor of new Finding_Template model new_finding.is_template = False new_finding.save(dedupe_option=False, push_to_jira=False) for ep in form.cleaned_data['endpoints']: eps, created = Endpoint_Status.objects.get_or_create( finding=new_finding, endpoint=ep) ep.endpoint_status.add(eps) new_finding.endpoints.add(ep) new_finding.endpoint_status.add(eps) # Push to jira? push_to_jira = False jira_message = None if jform and jform.is_valid(): # can't use helper as when push_all_jira_issues is True, the checkbox gets disabled and is always false # push_to_jira = jira_helper.is_push_to_jira(new_finding, jform.cleaned_data.get('push_to_jira')) push_to_jira = push_all_jira_issues or jform.cleaned_data.get( 'push_to_jira') # if the jira issue key was changed, update database new_jira_issue_key = jform.cleaned_data.get('jira_issue') if new_finding.has_jira_issue: jira_issue = new_finding.jira_issue # everything in DD around JIRA integration is based on the internal id of the issue in JIRA # instead of on the public jira issue key. # I have no idea why, but it means we have to retrieve the issue from JIRA to get the internal JIRA id. # we can assume the issue exist, which is already checked in the validation of the jform if not new_jira_issue_key: jira_helper.finding_unlink_jira(request, new_finding) jira_message = 'Link to JIRA issue removed successfully.' elif new_jira_issue_key != new_finding.jira_issue.jira_key: jira_helper.finding_unlink_jira(request, new_finding) jira_helper.finding_link_jira(request, new_finding, new_jira_issue_key) jira_message = 'Changed JIRA link successfully.' else: logger.debug('finding has no jira issue yet') if new_jira_issue_key: logger.debug( 'finding has no jira issue yet, but jira issue specified in request. trying to link.' ) jira_helper.finding_link_jira(request, new_finding, new_jira_issue_key) jira_message = 'Linked a JIRA issue successfully.' new_finding.save(false_history=True, push_to_jira=push_to_jira) create_notification(event='other', title='Addition of %s' % new_finding.title, description='Finding "%s" was added by %s' % (new_finding.title, request.user), url=request.build_absolute_uri( reverse('view_finding', args=(new_finding.id, ))), icon="exclamation-triangle") if 'request' in form.cleaned_data or 'response' in form.cleaned_data: burp_rr = BurpRawRequestResponse( finding=new_finding, burpRequestBase64=base64.b64encode( form.cleaned_data['request'].encode()), burpResponseBase64=base64.b64encode( form.cleaned_data['response'].encode()), ) burp_rr.clean() burp_rr.save() if create_template: templates = Finding_Template.objects.filter( title=new_finding.title) if len(templates) > 0: messages.add_message( request, messages.ERROR, 'A finding template was not created. A template with this title already ' 'exists.', extra_tags='alert-danger') else: template = Finding_Template( title=new_finding.title, cwe=new_finding.cwe, severity=new_finding.severity, description=new_finding.description, mitigation=new_finding.mitigation, impact=new_finding.impact, references=new_finding.references, numerical_severity=new_finding.numerical_severity) template.save() messages.add_message( request, messages.SUCCESS, 'A finding template was also created.', extra_tags='alert-success') if '_Finished' in request.POST: return HttpResponseRedirect( reverse('view_test', args=(test.id, ))) else: return HttpResponseRedirect( reverse('add_findings', args=(test.id, ))) else: if 'endpoints' in form.cleaned_data: form.fields['endpoints'].queryset = form.cleaned_data[ 'endpoints'] else: form.fields['endpoints'].queryset = Endpoint.objects.none() form_error = True messages.add_message( request, messages.ERROR, 'The form has errors, please correct them below.', extra_tags='alert-danger') else: if use_jira: jform = JIRAFindingForm( push_all=jira_helper.is_push_all_issues(test), prefix='jiraform', jira_project=jira_helper.get_jira_project(test)) product_tab = Product_Tab(test.engagement.product.id, title="Add Finding", tab="engagements") product_tab.setEngagement(test.engagement) return render( request, 'dojo/add_findings.html', { 'form': form, 'product_tab': product_tab, 'test': test, 'temp': False, 'tid': tid, 'form_error': form_error, 'jform': jform, })
def re_import_scan_results(request, tid): additional_message = "When re-uploading a scan, any findings not found in original scan will be updated as " \ "mitigated. The process attempts to identify the differences, however manual verification " \ "is highly recommended." test = get_object_or_404(Test, id=tid) scan_type = test.test_type.name engagement = test.engagement form = ReImportScanForm() jform = None jira_project = jira_helper.get_jira_project(test) push_all_jira_issues = jira_helper.is_push_all_issues(test) # Decide if we need to present the Push to JIRA form if get_system_setting('enable_jira') and jira_project: jform = JIRAImportScanForm(push_all=push_all_jira_issues, prefix='jiraform') form.initial['tags'] = [tag.name for tag in test.tags] if request.method == "POST": form = ReImportScanForm(request.POST, request.FILES) if jira_project: jform = JIRAImportScanForm(request.POST, push_all=push_all_jira_issues, prefix='jiraform') if form.is_valid() and (jform is None or jform.is_valid()): scan_date = form.cleaned_data['scan_date'] scan_date_time = datetime.combine(scan_date, timezone.now().time()) if settings.USE_TZ: scan_date_time = timezone.make_aware( scan_date_time, timezone.get_default_timezone()) min_sev = form.cleaned_data['minimum_severity'] file = request.FILES.get('file', None) scan_type = test.test_type.name active = form.cleaned_data['active'] verified = form.cleaned_data['verified'] tags = request.POST.getlist('tags') ts = ", ".join(tags) test.tags = ts if file and is_scan_file_too_large(file): messages.add_message( request, messages.ERROR, "Report file is too large. Maximum supported size is {} MB" .format(settings.SCAN_FILE_MAX_SIZE), extra_tags='alert-danger') return HttpResponseRedirect( reverse('re_import_scan_results', args=(test.id, ))) try: parser = import_parser_factory(file, test, active, verified) except ValueError: raise Http404() except Exception as e: messages.add_message( request, messages.ERROR, "An error has occurred in the parser, please see error " "log for details.", extra_tags='alert-danger') parse_logger.exception(e) parse_logger.error("Error in parser: {}".format(str(e))) return HttpResponseRedirect( reverse('re_import_scan_results', args=(test.id, ))) try: items = parser.items original_items = test.finding_set.all().values_list("id", flat=True) new_items = [] mitigated_count = 0 finding_count = 0 finding_added_count = 0 reactivated_count = 0 # can't use helper as when push_all_jira_issues is True, the checkbox gets disabled and is always false # push_to_jira = jira_helper.is_push_to_jira(new_finding, jform.cleaned_data.get('push_to_jira')) push_to_jira = push_all_jira_issues or ( jform and jform.cleaned_data.get('push_to_jira')) for item in items: sev = item.severity if sev == 'Information' or sev == 'Informational': sev = 'Info' item.severity = sev # existing findings may be from before we had component_name/version fields component_name = item.component_name if hasattr( item, 'component_name') else None component_version = item.component_version if hasattr( item, 'component_version') else None # If it doesn't clear minimum severity, move on if Finding.SEVERITIES[sev] > Finding.SEVERITIES[min_sev]: continue # Try to find the existing finding # If it's Veracode or Arachni, then we consider the description for some # reason... from titlecase import titlecase item.title = titlecase(item.title) if scan_type == 'Veracode Scan' or scan_type == 'Arachni Scan': finding = Finding.objects.filter( title=item.title, test__id=test.id, severity=sev, numerical_severity=Finding.get_numerical_severity( sev), description=item.description) else: finding = Finding.objects.filter( title=item.title, test__id=test.id, severity=sev, numerical_severity=Finding.get_numerical_severity( sev)) if len(finding) == 1: finding = finding[0] if finding.mitigated or finding.is_Mitigated: # it was once fixed, but now back finding.mitigated = None finding.is_Mitigated = False finding.mitigated_by = None finding.active = True finding.verified = verified # existing findings may be from before we had component_name/version fields finding.component_name = finding.component_name if finding.component_name else component_name finding.component_version = finding.component_version if finding.component_version else component_version finding.save() note = Notes( entry="Re-activated by %s re-upload." % scan_type, author=request.user) note.save() finding.notes.add(note) endpoint_status = finding.endpoint_status.all() for status in endpoint_status: status.mitigated_by = None status.mitigated_time = None status.mitigated = False status.last_modified = timezone.now() status.save() reactivated_count += 1 else: # existing findings may be from before we had component_name/version fields if not finding.component_name or not finding.component_version: finding.component_name = finding.component_name if finding.component_name else component_name finding.component_version = finding.component_version if finding.component_version else component_version finding.save(dedupe_option=False, push_to_jira=False) new_items.append(finding.id) else: item.test = test item.reporter = request.user item.last_reviewed = timezone.now() item.last_reviewed_by = request.user item.verified = verified item.active = active # Save it item.save(dedupe_option=False) finding_added_count += 1 # Add it to the new items new_items.append(item.id) finding = item if hasattr(item, 'unsaved_req_resp') and len( item.unsaved_req_resp) > 0: for req_resp in item.unsaved_req_resp: if scan_type == "Arachni Scan": burp_rr = BurpRawRequestResponse( finding=item, burpRequestBase64=req_resp["req"], burpResponseBase64=req_resp["resp"], ) else: burp_rr = BurpRawRequestResponse( finding=item, burpRequestBase64=base64.b64encode( req_resp["req"].encode("utf-8")), burpResponseBase64=base64.b64encode( req_resp["resp"].encode("utf-8")), ) burp_rr.clean() burp_rr.save() if item.unsaved_request is not None and item.unsaved_response is not None: burp_rr = BurpRawRequestResponse( finding=finding, burpRequestBase64=base64.b64encode( item.unsaved_request.encode()), burpResponseBase64=base64.b64encode( item.unsaved_response.encode()), ) burp_rr.clean() burp_rr.save() if finding: finding_count += 1 for endpoint in item.unsaved_endpoints: ep, created = Endpoint.objects.get_or_create( protocol=endpoint.protocol, host=endpoint.host, path=endpoint.path, query=endpoint.query, fragment=endpoint.fragment, product=test.engagement.product) eps, created = Endpoint_Status.objects.get_or_create( finding=finding, endpoint=ep) ep.endpoint_status.add(eps) finding.endpoints.add(ep) finding.endpoint_status.add(eps) for endpoint in form.cleaned_data['endpoints']: ep, created = Endpoint.objects.get_or_create( protocol=endpoint.protocol, host=endpoint.host, path=endpoint.path, query=endpoint.query, fragment=endpoint.fragment, product=test.engagement.product) eps, created = Endpoint_Status.objects.get_or_create( finding=finding, endpoint=ep) ep.endpoint_status.add(eps) finding.endpoints.add(ep) finding.endpoint_status.add(eps) if item.unsaved_tags is not None: finding.tags = item.unsaved_tags # Save it. This may be the second time we save it in this function. finding.save(push_to_jira=push_to_jira) # calculate the difference to_mitigate = set(original_items) - set(new_items) for finding_id in to_mitigate: finding = Finding.objects.get(id=finding_id) if not finding.mitigated or not finding.is_Mitigated: finding.mitigated = scan_date_time finding.is_Mitigated = True finding.mitigated_by = request.user finding.active = False finding.save() note = Notes(entry="Mitigated by %s re-upload." % scan_type, author=request.user) note.save() finding.notes.add(note) mitigated_count += 1 endpoint_status = finding.endpoint_status.all() for status in endpoint_status: status.mitigated_by = request.user status.mitigated_time = timezone.now() status.mitigated = True status.last_modified = timezone.now() status.save() test.updated = max_safe([scan_date_time, test.updated]) test.engagement.updated = max_safe( [scan_date_time, test.engagement.updated]) test.save() test.engagement.save() messages.add_message( request, messages.SUCCESS, '%s processed, a total of ' % scan_type + message(finding_count, 'finding', 'processed'), extra_tags='alert-success') if finding_added_count > 0: messages.add_message( request, messages.SUCCESS, 'A total of ' + message(finding_added_count, 'finding', 'added') + ', that are new to scan.', extra_tags='alert-success') if reactivated_count > 0: messages.add_message( request, messages.SUCCESS, 'A total of ' + message(reactivated_count, 'finding', 'reactivated') + ', that are back in scan results.', extra_tags='alert-success') if mitigated_count > 0: messages.add_message( request, messages.SUCCESS, 'A total of ' + message(mitigated_count, 'finding', 'mitigated') + '. Please manually verify each one.', extra_tags='alert-success') create_notification(event='scan_added', title=str(finding_count) + " findings for " + test.engagement.product.name, finding_count=finding_count, test=test, engagement=test.engagement, url=reverse('view_test', args=(test.id, ))) return HttpResponseRedirect( reverse('view_test', args=(test.id, ))) except SyntaxError: messages.add_message( request, messages.ERROR, 'There appears to be an error in the XML report, please check and try again.', extra_tags='alert-danger') product_tab = Product_Tab(engagement.product.id, title="Re-upload a %s" % scan_type, tab="engagements") product_tab.setEngagement(engagement) form.fields['endpoints'].queryset = Endpoint.objects.filter( product__id=product_tab.product.id) return render( request, 'dojo/import_scan_results.html', { 'form': form, 'product_tab': product_tab, 'eid': engagement.id, 'additional_message': additional_message, 'jform': jform, })
def save(self, push_to_jira=False): data = self.validated_data close_old_findings = data['close_old_findings'] active = data['active'] verified = data['verified'] test_type, created = Test_Type.objects.get_or_create( name=data.get('test_type', data['scan_type'])) endpoint_to_add = data['endpoint_to_add'] environment, created = Development_Environment.objects.get_or_create( name='Development') scan_date = data['scan_date'] scan_date_time = datetime.datetime.combine(scan_date, timezone.now().time()) if settings.USE_TZ: scan_date_time = timezone.make_aware( scan_date_time, timezone.get_default_timezone()) version = '' if 'version' in data: version = data['version'] test = Test(engagement=data['engagement'], lead=data['lead'], test_type=test_type, target_start=data['scan_date'], target_end=data['scan_date'], environment=environment, percent_complete=100, version=version) try: test.full_clean() except ValidationError: pass test.save() # return the id of the created test, can't find a better way because this is not a ModelSerializer.... self.fields['test'] = serializers.IntegerField(read_only=True, default=test.id) test.engagement.updated = max_safe( [scan_date_time, test.engagement.updated]) if test.engagement.engagement_type == 'CI/CD': test.engagement.target_end = max_safe( [scan_date, test.engagement.target_end]) test.engagement.save() if 'tags' in data: test.tags = ' '.join(data['tags']) try: parser = import_parser_factory( data.get('file', None), test, active, verified, data['scan_type'], ) except ValueError: raise Exception('FileParser ValueError') new_findings = [] skipped_hashcodes = [] try: for item in parser.items: sev = item.severity if sev == 'Information' or sev == 'Informational': sev = 'Info' item.severity = sev if (Finding.SEVERITIES[sev] > Finding.SEVERITIES[data['minimum_severity']]): continue item.test = test item.date = test.target_start item.reporter = self.context['request'].user item.last_reviewed = timezone.now() item.last_reviewed_by = self.context['request'].user item.active = data['active'] item.verified = data['verified'] item.save(dedupe_option=False) if (hasattr(item, 'unsaved_req_resp') and len(item.unsaved_req_resp) > 0): for req_resp in item.unsaved_req_resp: burp_rr = BurpRawRequestResponse( finding=item, burpRequestBase64=req_resp["req"], burpResponseBase64=req_resp["resp"]) burp_rr.clean() burp_rr.save() if (item.unsaved_request is not None and item.unsaved_response is not None): burp_rr = BurpRawRequestResponse( finding=item, burpRequestBase64=item.unsaved_request, burpResponseBase64=item.unsaved_response) burp_rr.clean() burp_rr.save() for endpoint in item.unsaved_endpoints: ep, created = Endpoint.objects.get_or_create( protocol=endpoint.protocol, host=endpoint.host, path=endpoint.path, query=endpoint.query, fragment=endpoint.fragment, product=test.engagement.product) item.endpoints.add(ep) if endpoint_to_add: item.endpoints.add(endpoint_to_add) if item.unsaved_tags is not None: item.tags = item.unsaved_tags item.save(push_to_jira=push_to_jira) new_findings.append(item) except SyntaxError: raise Exception('Parser SyntaxError') old_findings = [] if close_old_findings: # Close old active findings that are not reported by this scan. new_hash_codes = test.finding_set.values('hash_code') if test.engagement.deduplication_on_engagement: old_findings = Finding.objects.exclude(test=test) \ .exclude(hash_code__in=new_hash_codes) \ .exclude(hash_code__in=skipped_hashcodes) \ .filter(test__engagement=test.engagement, test__test_type=test_type, active=True) else: old_findings = Finding.objects.exclude(test=test) \ .exclude(hash_code__in=new_hash_codes) \ .exclude(hash_code__in=skipped_hashcodes) \ .filter(test__engagement__product=test.engagement.product, test__test_type=test_type, active=True) for old_finding in old_findings: old_finding.active = False old_finding.mitigated = datetime.datetime.combine( test.target_start, timezone.now().time()) if settings.USE_TZ: old_finding.mitigated = timezone.make_aware( old_finding.mitigated, timezone.get_default_timezone()) old_finding.mitigated_by = self.context['request'].user old_finding.notes.create( author=self.context['request'].user, entry="This finding has been automatically closed" " as it is not present anymore in recent scans.") endpoint_status = old_finding.endpoint_status.all() for status in endpoint_status: status.mitigated_by = self.context['request'].user status.mitigated_time = timezone.now() status.mitigated = True status.last_modified = timezone.now() status.save() Tag.objects.add_tag(old_finding, 'stale') old_finding.save() title = 'Test created for ' + str( test.engagement.product) + ': ' + str( test.engagement.name) + ': ' + str(test) create_notification(event='test_added', title=title, test=test, engagement=test.engagement, product=test.engagement.product, url=reverse('view_test', args=(test.id, ))) updated_count = len(new_findings) + len(old_findings) if updated_count > 0: title = 'Created ' + str(updated_count) + " findings for " + str( test.engagement.product) + ': ' + str( test.engagement.name) + ': ' + str(test) create_notification(initiator=self.context['request'].user, event='scan_added', title=title, findings_new=new_findings, findings_mitigated=old_findings, finding_count=updated_count, test=test, engagement=test.engagement, product=test.engagement.product, url=reverse('view_test', args=(test.id, ))) return test
def save(self, push_to_jira=False): data = self.validated_data test = data['test'] scan_type = data['scan_type'] endpoint_to_add = data['endpoint_to_add'] min_sev = data['minimum_severity'] scan_date = data['scan_date'] scan_date_time = datetime.datetime.combine(scan_date, timezone.now().time()) if settings.USE_TZ: scan_date_time = timezone.make_aware( scan_date_time, timezone.get_default_timezone()) verified = data['verified'] active = data['active'] try: parser = import_parser_factory( data.get('file', None), test, active, verified, data['scan_type'], ) except ValueError: raise Exception("Parser ValueError") try: items = parser.items original_items = list(test.finding_set.all()) new_items = [] mitigated_count = 0 finding_count = 0 finding_added_count = 0 reactivated_count = 0 reactivated_items = [] unchanged_count = 0 unchanged_items = [] for item in items: sev = item.severity if sev == 'Information' or sev == 'Informational': sev = 'Info' if (Finding.SEVERITIES[sev] > Finding.SEVERITIES[min_sev]): continue if scan_type == 'Veracode Scan' or scan_type == 'Arachni Scan': findings = Finding.objects.filter( title=item.title, test=test, severity=sev, numerical_severity=Finding.get_numerical_severity(sev), description=item.description).all() else: findings = Finding.objects.filter( title=item.title, test=test, severity=sev, numerical_severity=Finding.get_numerical_severity( sev)).all() if findings: # existing finding found finding = findings[0] if finding.mitigated or finding.is_Mitigated: finding.mitigated = None finding.is_Mitigated = False finding.mitigated_by = None finding.active = True finding.verified = verified finding.save() note = Notes(entry="Re-activated by %s re-upload." % scan_type, author=self.context['request'].user) note.save() endpoint_status = finding.endpoint_status.all() for status in endpoint_status: status.mitigated_by = None status.mitigated_time = None status.mitigated = False status.last_modified = timezone.now() status.save() finding.notes.add(note) reactivated_items.append(finding) reactivated_count += 1 else: unchanged_items.append(finding) unchanged_count += 1 else: # no existing finding found item.test = test item.date = scan_date item.reporter = self.context['request'].user item.last_reviewed = timezone.now() item.last_reviewed_by = self.context['request'].user item.verified = verified item.active = active item.save(dedupe_option=False) finding_added_count += 1 new_items.append(item) finding = item if hasattr(item, 'unsaved_req_resp'): for req_resp in item.unsaved_req_resp: burp_rr = BurpRawRequestResponse( finding=finding, burpRequestBase64=req_resp['req'], burpResponseBase64=req_resp['resp']) burp_rr.clean() burp_rr.save() if item.unsaved_request and item.unsaved_response: burp_rr = BurpRawRequestResponse( finding=finding, burpRequestBase64=item.unsaved_request, burpResponseBase64=item.unsaved_response) burp_rr.clean() burp_rr.save() if finding: finding_count += 1 for endpoint in item.unsaved_endpoints: ep, created = Endpoint.objects.get_or_create( protocol=endpoint.protocol, host=endpoint.host, path=endpoint.path, query=endpoint.query, fragment=endpoint.fragment, product=test.engagement.product) finding.endpoints.add(ep) if endpoint_to_add: finding.endpoints.add(endpoint_to_add) if item.unsaved_tags: finding.tags = item.unsaved_tags finding.save(push_to_jira=push_to_jira) to_mitigate = set(original_items) - set(reactivated_items) - set( unchanged_items) mitigated_findings = [] for finding in to_mitigate: if not finding.mitigated or not finding.is_Mitigated: finding.mitigated = scan_date_time finding.is_Mitigated = True finding.mitigated_by = self.context['request'].user finding.active = False endpoint_status = finding.endpoint_status.all() for status in endpoint_status: status.mitigated_by = self.context['request'].user status.mitigated_time = timezone.now() status.mitigated = True status.last_modified = timezone.now() status.save() finding.save(push_to_jira=push_to_jira) note = Notes(entry="Mitigated by %s re-upload." % scan_type, author=self.context['request'].user) note.save() finding.notes.add(note) mitigated_findings.append(finding) mitigated_count += 1 untouched = set(unchanged_items) - set(to_mitigate) test.updated = max_safe([scan_date_time, test.updated]) test.engagement.updated = max_safe( [scan_date_time, test.engagement.updated]) if test.engagement.engagement_type == 'CI/CD': test.target_end = max_safe([scan_date_time, test.target_end]) test.engagement.target_end = max_safe( [scan_date, test.engagement.target_end]) test.save() test.engagement.save() print(len(new_items)) print(reactivated_count) print(mitigated_count) print(unchanged_count - mitigated_count) updated_count = mitigated_count + reactivated_count + len( new_items) if updated_count > 0: # new_items = original_items title = 'Updated ' + str( updated_count) + " findings for " + str( test.engagement.product) + ': ' + str( test.engagement.name) + ': ' + str(test) create_notification(initiator=self.context['request'].user, event='scan_added', title=title, findings_new=new_items, findings_mitigated=mitigated_findings, findings_reactivated=reactivated_items, finding_count=updated_count, test=test, engagement=test.engagement, product=test.engagement.product, findings_untouched=untouched, url=reverse('view_test', args=(test.id, ))) except SyntaxError: raise Exception("Parser SyntaxError") return test
def log_generic_alert(source, title, description): create_notification(event='other', title=title, description=description, icon='bullseye', source=source)
def edit_engagement(request, eid): engagement = Engagement.objects.get(pk=eid) is_ci_cd = engagement.engagement_type == "CI/CD" jira_project_form = None jira_epic_form = None jira_project = None jira_error = False if request.method == 'POST': form = EngForm(request.POST, instance=engagement, cicd=is_ci_cd, product=engagement.product, user=request.user) jira_project = jira_helper.get_jira_project(engagement, use_inheritance=False) if form.is_valid(): # first save engagement details new_status = form.cleaned_data.get('status') engagement = form.save(commit=False) if (new_status == "Cancelled" or new_status == "Completed"): engagement.active = False create_notification( event='close_engagement', title='Closure of %s' % engagement.name, description='The engagement "%s" was closed' % (engagement.name), engagement=engagement, url=reverse('engagement_all_findings', args=(engagement.id, ))), else: engagement.active = True engagement.save() form.save_m2m() messages.add_message(request, messages.SUCCESS, 'Engagement updated successfully.', extra_tags='alert-success') success, jira_project_form = jira_helper.process_jira_project_form( request, instance=jira_project, target='engagement', engagement=engagement, product=engagement.product) error = not success success, jira_epic_form = jira_helper.process_jira_epic_form( request, engagement=engagement) error = error or not success if not error: if '_Add Tests' in request.POST: return HttpResponseRedirect( reverse('add_tests', args=(engagement.id, ))) else: return HttpResponseRedirect( reverse('view_engagement', args=(engagement.id, ))) else: logger.debug(form.errors) else: form = EngForm(initial={'product': engagement.product}, instance=engagement, cicd=is_ci_cd, product=engagement.product, user=request.user) jira_epic_form = None if get_system_setting('enable_jira'): jira_project = jira_helper.get_jira_project(engagement, use_inheritance=False) jira_project_form = JIRAProjectForm(instance=jira_project, target='engagement', product=engagement.product) logger.debug('showing jira-epic-form') jira_epic_form = JIRAEngagementForm(instance=engagement) if is_ci_cd: title = 'Edit CI/CD Engagement' else: title = 'Edit Interactive Engagement' product_tab = Product_Tab(engagement.product.id, title=title, tab="engagements") product_tab.setEngagement(engagement) return render( request, 'dojo/new_eng.html', { 'product_tab': product_tab, 'title': title, 'form': form, 'edit': True, 'jira_epic_form': jira_epic_form, 'jira_project_form': jira_project_form, 'engagement': engagement, })
def express_new_jira(request): if request.method == 'POST': jform = ExpressJIRAForm(request.POST, instance=JIRA_Instance()) if jform.is_valid(): jira_server = jform.cleaned_data.get('url').rstrip('/') jira_username = jform.cleaned_data.get('username') jira_password = jform.cleaned_data.get('password') try: jira = jira_helper.get_jira_connection_raw( jira_server, jira_username, jira_password) except Exception as e: logger.exception(e) # already logged in jira_helper messages.add_message( request, messages.ERROR, 'Unable to authenticate. Please check credentials.', extra_tags='alert-danger') return render(request, 'dojo/express_new_jira.html', {'jform': jform}) # authentication successful # Get the open and close keys try: issue_id = jform.cleaned_data.get('issue_key') key_url = jira_server.strip( '/' ) + '/rest/api/latest/issue/' + issue_id + '/transitions?expand=transitions.fields' response = jira._session.get(key_url).json() open_key = close_key = None for node in response['transitions']: if node['to']['statusCategory']['name'] == 'To Do': open_key = int( node['id']) if not open_key else open_key if node['to']['statusCategory']['name'] == 'Done': close_key = int( node['id']) if not close_key else close_key except Exception as e: logger.exception(e) # already logged in jira_helper messages.add_message( request, messages.ERROR, 'Unable to find Open/Close ID\'s. They will need to be found manually', extra_tags='alert-danger') return render(request, 'dojo/new_jira.html', {'jform': jform}) # Get the epic id name try: epic_name = get_custom_field(jira, 'Epic Name') except Exception as e: logger.exception(e) # already logged in jira_helper messages.add_message( request, messages.ERROR, 'Unable to find Epic Name. It will need to be found manually', extra_tags='alert-danger') return render(request, 'dojo/new_jira.html', {'jform': jform}) jira_instance = JIRA_Instance( username=jira_username, password=jira_password, url=jira_server, configuration_name=jform.cleaned_data.get( 'configuration_name'), info_mapping_severity='Lowest', low_mapping_severity='Low', medium_mapping_severity='Medium', high_mapping_severity='High', critical_mapping_severity='Highest', epic_name_id=epic_name, open_status_key=open_key, close_status_key=close_key, finding_text='', default_issue_type=jform.cleaned_data.get( 'default_issue_type')) jira_instance.save() messages.add_message(request, messages.SUCCESS, 'JIRA Configuration Successfully Created.', extra_tags='alert-success') create_notification( event='other', title='New addition of JIRA: %s' % jform.cleaned_data.get('configuration_name'), description='JIRA "%s" was added by %s' % (jform.cleaned_data.get('configuration_name'), request.user), url=request.build_absolute_uri(reverse('jira')), ) return HttpResponseRedirect(reverse('jira', )) else: jform = ExpressJIRAForm() add_breadcrumb(title="New Jira Configuration (Express)", top_level=False, request=request) return render(request, 'dojo/express_new_jira.html', {'jform': jform})
def async_custom_pdf_report(self, report=None, template="None", filename='report.pdf', host=None, user=None, uri=None, finding_notes=False, finding_images=False): config = pdfkit.configuration(wkhtmltopdf=settings.WKHTMLTOPDF_PATH) selected_widgets = report_widget_factory(json_data=report.options, request=None, user=user, finding_notes=finding_notes, finding_images=finding_images, host=host) widgets = list(selected_widgets.values()) temp = None try: report.task_id = async_custom_pdf_report.request.id report.save() toc = None toc_depth = 4 if 'table-of-contents' in selected_widgets: xsl_style_sheet_tempalte = "dojo/pdf_toc.xsl" temp = tempfile.NamedTemporaryFile() toc_settings = selected_widgets['table-of-contents'] toc_depth = toc_settings.depth toc_bytes = render_to_string(xsl_style_sheet_tempalte, {'widgets': widgets, 'depth': toc_depth, 'title': toc_settings.title}) temp.write(toc_bytes) temp.seek(0) toc = {'toc-header-text': toc_settings.title, 'xsl-style-sheet': temp.name} # default the cover to not come first by default cover_first_val = False cover = None if 'cover-page' in selected_widgets: cover_first_val = True cp = selected_widgets['cover-page'] x = urlencode({'title': cp.title, 'subtitle': cp.sub_heading, 'info': cp.meta_info}) cover = host + reverse( 'report_cover_page') + "?" + x bytes = render_to_string(template, {'widgets': widgets, 'toc_depth': toc_depth, 'host': host, 'report_name': report.name}) pdf = pdfkit.from_string(bytes, False, configuration=config, toc=toc, cover=cover, cover_first=cover_first_val) if report.file.name: with open(report.file.path, 'w') as f: f.write(pdf) f.close() else: f = ContentFile(pdf) report.file.save(filename, f) report.status = 'success' report.done_datetime = timezone.now() report.save() create_notification(event='report_created', title='Report created', description='The report "%s" is ready.' % report.name, url=uri, report=report, objowner=report.requester) except Exception as e: report.status = 'error' report.save() # email_requester(report, uri, error=e) # raise e log_generic_alert("PDF Report", "Report Creation Failure", "Make sure WKHTMLTOPDF is installed. " + str(e)) finally: if temp is not None: # deleting temp xsl file temp.close() return True
def expiration_handler(*args, **kwargs): """ Creates a notification upon risk expiration and X days beforehand if configured. This notification is 1 per risk acceptance. If configured also sends a JIRA comment in both case to each jira issue. This is per finding. """ try: system_settings = System_Settings.objects.get() except System_Settings.DoesNotExist: logger.warn( "Unable to get system_settings, skipping risk acceptance expiration job" ) risk_acceptances = get_expired_risk_acceptances_to_handle() logger.info('expiring %i risk acceptances that are past expiration date', len(risk_acceptances)) for risk_acceptance in risk_acceptances: expire_now(risk_acceptance) # notification created by expire_now code heads_up_days = system_settings.risk_acceptance_notify_before_expiration if heads_up_days > 0: risk_acceptances = get_almost_expired_risk_acceptances_to_handle( heads_up_days) logger.info( 'notifying for %i risk acceptances that are expiring within %i days', len(risk_acceptances), heads_up_days) for risk_acceptance in risk_acceptances: logger.debug( 'notifying for risk acceptance %i:%s with %i findings', risk_acceptance.id, risk_acceptance, len(risk_acceptance.accepted_findings.all())) notification_title = 'Risk acceptance with ' + str(len(risk_acceptance.accepted_findings.all())) + " accepted findings will expire on " + \ timezone.localtime(risk_acceptance.expiration_date).strftime("%b %d, %Y") + " for " + \ str(risk_acceptance.engagement.product) + ': ' + str(risk_acceptance.engagement.name) create_notification( event='risk_acceptance_expiration', title=notification_title, risk_acceptance=risk_acceptance, accepted_findings=risk_acceptance.accepted_findings.all(), engagement=risk_acceptance.engagement, product=risk_acceptance.engagement.product, url=reverse('view_risk_acceptance', args=( risk_acceptance.engagement.id, risk_acceptance.id, ))) post_jira_comments(risk_acceptance, expiration_warning_message_creator, heads_up_days) risk_acceptance.expiration_date_warned = timezone.now() risk_acceptance.save()
def import_issues(self, test): items = list() try: product = test.engagement.product config = product.sonarqube_product_set.all().first() client = SonarQubeAPI( tool_config=config.sonarqube_tool_config if config else None) if config and config.sonarqube_project_key: component = client.get_project(config.sonarqube_project_key) else: component = client.find_project(product.name) issues = client.find_issues(component['key']) logging.info('Found {} issues for component {}'.format( len(issues), component["key"])) for issue in issues: status = issue['status'] from_hotspot = issue.get('fromHotspot', False) if self.is_closed(status) or from_hotspot: continue type = issue['type'] if len(issue['message']) > 511: title = issue['message'][0:507] + "..." else: title = issue['message'] component_key = issue['component'] line = issue.get('line') rule_id = issue['rule'] rule = client.get_rule(rule_id) severity = self.convert_sonar_severity(rule['severity']) description = self.clean_rule_description_html( rule['htmlDesc']) cwe = self.clean_cwe(rule['htmlDesc']) references = self.get_references(rule['htmlDesc']) sonarqube_issue, _ = Sonarqube_Issue.objects.update_or_create( key=issue['key'], defaults={ 'status': status, 'type': type, }) # Only assign the SonarQube_issue to the first finding related to the issue if Finding.objects.filter( sonarqube_issue=sonarqube_issue).exists(): sonarqube_issue = None find = Finding( title=title, cwe=cwe, description=description, test=test, severity=severity, references=references, file_path=component_key, line=line, verified=self.is_confirmed(status), false_p=False, duplicate=False, out_of_scope=False, mitigated=None, mitigation='No mitigation provided', impact="No impact provided", static_finding=True, sonarqube_issue=sonarqube_issue, ) items.append(find) except Exception as e: logger.exception(e) create_notification(event='other', title='SonarQube API import issue', description=e, icon='exclamation-triangle', source='SonarQube API') return items
def import_scan_results(request, eid=None, pid=None): engagement = None form = ImportScanForm() cred_form = CredMappingForm() finding_count = 0 jform = None user = request.user if eid: engagement = get_object_or_404(Engagement, id=eid) engagement_or_product = engagement cred_form.fields["cred_user"].queryset = Cred_Mapping.objects.filter( engagement=engagement).order_by('cred_id') elif pid: product = get_object_or_404(Product, id=pid) engagement_or_product = product elif not user.is_staff: raise PermissionDenied if not user_is_authorized(user, 'staff', engagement_or_product): raise PermissionDenied push_all_jira_issues = jira_helper.is_push_all_issues( engagement_or_product) if request.method == "POST": form = ImportScanForm(request.POST, request.FILES) cred_form = CredMappingForm(request.POST) cred_form.fields["cred_user"].queryset = Cred_Mapping.objects.filter( engagement=engagement).order_by('cred_id') if jira_helper.get_jira_project(engagement_or_product): jform = JIRAImportScanForm(request.POST, push_all=push_all_jira_issues, prefix='jiraform') logger.debug('jform valid: %s', jform.is_valid()) logger.debug('jform errors: %s', jform.errors) if form.is_valid() and (jform is None or jform.is_valid()): # Allows for a test to be imported with an engagement created on the fly if engagement is None: engagement = Engagement() # product = get_object_or_404(Product, id=pid) engagement.name = "AdHoc Import - " + strftime( "%a, %d %b %Y %X", timezone.now().timetuple()) engagement.threat_model = False engagement.api_test = False engagement.pen_test = False engagement.check_list = False engagement.target_start = timezone.now().date() engagement.target_end = timezone.now().date() engagement.product = product engagement.active = True engagement.status = 'In Progress' engagement.save() file = request.FILES.get('file', None) scan_date = form.cleaned_data['scan_date'] min_sev = form.cleaned_data['minimum_severity'] active = form.cleaned_data['active'] verified = form.cleaned_data['verified'] scan_type = request.POST['scan_type'] if not any(scan_type in code for code in ImportScanForm.SCAN_TYPE_CHOICES): raise Http404() if file and is_scan_file_too_large(file): messages.add_message( request, messages.ERROR, "Report file is too large. Maximum supported size is {} MB" .format(settings.SCAN_FILE_MAX_SIZE), extra_tags='alert-danger') return HttpResponseRedirect( reverse('import_scan_results', args=(engagement, ))) tt, t_created = Test_Type.objects.get_or_create(name=scan_type) # will save in development environment environment, env_created = Development_Environment.objects.get_or_create( name="Development") t = Test(engagement=engagement, test_type=tt, target_start=scan_date, target_end=scan_date, environment=environment, percent_complete=100) t.lead = user t.full_clean() t.save() tags = request.POST.getlist('tags') ts = ", ".join(tags) t.tags = ts # Save the credential to the test if cred_form.is_valid(): if cred_form.cleaned_data['cred_user']: # Select the credential mapping object from the selected list and only allow if the credential is associated with the product cred_user = Cred_Mapping.objects.filter( pk=cred_form.cleaned_data['cred_user'].id, engagement=eid).first() new_f = cred_form.save(commit=False) new_f.test = t new_f.cred_id = cred_user.cred_id new_f.save() try: parser = import_parser_factory(file, t, active, verified) except Exception as e: messages.add_message( request, messages.ERROR, "An error has occurred in the parser, please see error " "log for details.", extra_tags='alert-danger') parse_logger.exception(e) parse_logger.error("Error in parser: {}".format(str(e))) return HttpResponseRedirect( reverse('import_scan_results', args=(engagement.id, ))) try: # can't use helper as when push_all_jira_issues is True, the checkbox gets disabled and is always false # push_to_jira = jira_helper.is_push_to_jira(new_finding, jform.cleaned_data.get('push_to_jira')) push_to_jira = push_all_jira_issues or ( jform and jform.cleaned_data.get('push_to_jira')) for item in parser.items: # print("item blowup") # print(item) sev = item.severity if sev == 'Information' or sev == 'Informational': sev = 'Info' item.severity = sev if Finding.SEVERITIES[sev] > Finding.SEVERITIES[min_sev]: continue item.test = t item.reporter = user item.last_reviewed = timezone.now() item.last_reviewed_by = user if not handles_active_verified_statuses( form.get_scan_type()): item.active = active item.verified = verified item.save(dedupe_option=False, false_history=True) if hasattr(item, 'unsaved_req_resp') and len( item.unsaved_req_resp) > 0: for req_resp in item.unsaved_req_resp: if form.get_scan_type() == "Arachni Scan": burp_rr = BurpRawRequestResponse( finding=item, burpRequestBase64=req_resp["req"], burpResponseBase64=req_resp["resp"], ) else: burp_rr = BurpRawRequestResponse( finding=item, burpRequestBase64=base64.b64encode( req_resp["req"].encode("utf-8")), burpResponseBase64=base64.b64encode( req_resp["resp"].encode("utf-8")), ) burp_rr.clean() burp_rr.save() if item.unsaved_request is not None and item.unsaved_response is not None: burp_rr = BurpRawRequestResponse( finding=item, burpRequestBase64=base64.b64encode( item.unsaved_request.encode()), burpResponseBase64=base64.b64encode( item.unsaved_response.encode()), ) burp_rr.clean() burp_rr.save() for endpoint in item.unsaved_endpoints: ep, created = Endpoint.objects.get_or_create( protocol=endpoint.protocol, host=endpoint.host, path=endpoint.path, query=endpoint.query, fragment=endpoint.fragment, product=t.engagement.product) eps, created = Endpoint_Status.objects.get_or_create( finding=item, endpoint=ep) ep.endpoint_status.add(eps) item.endpoints.add(ep) item.endpoint_status.add(eps) for endpoint in form.cleaned_data['endpoints']: ep, created = Endpoint.objects.get_or_create( protocol=endpoint.protocol, host=endpoint.host, path=endpoint.path, query=endpoint.query, fragment=endpoint.fragment, product=t.engagement.product) eps, created = Endpoint_Status.objects.get_or_create( finding=item, endpoint=ep) ep.endpoint_status.add(eps) item.endpoints.add(ep) item.endpoint_status.add(eps) item.save(false_history=True, push_to_jira=push_to_jira) if item.unsaved_tags is not None: item.tags = item.unsaved_tags finding_count += 1 messages.add_message( request, messages.SUCCESS, scan_type + ' processed, a total of ' + message(finding_count, 'finding', 'processed'), extra_tags='alert-success') create_notification(initiator=user, event='scan_added', title=str(finding_count) + " findings for " + engagement.product.name, finding_count=finding_count, test=t, engagement=engagement, url=reverse('view_test', args=(t.id, ))) return HttpResponseRedirect(reverse('view_test', args=(t.id, ))) except SyntaxError: messages.add_message( request, messages.ERROR, 'There appears to be an error in the XML report, please check and try again.', extra_tags='alert-danger') prod_id = None custom_breadcrumb = None title = "Import Scan Results" if engagement: prod_id = engagement.product.id product_tab = Product_Tab(prod_id, title=title, tab="engagements") product_tab.setEngagement(engagement) else: prod_id = pid custom_breadcrumb = {"", ""} product_tab = Product_Tab(prod_id, title=title, tab="findings") if jira_helper.get_jira_project(engagement_or_product): jform = JIRAImportScanForm(push_all=push_all_jira_issues, prefix='jiraform') form.fields['endpoints'].queryset = Endpoint.objects.filter( product__id=product_tab.product.id) return render( request, 'dojo/import_scan_results.html', { 'form': form, 'product_tab': product_tab, 'engagement_or_product': engagement_or_product, 'custom_breadcrumb': custom_breadcrumb, 'title': title, 'cred_form': cred_form, 'jform': jform })
def add_tests(request, eid): eng = Engagement.objects.get(id=eid) cred_form = CredMappingForm() cred_form.fields["cred_user"].queryset = Cred_Mapping.objects.filter( engagement=eng).order_by('cred_id') if request.method == 'POST': form = TestForm(request.POST, engagement=eng) cred_form = CredMappingForm(request.POST) cred_form.fields["cred_user"].queryset = Cred_Mapping.objects.filter( engagement=eng).order_by('cred_id') if form.is_valid(): new_test = form.save(commit=False) new_test.engagement = eng try: new_test.lead = User.objects.get(id=form['lead'].value()) except: new_test.lead = None pass # Set status to in progress if a test is added if eng.status != "In Progress" and eng.active is True: eng.status = "In Progress" eng.save() new_test.save() tags = request.POST.getlist('tags') t = ", ".join('"{0}"'.format(w) for w in tags) new_test.tags = t # Save the credential to the test if cred_form.is_valid(): if cred_form.cleaned_data['cred_user']: # Select the credential mapping object from the selected list and only allow if the credential is associated with the product cred_user = Cred_Mapping.objects.filter( pk=cred_form.cleaned_data['cred_user'].id, engagement=eid).first() new_f = cred_form.save(commit=False) new_f.test = new_test new_f.cred_id = cred_user.cred_id new_f.save() messages.add_message(request, messages.SUCCESS, 'Test added successfully.', extra_tags='alert-success') create_notification( event='test_added', title=new_test.test_type.name + " for " + eng.product.name, test=new_test, engagement=eng, url=reverse('view_engagement', args=(eng.id, ))) if '_Add Another Test' in request.POST: return HttpResponseRedirect( reverse('add_tests', args=(eng.id, ))) elif '_Add Findings' in request.POST: return HttpResponseRedirect( reverse('add_findings', args=(new_test.id, ))) elif '_Finished' in request.POST: return HttpResponseRedirect( reverse('view_engagement', args=(eng.id, ))) else: form = TestForm(engagement=eng) form.initial['target_start'] = eng.target_start form.initial['target_end'] = eng.target_end form.initial['lead'] = request.user add_breadcrumb(parent=eng, title="Add Tests", top_level=False, request=request) product_tab = Product_Tab(eng.product.id, title="Add Tests", tab="engagements") product_tab.setEngagement(eng) return render( request, 'dojo/add_tests.html', { 'product_tab': product_tab, 'form': form, 'cred_form': cred_form, 'eid': eid, 'eng': eng })
def import_object_eng(request, engagement, json_data): create_test_code_review = False create_alert = False # Get the product from the engagement product = engagement.product # Retrieve the files currently set for this product object_queryset = Objects_Product.objects.filter( product=engagement.product.id).order_by('-path') tree = json_data.read() try: data = json.loads(str(tree, 'utf-8')) except: data = json.loads(tree) # Set default review status review_status_id = 1 review_status = Objects_Review.objects.get(pk=review_status_id) for file in data: # print(file["path"]) # Save the file if the object isn't in object table file_type, found_object = find_item(file["path"], object_queryset) if found_object is None or file_type == "path": review_status_id = 1 if file_type == "path": # Copy the review status review_status_id = found_object.review_status.id # Set default review status review_status = Objects_Review.objects.get(pk=review_status_id) # if found_object is None: object = Objects_Product(product=product, path=file["path"], review_status=review_status) object.save() found_object = object if file_type == "path": for tag in found_object.tags.all(): object.tags.add(tag) full_url = None file_type = None percentUnchanged = None build_id = None if "full_url" in file: full_url = file["full_url"] if "type" in file: file_type = file["type"] if "percentUnchanged" in file: percentUnchanged = file["percentUnchanged"] if "build_id" in file: build_id = file["build_id"][:12] # Find the status so the appropriate action takes place if found_object.review_status.id == 2: create_alert = True elif found_object.review_status.id == 3: create_test_code_review = True create_alert = True # Save the changed files to the engagement view object_eng = Objects_Engagement(engagement=engagement, object_id=found_object, full_url=full_url, type=file_type, percentUnchanged=percentUnchanged, build_id=build_id) object_eng.save() # Create the notification if create_alert: create_notification( event='code_review', title='Manual Code Review Requested', description= "Manual code review requested as tracked file changes were found in the latest build.", engagement=engagement, url=reverse('view_object_eng', args=(engagement.id, ))) # Create the test within the engagement if create_test_code_review: environment, env_created = Development_Environment.objects.get_or_create( name="Development") tt = Test_Type.objects.get(pk=27) # Manual code review if tt: test = Test(engagement=engagement, test_type=tt, target_start=timezone.now(), target_end=timezone.now() + timezone.timedelta(days=1), environment=environment, percent_complete=0) test.save() create_notification(event='test_added', title='Test added for Manual Code Review', test=test, engagement=engagement, url=request.build_absolute_uri( reverse('view_engagement', args=(engagement.id, ))))