Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/content/en/connecting_your_tools/parsers/file/nowsecure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
title: "NowSecure"
toc_hide: true
---
This parser imports the Now Secure app analysis resutls using `json` output.

### Sample analysis Data
Sample NowSecure result files can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/nowsecure).

### Using your own data

```sh
# https://support.nowsecure.com/hc/en-us/articles/7499657262093-Creating-a-NowSecure-Platform-API-Bearer-Token
API_TOKEN=""
# Can be obteained from the Webui
APP_REF=""
# Sample CURL request (beware of the pipe to jq!)
curl 'https://api.nowsecure.com/graphql' -H 'Accept-Language: en-US,en;q=0.9' -H 'Connection: keep-alive' -H 'Origin: https://app.nowsecure.com' -H 'Referer: https://app.nowsecure.com/' -H 'Sec-Fetch-Dest: empty' -H 'Sec-Fetch-Mode: cors' -H 'Sec-Fetch-Site: same-site' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36' -H 'accept: */*' -H "authorization: Bearer ${API_TOKEN}" -H 'client-source: platform' -H 'content-type: application/json' -H 'sec-ch-ua: "Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"' -H 'sec-ch-ua-mobile: ?0' -H 'sec-ch-ua-platform: "macOS"' --data-raw "{\"operationName\":\"getAssessment\",\"variables\":{\"canViewPolicies\":true,\"ref\":\"${app_ref}\",\"policyVersionRef\":null,\"catalog\":null},\"query\":\"query getAssessment(\$ref: UUID\u0021, \$canViewPolicies: Boolean = false, \$policyVersionRef: UUID, \$catalog: ComplianceReportCatalog) {\\n auto {\\n assessment(ref: \$ref, policyVersionRef: \$policyVersionRef) {\\n ...assessment\\n __typename\\n }\\n __typename\\n }\\n}\\n\\nfragment assessment on AutoAssessment {\\n ...assessmentWithoutReport\\n report {\\n ...report\\n __typename\\n }\\n baseReport {\\n findings(hidden: true, indeterminate: true) {\\n checkId\\n affected\\n cvss\\n findingStatus\\n __typename\\n }\\n __typename\\n }\\n __typename\\n}\\n\\nfragment assessmentWithoutReport on AutoAssessment {\\n ref\\n applicationRef\\n platformType\\n packageKey\\n packageVersion\\n taskId\\n title\\n iconURL\\n isAppstoreDownload\\n createdAt\\n createdBy {\\n ref\\n email\\n name\\n __typename\\n }\\n buildRef\\n buildVersion\\n upload {\\n ref\\n createdAt\\n digest\\n __typename\\n }\\n analysisConfigLevel\\n type\\n analysisJobType\\n staticScore\\n score\\n favorite\\n assessmentError {\\n code\\n title\\n description\\n options\\n __typename\\n }\\n isDebugMode\\n hasPreviousAssessmentForComparison\\n policy @include(if: \$canViewPolicies) {\\n ...litePolicy\\n __typename\\n }\\n policyVersion @include(if: \$canViewPolicies) {\\n ref\\n policyRef\\n version\\n policyCategories {\\n policyCategory\\n label\\n description\\n color\\n __typename\\n }\\n __typename\\n }\\n analysis {\\n isAuthenticated\\n isCancelled\\n isComplete\\n isRunning\\n status\\n mappedStatus\\n errorCode\\n error {\\n code\\n message\\n __typename\\n }\\n artifacts(artifactType: HAR) {\\n assessmentRef\\n ref\\n artifactType\\n tags\\n displayName\\n byteSize\\n filename\\n contentType\\n url\\n __typename\\n }\\n screenshots {\\n ref\\n url\\n analysisPass\\n __typename\\n }\\n task {\\n static {\\n ...analysisJob\\n __typename\\n }\\n dynamic {\\n ...analysisJob\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n build {\\n ref\\n digest\\n version\\n __typename\\n }\\n group {\\n ref\\n __typename\\n }\\n __typename\\n}\\n\\nfragment analysisJob on AnalysisJob {\\n id\\n status\\n modifiedAt\\n isRunning\\n isComplete\\n __typename\\n}\\n\\nfragment litePolicy on Policy {\\n ref\\n name\\n createdBy\\n createdAt\\n updatedAt\\n archivedAt\\n targetType\\n isDefault\\n isNSManaged\\n latestVersion {\\n ref\\n version\\n trackedVersion\\n trackedVersionRef\\n __typename\\n }\\n targets {\\n ref\\n type\\n __typename\\n }\\n enabled\\n trackedPolicy {\\n ref\\n name\\n latestVersion {\\n ref\\n version\\n __typename\\n }\\n __typename\\n }\\n __typename\\n}\\n\\nfragment report on AssessmentReport {\\n createdAt\\n findings(hidden: true, indeterminate: true) {\\n ...reportFinding\\n __typename\\n }\\n __typename\\n}\\n\\nfragment reportFinding on Finding {\\n affected\\n checkId\\n uniqueVulnerabilityId\\n check {\\n ...findingCheck\\n __typename\\n }\\n userChanges {\\n date\\n user {\\n name\\n __typename\\n }\\n __typename\\n }\\n cvss\\n cvssVector\\n findingStatus\\n decision\\n decisionInfo {\\n note\\n reason\\n wasInherited\\n __typename\\n }\\n evidenceDecision\\n hidden\\n impactType\\n severity\\n note\\n adjustments\\n shortRemediation\\n title\\n hasContext\\n hasCodeLocations\\n canHaveActionableEvidence\\n hasActionedEvidence\\n policyCategory @include(if: \$canViewPolicies) {\\n policyCategory\\n color\\n label\\n __typename\\n }\\n policyFindingOverrides {\\n checkId\\n cvss\\n cvssVector\\n hidden\\n impactType\\n __typename\\n }\\n complianceReportRelations(catalog: \$catalog) {\\n catalog\\n groupId\\n controlId\\n version\\n __typename\\n }\\n __typename\\n}\\n\\nfragment findingCheck on FindingCheck {\\n id\\n analysisType\\n title\\n description\\n shortDescription\\n platformType\\n category\\n categories\\n legacyFindingKey\\n isNew\\n deprecated\\n context {\\n view\\n title\\n fields {\\n key\\n format\\n title\\n description\\n __typename\\n }\\n children {\\n view\\n title\\n fields {\\n key\\n format\\n title\\n description\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n issue {\\n cvss\\n cvssVector\\n title\\n description\\n recommendation\\n passingContent\\n category\\n severity\\n warn\\n impactSummary\\n regulations {\\n type\\n label\\n links {\\n title\\n url\\n __typename\\n }\\n __typename\\n }\\n stepsToReproduce\\n codeSamples {\\n platform\\n syntax\\n caption\\n block\\n __typename\\n }\\n guidanceLinks {\\n platform\\n caption\\n url\\n __typename\\n }\\n __typename\\n }\\n compliance {\\n regulation {\\n description\\n __typename\\n }\\n notes {\\n description\\n __typename\\n }\\n activity {\\n tss {\\n description\\n __typename\\n }\\n guidance {\\n description\\n __typename\\n }\\n tests {\\n description\\n __typename\\n }\\n __typename\\n }\\n nowSecureGuidance {\\n description\\n __typename\\n }\\n __typename\\n }\\n __typename\\n}\\n\"}" | jq
```
### Other considerations

* `fix_available` will be set to true if the `mitigation` field isn't empty.

### Default Deduplication Hashcode Fields
By default, DefectDojo identifies duplicate Findings using these [hashcode fields](https://docs.defectdojo.com/en/working_with_findings/finding_deduplication/about_deduplication/):

- title
- description
21 changes: 21 additions & 0 deletions dojo/fixtures/test_type.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,26 @@
},
"model": "dojo.test_type",
"pk": 7
},
{
"fields": {
"name": "NowSecure"
},
"model": "dojo.test_type",
"pk": 8
},
{
"fields": {
"name": "NowSecure"
},
"model": "dojo.test_type",
"pk": 9
},
{
"fields": {
"name": "NowSecure"
},
"model": "dojo.test_type",
"pk": 10
}
]
Empty file.
123 changes: 123 additions & 0 deletions dojo/tools/nowsecure/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import hashlib
import json
from urllib.parse import urlparse

Check failure on line 3 in dojo/tools/nowsecure/parser.py

View workflow job for this annotation

GitHub Actions / ruff-linting

Ruff (F401)

dojo/tools/nowsecure/parser.py:3:26: F401 `urllib.parse.urlparse` imported but unused
from dojo.models import Endpoint, Finding

Check failure on line 4 in dojo/tools/nowsecure/parser.py

View workflow job for this annotation

GitHub Actions / ruff-linting

Ruff (F401)

dojo/tools/nowsecure/parser.py:4:25: F401 `dojo.models.Endpoint` imported but unused
from dojo.utils import parse_cvss_data
import re

Check failure on line 6 in dojo/tools/nowsecure/parser.py

View workflow job for this annotation

GitHub Actions / ruff-linting

Ruff (I001)

dojo/tools/nowsecure/parser.py:1:1: I001 Import block is un-sorted or un-formatted


class NowSecureParser(object):

Check failure on line 9 in dojo/tools/nowsecure/parser.py

View workflow job for this annotation

GitHub Actions / ruff-linting

Ruff (UP004)

dojo/tools/nowsecure/parser.py:9:23: UP004 Class `NowSecureParser` inherits from `object`
"""
Importing findings from NowSecure app analysis.
"""

Check failure on line 12 in dojo/tools/nowsecure/parser.py

View workflow job for this annotation

GitHub Actions / ruff-linting

Ruff (D203)

dojo/tools/nowsecure/parser.py:10:5: D203 1 blank line required before class docstring

Check failure on line 12 in dojo/tools/nowsecure/parser.py

View workflow job for this annotation

GitHub Actions / ruff-linting

Ruff (D200)

dojo/tools/nowsecure/parser.py:10:5: D200 One-line docstring should fit on one line

def get_scan_types(self):
return ["NowSecure Scan"]

def get_label_for_scan_types(self, scan_type):
return "NowSecure Scan"

def get_description_for_scan_types(self, scan_type):
return "NowSecure report file can be imported in JSON format (option --json)."

def get_findings(self, file, test):
data = json.load(file)
dupes = dict()

Check failure on line 25 in dojo/tools/nowsecure/parser.py

View workflow job for this annotation

GitHub Actions / ruff-linting

Ruff (C408)

dojo/tools/nowsecure/parser.py:25:17: C408 Unnecessary `dict()` call (rewrite as a literal)
for content in data.get('data', {}).get('auto', {}).get('assessment', {}).get('report', {}).get('findings', []):

Check failure on line 26 in dojo/tools/nowsecure/parser.py

View workflow job for this annotation

GitHub Actions / ruff-linting

Ruff (Q000)

dojo/tools/nowsecure/parser.py:26:65: Q000 Single quotes found but double quotes preferred

Check failure on line 26 in dojo/tools/nowsecure/parser.py

View workflow job for this annotation

GitHub Actions / ruff-linting

Ruff (Q000)

dojo/tools/nowsecure/parser.py:26:49: Q000 Single quotes found but double quotes preferred

Check failure on line 26 in dojo/tools/nowsecure/parser.py

View workflow job for this annotation

GitHub Actions / ruff-linting

Ruff (Q000)

dojo/tools/nowsecure/parser.py:26:33: Q000 Single quotes found but double quotes preferred
if content.get('affected',[]):
if content.get('check',{}).get('issue',[]):
# If the issue exist, it always has a title
title = content.get('check',{}).get('issue',{}).get('title',[])
for regulation in content.get('check',{}).get('issue',{}).get('regulations',[]):
if regulation.get('type',[]) == "cwe":
cwe = regulation.get('links',[])[0].get('title',[])
if content.get('check',{}).get('issue',{}).get('description',[]):
description = content.get('check',{}).get('issue',{}).get('description',[])
else:
description = ''''''
# https://docs.defectdojo.com/en/open_source/contributing/how-to-write-a-parser/#parsing-of-cvss-vectors
if content.get('check',{}).get('issue',{}).get('cvssVector',[]):
cvss_vector = content.get('check',{}).get('issue',{}).get('cvssVector',[])
cvss_data = parse_cvss_data(cvss_vector)
if cvss_data:
severity = cvss_data.get('severity',[])
cvssv3 = cvss_data.get('cvssv3',[])
cvssv4 = cvss_data.get('cvssv4',[])
# we don't set any score fields as those will be overwritten by Defect Dojo
if content.get('check',{}).get('issue',{}).get('recommendation',[]):
mitigation = content.get('check',{}).get('issue',{}).get('recommendation',[])
fix_available=True
else:
fix_available=False

impact = content.get('check',{}).get('issue',{}).get('impactSummary',[]) or ''''''
if data.get('data',{}).get('auto',{}).get('assessment',{}).get('applicationRef',[]) and data.get('data',{}).get('auto',{}).get('assessment',{}).get('ref',[]) and content.get('checkId',[]):
url = f"https://app.nowsecure.com/app/{data.get('data',{}).get('auto',{}).get('assessment',{}).get('applicationRef',[])}/assessment/{data.get('data',{}).get('auto',{}).get('assessment',{}).get('ref',[])}?viewObservationsBy=categories&viewFindingsBy=policyCategory#{content.get('checkId',[])}"
steps_to_reproduce = (content.get('check',{}).get('issue',{}).get('stepsToReproduce',[]) or '''''')
if content.get('check',{}).get('issue',{}).get('codeSamples',[]):
code_samples = ''''''
for code_sample in content.get('check',{}).get('issue',{}).get('codeSamples',{}):
code_samples += f'''**{code_sample.get('platform',[])}**: {code_sample.get('caption',[])}\n```{code_sample.get('syntax',[])}\n{code_sample.get('block',[])}\n```\n'''
if code_samples:
steps_to_reproduce += code_samples
references = ''''''
if content.get('check',{}).get('issue',{}).get('guidanceLinks',[]):
references+="### Guidance Links\n"
for reference in content.get('check',{}).get('issue',{}).get('guidanceLinks',[]):
references+=f'''* [{reference.get('caption',[])}]({reference.get('url',[])})\n'''

cwe = None
if content.get('check',{}).get('issue',{}).get('regulations',[]):
references+="### Regulations\n"
for regulation in content.get('check',{}).get('issue',{}).get('regulations',{}):
if regulation.get('type',[]) == 'cwe':
cwe = regulation.get('links',[])[0].get('title',[])
for link in regulation.get('links',{}):
references+=f"* **{regulation.get('label',[])}**: [{link.get('title',[])}]({link.get('url',[])})\n"
cve = None
cve_pattern = r'CVE-\d{4}-\d{4,7}'
cve_matches = re.findall(cve_pattern, title)
if cve_matches:
cve = list(dict.fromkeys(cve_matches))[0]

finding = Finding(
title=title,
test=test,
description=description,
severity=severity,
cvssv3 = cvssv3,
cvssv4 = cvssv4,
mitigation = mitigation,
url = url,
steps_to_reproduce = steps_to_reproduce,
impact=impact,
references = references,
cwe = cwe,
cve = cve,
fix_available = fix_available,

static_finding=True,
dynamic_finding=False,
)
dupe_key = hashlib.sha256(str(description + title).encode('utf-8')).hexdigest()
if dupe_key in dupes:
find = dupes[dupe_key]
if finding.description:
find.description += "\n" + finding.description
find.unsaved_endpoints.extend(finding.unsaved_endpoints)
dupes[dupe_key] = find
else:
dupes[dupe_key] = finding

return list(dupes.values())

def convert_severity(self, num_severity):
"""Convert severity value"""
if num_severity >= -10:
return "Low"
elif -11 >= num_severity > -26:
return "Medium"
elif num_severity <= -26:
return "High"
else:
return "Info"
Loading
Loading