Skip to content

Commit

Permalink
Merge branch 'dev' into add_ruffS112S311
Browse files Browse the repository at this point in the history
  • Loading branch information
manuel-sommer authored Nov 1, 2024
2 parents 7ee0526 + ab2b88e commit dffcbc6
Show file tree
Hide file tree
Showing 42 changed files with 1,844 additions and 133 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/support_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ assignees: ''

---
**Slack us first!**
The easiest and fastest way to help you is via Slack. There's a free and easy signup to join our #defectdojo channel in the OWASP Slack workspace: [Get Access.](https://owasp-slack.herokuapp.com/)
The easiest and fastest way to help you is via Slack. There's a free and easy signup to join our #defectdojo channel in the OWASP Slack workspace: [Get Access.](https://owasp.org/slack/invite)
If you're confident you've found a bug, or are allergic to Slack, you can submit an issue anyway.

**Be informative**
Expand Down
2 changes: 1 addition & 1 deletion components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"google-code-prettify": "^1.0.0",
"jquery": "^3.7.1",
"jquery-highlight": "3.5.0",
"jquery-ui": "1.14.0",
"jquery-ui": "1.14.1",
"jquery.cookie": "1.4.1",
"jquery.flot.tooltip": "^0.9.0",
"jquery.hotkeys": "jeresig/jquery.hotkeys#master",
Expand Down
8 changes: 4 additions & 4 deletions components/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -678,10 +678,10 @@ [email protected]:
dependencies:
jquery ">= 1.0.0"

[email protected].0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.14.0.tgz#b75d417826f0bab38125f907356d2e3313a9c6d5"
integrity sha512-mPfYKBoRCf0MzaT2cyW5i3IuZ7PfTITaasO5OFLAQxrHuI+ZxruPa+4/K1OMNT8oElLWGtIxc9aRbyw20BKr8g==
[email protected].1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.14.1.tgz#ba342ea3ffff662b787595391f607d923313e040"
integrity sha512-DhzsYH8VeIvOaxwi+B/2BCsFFT5EGjShdzOcm5DssWjtcpGWIMsn66rJciDA6jBruzNiLf1q0KvwMoX1uGNvnQ==
dependencies:
jquery ">=1.12.0 <5.0.0"

Expand Down
14 changes: 14 additions & 0 deletions docs/content/en/integrations/parsers/file/ptart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
title: "PTART Reports"
toc_hide: true
---

### What is PTART?
PTART is a Pentest and Security Auditing Reporting Tool developed by the Michelin CERT (https://github.com/certmichelin/PTART)

### Importing Reports
Reports can be exported to JSON format from the PTART web UI, and imported into DefectDojo by using the "PTART Report" importer.

### Sample Scan Data
Sample scan data for testing purposes can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/ptart).

67 changes: 46 additions & 21 deletions dojo/api_v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2064,7 +2064,6 @@ class CommonImportScanSerializer(serializers.Serializer):
help_text="Override the verified setting from the tool.",
)

scan_type = serializers.ChoiceField(choices=get_choices_sorted())
# TODO: why do we allow only existing endpoints?
endpoint_to_add = serializers.PrimaryKeyRelatedField(
queryset=Endpoint.objects.all(),
Expand Down Expand Up @@ -2092,26 +2091,8 @@ class CommonImportScanSerializer(serializers.Serializer):
lead = serializers.PrimaryKeyRelatedField(
allow_null=True, default=None, queryset=User.objects.all(),
)
tags = TagListSerializerField(
required=False, allow_empty=True, help_text="Add tags that help describe this scan.",
)
close_old_findings = serializers.BooleanField(
required=False,
default=False,
help_text="Select if old findings no longer present in the report get closed as mitigated when importing. "
"If service has been set, only the findings for this service will be closed.",
)
close_old_findings_product_scope = serializers.BooleanField(
required=False,
default=False,
help_text="Select if close_old_findings applies to all findings of the same type in the product. "
"By default, it is false meaning that only old findings of the same type in the engagement are in scope.",
)
push_to_jira = serializers.BooleanField(default=False)
environment = serializers.CharField(required=False)
version = serializers.CharField(
required=False, help_text="Version that was scanned.",
)
build_id = serializers.CharField(
required=False, help_text="ID of the build that was scanned.",
)
Expand Down Expand Up @@ -2280,11 +2261,28 @@ def setup_common_context(self, data: dict) -> dict:


class ImportScanSerializer(CommonImportScanSerializer):

scan_type = serializers.ChoiceField(choices=get_choices_sorted())
engagement = serializers.PrimaryKeyRelatedField(
queryset=Engagement.objects.all(), required=False,
)

tags = TagListSerializerField(
required=False, allow_empty=True, help_text="Add tags that help describe this scan.",
)
close_old_findings = serializers.BooleanField(
required=False,
default=False,
help_text="Select if old findings no longer present in the report get closed as mitigated when importing. "
"If service has been set, only the findings for this service will be closed.",
)
close_old_findings_product_scope = serializers.BooleanField(
required=False,
default=False,
help_text="Select if close_old_findings applies to all findings of the same type in the product. "
"By default, it is false meaning that only old findings of the same type in the engagement are in scope.",
)
version = serializers.CharField(
required=False, help_text="Version that was scanned.",
)
# extra fields populated in response
# need to use the _id suffix as without the serializer framework gets
# confused
Expand Down Expand Up @@ -2340,9 +2338,36 @@ class ReImportScanSerializer(TaggitSerializer, CommonImportScanSerializer):
do_not_reactivate = serializers.BooleanField(
default=False, required=False, help_text=help_do_not_reactivate,
)
scan_type = serializers.ChoiceField(
choices=get_choices_sorted(), required=True,
)
test = serializers.PrimaryKeyRelatedField(
required=False, queryset=Test.objects.all(),
)
# Close the old findings if the parameter is not provided. This is to
# maintain the old API behavior after reintroducing the close_old_findings parameter
# also for ReImport.
close_old_findings = serializers.BooleanField(
required=False,
default=True,
help_text="Select if old findings no longer present in the report get closed as mitigated when importing.",
)
close_old_findings_product_scope = serializers.BooleanField(
required=False,
default=False,
help_text="Select if close_old_findings applies to all findings of the same type in the product. "
"By default, it is false meaning that only old findings of the same type in the engagement are in scope. "
"Note that this only applies on the first call to reimport-scan.",
)
version = serializers.CharField(
required=False,
help_text="Version that will be set on existing Test object. Leave empty to leave existing value in place.",
)
tags = TagListSerializerField(
required=False,
allow_empty=True,
help_text="Modify existing tags that help describe this scan. (Existing test tags will be overwritten)",
)

def set_context(
self,
Expand Down
5 changes: 3 additions & 2 deletions dojo/engagement/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from django.db import DEFAULT_DB_ALIAS
from django.db.models import Count, Q
from django.db.models.query import Prefetch, QuerySet
from django.http import FileResponse, HttpRequest, HttpResponse, HttpResponseRedirect, QueryDict, StreamingHttpResponse
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, QueryDict, StreamingHttpResponse
from django.shortcuts import get_object_or_404, render
from django.urls import Resolver404, reverse
from django.utils import timezone
Expand Down Expand Up @@ -99,6 +99,7 @@
add_success_message_to_response,
async_delete,
calculate_grade,
generate_file_response_from_file_path,
get_cal_event,
get_page_items,
get_return_url,
Expand Down Expand Up @@ -1515,7 +1516,7 @@ def upload_threatmodel(request, eid):
@user_is_authorized(Engagement, Permissions.Engagement_View, "eid")
def view_threatmodel(request, eid):
eng = get_object_or_404(Engagement, pk=eid)
return FileResponse(open(eng.tmodel_path, "rb"))
return generate_file_response_from_file_path(eng.tmodel_path)


@user_is_authorized(Engagement, Permissions.Engagement_View, "eid")
Expand Down
17 changes: 17 additions & 0 deletions dojo/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,23 @@ class UploadThreatForm(forms.Form):
attrs={"accept": ".jpg,.png,.pdf"}),
label="Select Threat Model")

def clean(self):
if (file := self.cleaned_data.get("file", None)) is not None:
ext = os.path.splitext(file.name)[1] # [0] returns path+filename
valid_extensions = [".jpg", ".png", ".pdf"]
if ext.lower() not in valid_extensions:
if accepted_extensions := f"{', '.join(valid_extensions)}":
msg = (
"Unsupported extension. Supported extensions are as "
f"follows: {accepted_extensions}"
)
else:
msg = (
"File uploads are prohibited due to the list of acceptable "
"file extensions being empty"
)
raise ValidationError(msg)


class MergeFindings(forms.ModelForm):
FINDING_ACTION = (("", "Select an Action"), ("inactive", "Inactive"), ("delete", "Delete"))
Expand Down
8 changes: 7 additions & 1 deletion dojo/jira_link/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,13 @@ def can_be_pushed_to_jira(obj, form=None):
elif isinstance(obj, Finding_Group):
if not obj.findings.all():
return False, f"{to_str_typed(obj)} cannot be pushed to jira as it is empty.", "error_empty"
if "Active" not in obj.status():
# Accommodating a strange behavior where a finding group sometimes prefers `obj.status` rather than `obj.status()`
try:
not_active = "Active" not in obj.status()
except TypeError: # TypeError: 'str' object is not callable
not_active = "Active" not in obj.status
# Determine if the finding group is not active
if not_active:
return False, f"{to_str_typed(obj)} cannot be pushed to jira as it is not active.", "error_inactive"

else:
Expand Down
16 changes: 10 additions & 6 deletions dojo/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@ def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
assert hasattr(request, "user"), "The Login Required middleware\
requires authentication middleware to be installed. Edit your\
MIDDLEWARE_CLASSES setting to insert\
'django.contrib.auth.middleware.AuthenticationMiddleware'. If that doesn't\
work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes\
'django.core.context_processors.auth'."
if not hasattr(request, "user"):
msg = (
"The Login Required middleware "
"requires authentication middleware to be installed. Edit your "
"MIDDLEWARE_CLASSES setting to insert "
"'django.contrib.auth.middleware.AuthenticationMiddleware'. If that doesn't "
"work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes "
"'django.core.context_processors.auth'."
)
raise AttributeError(msg)
if not request.user.is_authenticated:
path = request.path_info.lstrip("/")
if not any(m.match(path) for m in EXEMPT_URLS):
Expand Down
2 changes: 1 addition & 1 deletion dojo/settings/.settings.dist.py.sha256sum
Original file line number Diff line number Diff line change
@@ -1 +1 @@
42026ac47884ee26fe742e59fb7dc621b5f927ee6ee3c92daf09b97f2a740163
6a90a111e2b89eb2c400945c80ff76c64b135d78b84fdf6b09a6b83569946904
2 changes: 2 additions & 0 deletions dojo/settings/settings.dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,7 @@ def saml2_attrib_map_format(dict):
"ThreatComposer Scan": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL_OR_HASH_CODE,
"Invicti Scan": DEDUPE_ALGO_HASH_CODE,
"KrakenD Audit Scan": DEDUPE_ALGO_HASH_CODE,
"PTART Report": DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL,
}

# Override the hardcoded settings here via the env var
Expand Down Expand Up @@ -1736,6 +1737,7 @@ def saml2_attrib_map_format(dict):
"USN": "https://ubuntu.com/security/notices/", # e.g. https://ubuntu.com/security/notices/USN-6642-1
"DLA": "https://security-tracker.debian.org/tracker/", # e.g. https://security-tracker.debian.org/tracker/DLA-3917-1
"ELSA": "https://linux.oracle.com/errata/&&.html", # e.g. https://linux.oracle.com/errata/ELSA-2024-12714.html
"RXSA": "https://errata.rockylinux.org/", # e.g. https://errata.rockylinux.org/RXSA-2024:4928
}
# List of acceptable file types that can be uploaded to a given object via arbitrary file upload
FILE_UPLOAD_TYPES = env("DD_FILE_UPLOAD_TYPES")
Expand Down
38 changes: 23 additions & 15 deletions dojo/tools/osv_scanner/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,34 @@ def get_findings(self, file, test):
except json.decoder.JSONDecodeError:
return []
findings = []
for result in data["results"]:
source_path = result["source"]["path"]
source_type = result["source"]["type"]
for package in result["packages"]:
package_name = package["package"]["name"]
package_version = package["package"]["version"]
package_ecosystem = package["package"]["ecosystem"]
for vulnerability in package["vulnerabilities"]:
for result in data.get("results", []):
# Extract source locations if present
source_path = result.get("source", {}).get("path", "")
source_type = result.get("source", {}).get("type", "")
for package in result.get("packages", []):
package_name = package.get("package", {}).get("name")
package_version = package.get("package", {}).get("version")
package_ecosystem = package.get("package", {}).get("ecosystem", "")
for vulnerability in package.get("vulnerabilities", []):
vulnerabilityid = vulnerability.get("id", "")
vulnerabilitysummary = vulnerability.get("summary", "")
vulnerabilitydetails = vulnerability["details"]
vulnerabilitypackagepurl = vulnerability["affected"][0].get("package", "")
if vulnerabilitypackagepurl != "":
vulnerabilitypackagepurl = vulnerabilitypackagepurl["purl"]
cwe = vulnerability["affected"][0]["database_specific"].get("cwes", None)
if cwe is not None:
cwe = cwe[0]["cweId"]
vulnerabilitydetails = vulnerability.get("details", "")
vulnerabilitypackagepurl = ""
cwe = None
# Make sure we have an affected section to work with
if (affected := vulnerability.get("affected")) is not None:
if len(affected) > 0:
# Pull the package purl if present
if (vulnerabilitypackage := affected[0].get("package", "")) != "":
vulnerabilitypackagepurl = vulnerabilitypackage.get("purl", "")
# Extract the CWE
if (cwe := affected[0].get("database_specific", {}).get("cwes", None)) is not None:
cwe = cwe[0]["cweId"]
# Create some references
reference = ""
for ref in vulnerability.get("references"):
reference += ref.get("url") + "\n"
# Define the description
description = vulnerabilitysummary + "\n"
description += "**source_type**: " + source_type + "\n"
description += "**package_ecosystem**: " + package_ecosystem + "\n"
Expand Down
Empty file added dojo/tools/ptart/__init__.py
Empty file.
62 changes: 62 additions & 0 deletions dojo/tools/ptart/assessment_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import dojo.tools.ptart.ptart_parser_tools as ptart_tools
from dojo.models import Finding


class PTARTAssessmentParser:
def __init__(self):
self.cvss_type = None

def get_test_data(self, tree):
# Check that the report is valid, If we have no assessments, then
# return an empty list
if "assessments" not in tree:
return []

self.cvss_type = tree.get("cvss_type", None)
assessments = tree["assessments"]
return [finding for assessment in assessments
for finding in self.parse_assessment(assessment)]

def parse_assessment(self, assessment):
hits = assessment.get("hits", [])
return [self.get_finding(assessment, hit) for hit in hits]

def get_finding(self, assessment, hit):
effort = ptart_tools.parse_ptart_fix_effort(hit.get("fix_complexity"))
finding = Finding(
title=ptart_tools.parse_title_from_hit(hit),
severity=ptart_tools.parse_ptart_severity(hit.get("severity")),
effort_for_fixing=effort,
component_name=assessment.get("title", "Unknown Component"),
date=ptart_tools.parse_date_added_from_hit(hit),
)

# Don't add fields if they are blank
if hit["body"]:
finding.description = hit.get("body")

if hit["remediation"]:
finding.mitigation = hit.get("remediation")

if hit["id"]:
finding.unique_id_from_tool = hit.get("id")
finding.vuln_id_from_tool = hit.get("id")
finding.cve = hit.get("id")

# Clean up and parse the CVSS vector
cvss_vector = ptart_tools.parse_cvss_vector(hit, self.cvss_type)
if cvss_vector:
finding.cvssv3 = cvss_vector

if "labels" in hit:
finding.unsaved_tags = hit["labels"]

finding.unsaved_endpoints = ptart_tools.parse_endpoints_from_hit(hit)

# Add screenshots to files, and add other attachments as well.
finding.unsaved_files = ptart_tools.parse_screenshots_from_hit(hit)
finding.unsaved_files.extend(ptart_tools.parse_attachment_from_hit(hit))

finding.references = ptart_tools.parse_references_from_hit(hit)

return finding
Loading

0 comments on commit dffcbc6

Please sign in to comment.