Exemple #1
0
def log_jira_generic_alert(title, description):
    create_notification(event='jira_update',
                        title=title,
                        description=description,
                        icon='bullseye',
                        source='JIRA')
Exemple #2
0
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})
Exemple #3
0
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('')
Exemple #4
0
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,
        })
Exemple #5
0
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)
Exemple #9
0
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,
        })
Exemple #10
0
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
Exemple #12
0
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()
Exemple #13
0
    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
Exemple #14
0
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
        })
Exemple #15
0
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
        })
Exemple #16
0
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, ))))