-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Wizcli improvements #12446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: bugfix
Are you sure you want to change the base?
Wizcli improvements #12446
Conversation
This pull request contains multiple security vulnerabilities, including potential information disclosure in error logging, a possible denial of service risk through resource exhaustion, and a hardcoded service account key in test data, which could expose sensitive credentials and system information if not properly addressed.
|
Vulnerability | Potential Information Disclosure in Error Logging |
---|---|
Description | Error messages in the Wizcli directory parser include exception details. If these logs or exceptions are not carefully handled, they could expose internal system information. Implementing more generic error messages and ensuring proper exception handling is recommended to mitigate potential information disclosure. |
django-DefectDojo/dojo/tools/wizcli_dir/parser.py
Lines 1 to 66 in ae1cd2a
import json | |
import logging | |
from dojo.tools.wizcli_common_parsers.parsers import WizcliParsers | |
logger = logging.getLogger(__name__) | |
class WizcliDirParser: | |
"""Wiz CLI Directory/IaC Scan results in JSON file format.""" | |
def get_scan_types(self): | |
return ["Wizcli Dir Scan"] | |
def get_label_for_scan_types(self, scan_type): | |
return "Wiz CLI Scan (Directory)" | |
def get_description_for_scan_types(self, scan_type): | |
return "Parses Wiz CLI Directory/IaC scan results in JSON format, creating granular findings for vulnerabilities and secrets." | |
def get_findings(self, file, test): | |
"""Processes the JSON report and returns a list of DefectDojo Finding objects.""" | |
try: | |
scan_data = file.read() | |
if isinstance(scan_data, bytes): | |
# Try decoding common encodings | |
try: | |
scan_data = scan_data.decode("utf-8-sig") # Handles BOM | |
except UnicodeDecodeError: | |
scan_data = scan_data.decode("utf-8") # Fallback | |
data = json.loads(scan_data) | |
except json.JSONDecodeError as e: | |
msg = f"Invalid JSON format: {e}" | |
logger.error(msg) | |
raise ValueError(msg) from e | |
except Exception as e: | |
msg = f"Error processing report file: {e}" | |
logger.error(msg) | |
raise ValueError(msg) from e | |
findings = [] | |
results_data = data.get("result", {}) | |
if not results_data: | |
logger.warning("No 'result' key found in the Wiz report. Unable to parse findings.") | |
return findings | |
# Parse Libraries (Vulnerabilities) | |
libraries = results_data.get("libraries") | |
if libraries: | |
logger.debug(f"Parsing {len(libraries)} library entries.") | |
findings.extend(WizcliParsers.parse_libraries(libraries, test)) | |
else: | |
logger.debug("No 'libraries' data found in results.") | |
# Parse Secrets | |
secrets = results_data.get("secrets") | |
if secrets: | |
logger.debug(f"Parsing {len(secrets)} secret entries.") | |
findings.extend(WizcliParsers.parse_secrets(secrets, test)) | |
else: | |
logger.debug("No 'secrets' data found in results.") | |
logger.info(f"WizcliDirParser processed {len(findings)} findings.") | |
return findings |
⚠️ Potential Denial of Service via Resource Exhaustion in dojo/tools/wizcli_img/parser.py
Vulnerability | Potential Denial of Service via Resource Exhaustion |
---|---|
Description | The get_findings method reads entire file contents without size limits, which could lead to memory exhaustion if a very large file is processed. Implementing file size limits and streaming parsing techniques would help prevent potential denial of service attacks. |
django-DefectDojo/dojo/tools/wizcli_img/parser.py
Lines 1 to 73 in ae1cd2a
import json | |
import logging | |
from dojo.tools.wizcli_common_parsers.parsers import WizcliParsers # Adjust import path | |
logger = logging.getLogger(__name__) | |
class WizcliImgParser: | |
"""Wiz CLI Container Image Scan results in JSON file format.""" | |
def get_scan_types(self): | |
# Use a distinct name for image scans | |
return ["Wizcli Img Scan"] | |
def get_label_for_scan_types(self, scan_type): | |
return "Wiz CLI Scan (Image)" | |
def get_description_for_scan_types(self, scan_type): | |
return "Parses Wiz CLI Container Image scan results in JSON format." | |
def get_findings(self, file, test): | |
try: | |
scan_data = file.read() | |
if isinstance(scan_data, bytes): | |
try: | |
scan_data = scan_data.decode("utf-8-sig") | |
except UnicodeDecodeError: | |
scan_data = scan_data.decode("utf-8") | |
data = json.loads(scan_data) | |
except json.JSONDecodeError as e: | |
msg = f"Invalid JSON format: {e}" | |
logger.error(msg) | |
raise ValueError(msg) from e | |
except Exception as e: | |
msg = f"Error processing report file: {e}" | |
logger.error(msg) | |
raise ValueError(msg) from e | |
findings = [] | |
results_data = data.get("result", {}) | |
if not results_data: | |
logger.warning("No 'result' key found in the Wiz report.") | |
return findings | |
# Parse OS Packages - Key difference for image scans | |
os_packages = results_data.get("osPackages") | |
if os_packages: | |
logger.debug(f"Parsing {len(os_packages)} OS package entries.") | |
findings.extend(WizcliParsers.parse_os_packages(os_packages, test)) | |
else: | |
logger.debug("No 'osPackages' data found in results.") | |
# Parse Libraries (if present in image scans) | |
libraries = results_data.get("libraries") | |
if libraries: | |
logger.debug(f"Parsing {len(libraries)} library entries.") | |
findings.extend(WizcliParsers.parse_libraries(libraries, test)) | |
else: | |
logger.debug("No 'libraries' data found in results.") | |
# Parse Secrets (if present in image scans) | |
secrets = results_data.get("secrets") | |
if secrets: | |
logger.debug(f"Parsing {len(secrets)} secret entries.") | |
findings.extend(WizcliParsers.parse_secrets(secrets, test)) | |
else: | |
logger.debug("No 'secrets' data found in results.") | |
logger.info(f"WizcliImgParser processed {len(findings)} findings.") | |
return findings |
⚠️ Hardcoded Service Account Key in unittests/scans/wizcli_img/wizcli_img_one_vul.json
Vulnerability | Hardcoded Service Account Key |
---|---|
Description | A GCP Service Account Key is present in the test JSON file. Even in test data, hardcoding service account details poses a security risk. Ensure that such sensitive credentials are never committed to version control, even in test fixtures, and use secure secret management practices. |
django-DefectDojo/unittests/scans/wizcli_img/wizcli_img_one_vul.json
Lines 1 to 387 in ae1cd2a
{ | |
"id": "8001d6bd-2b30-419d-8819-a3e962c90d42", | |
"projects": null, | |
"createdAt": "2025-05-07T13:46:45.864014091Z", | |
"startedAt": "2025-05-07T13:46:31.95780963Z", | |
"createdBy": { | |
"serviceAccount": { | |
"id": "hycyzczp25cxpbmp67mtt2cg4mcadi4doz2fey4y4bgrqmk5b2ugs" | |
} | |
}, | |
"status": { | |
"state": "SUCCESS", | |
"verdict": "FAILED_BY_POLICY" | |
}, | |
"policies": [ | |
{ | |
"id": "9bf73b16-99e7-4a54-af1e-dcfa1436a8f2", | |
"name": "test Default vulnerabilities policy ( Updated )", | |
"description": "Default built-in policy", | |
"type": "VULNERABILITIES", | |
"builtin": false, | |
"projects": null, | |
"policyLifecycleEnforcements": [ | |
{ | |
"enforcementMethod": "BLOCK", | |
"deploymentLifecycle": "CLI" | |
} | |
], | |
"ignoreRules": null, | |
"lifecycleTargets": null, | |
"Default": false, | |
"params": { | |
"__typename": "cicdscanpolicyparamsvulnerabilities", | |
"severity": "HIGH", | |
"packageCountThreshold": 1, | |
"ignoreUnfixed": true, | |
"packageAllowList": [], | |
"detectionMethods": [ | |
"PACKAGE", | |
"LIBRARY", | |
"FILE_PATH" | |
], | |
"vulnerabilities": [], | |
"fixGracePeriodHours": 0, | |
"publishGracePeriodHours": 0, | |
"ignoreTransitiveVulnerabilities": true | |
} | |
}, | |
{ | |
"id": "f3393997-29e9-4d15-b490-b91f575aebef", | |
"name": "Default malware policy", | |
"description": "Default built-in policy for malware scanning", | |
"type": "MALWARE", | |
"builtin": true, | |
"projects": null, | |
"policyLifecycleEnforcements": [ | |
{ | |
"enforcementMethod": "AUDIT", | |
"deploymentLifecycle": "CLI" | |
} | |
], | |
"ignoreRules": null, | |
"lifecycleTargets": null, | |
"Default": false, | |
"params": { | |
"__typename": "cicdscanpolicyparamsmalware", | |
"malwareFindingSeverityThreshold": "HIGH", | |
"malwareFindingConfidenceLevelThreshold": "HIGH", | |
"countThreshold": 1 | |
} | |
}, | |
{ | |
"id": "9c6726d0-1ada-4541-b6d6-3da5ca1124f9", | |
"name": "test Default vulnerabilities policy", | |
"description": "Default built-in policy", | |
"type": "VULNERABILITIES", | |
"builtin": false, | |
"projects": null, | |
"policyLifecycleEnforcements": [ | |
{ | |
"enforcementMethod": "BLOCK", | |
"deploymentLifecycle": "CLI" | |
} | |
], | |
"ignoreRules": null, | |
"lifecycleTargets": null, | |
"Default": false, | |
"params": { | |
"__typename": "cicdscanpolicyparamsvulnerabilities", | |
"severity": "HIGH", | |
"packageCountThreshold": 1, | |
"ignoreUnfixed": true, | |
"packageAllowList": [], | |
"detectionMethods": [], | |
"vulnerabilities": [], | |
"fixGracePeriodHours": 0, | |
"publishGracePeriodHours": 0, | |
"ignoreTransitiveVulnerabilities": true | |
} | |
}, | |
{ | |
"id": "5a03dfb5-99ff-49b6-8a48-a9b65b13bf9a", | |
"name": "test Default secrets policy", | |
"description": "Default built-in policy for secret scanning", | |
"type": "SECRETS", | |
"builtin": false, | |
"projects": null, | |
"policyLifecycleEnforcements": [ | |
{ | |
"enforcementMethod": "BLOCK", | |
"deploymentLifecycle": "CLI" | |
} | |
], | |
"ignoreRules": null, | |
"lifecycleTargets": null, | |
"Default": false, | |
"params": { | |
"__typename": "cicdscanpolicyparamssecrets", | |
"countThreshold": 1, | |
"pathAllowList": [ | |
"/.git/config", | |
".git/config" | |
], | |
"secretFindingSeverityThreshold": "INFORMATIONAL" | |
} | |
}, | |
{ | |
"id": "978a1803-2e29-42c1-832a-ddfbb836c051", | |
"name": "test Default sensitive data policy", | |
"description": "Default built-in policy for sensitive data scanning", | |
"type": "SENSITIVE_DATA", | |
"builtin": false, | |
"projects": null, | |
"policyLifecycleEnforcements": [ | |
{ | |
"enforcementMethod": "AUDIT", | |
"deploymentLifecycle": "CLI" | |
} | |
], | |
"ignoreRules": null, | |
"lifecycleTargets": null, | |
"Default": false, | |
"params": { | |
"__typename": "cicdscanpolicyparamssensitivedata", | |
"dataFindingSeverityThreshold": "", | |
"countThreshold": 0 | |
} | |
} | |
], | |
"extraInfo": null, | |
"tags": null, | |
"outdatedPolicies": [], | |
"taggedResource": null, | |
"scanOriginResource": { | |
"__typename": "CICDScanOriginContainerImage", | |
"name": "registry.sss.com/test.ai/services/api/release-3-967-0:latest", | |
"id": null, | |
"digest": null, | |
"imageLabels": null | |
}, | |
"result": { | |
"__typename": "CICDDiskScanResult", | |
"osPackages": null, | |
"libraries": null, | |
"applications": null, | |
"cpes": null, | |
"secrets": [ | |
{ | |
"id": "fcc00ecc-249b-5723-84fc-729aca5a5a67", | |
"externalId": null, | |
"description": "GCP Service Account Key ([email protected])", | |
"path": "/app/keys/gcp.json", | |
"lineNumber": 5, | |
"offset": 141, | |
"type": "CLOUD_KEY", | |
"contains": [ | |
{ | |
"name": "GCP Service Account Key ([email protected])", | |
"type": "CLOUD_KEY" | |
} | |
], | |
"snippet": null, | |
"failedPolicyMatches": [ | |
{ | |
"policy": { | |
"id": "5a03dfb5-99ff-49b6-8a48-a9b65b13bf9a", | |
"name": "test Default secrets policy", | |
"description": "Default built-in policy for secret scanning", | |
"type": "SECRETS", | |
"builtin": false, | |
"projects": null, | |
"policyLifecycleEnforcements": [ | |
{ | |
"enforcementMethod": "BLOCK", | |
"deploymentLifecycle": "CLI", | |
"enforcementConfig": null | |
} | |
], | |
"ignoreRules": null, | |
"lifecycleTargets": null, | |
"Default": false, | |
"params": { | |
"__typename": "cicdscanpolicyparamssecrets", | |
"countThreshold": 1, | |
"pathAllowList": [ | |
"/.git/config", | |
".git/config" | |
], | |
"secretFindingSeverityThreshold": "INFORMATIONAL" | |
} | |
}, | |
"ignoreReason": null, | |
"matchedIgnoreRules": null | |
} | |
], | |
"hasAdminPrivileges": null, | |
"hasHighPrivileges": null, | |
"severity": "HIGH", | |
"relatedEntities": null, | |
"ignoredPolicyMatches": null, | |
"details": { | |
"__typename": "DiskScanSecretDetailsCloudKey", | |
"providerUniqueID": "[email protected]", | |
"keyType": 3, | |
"isLongTerm": true | |
} | |
} | |
], | |
"dataFindings": null, | |
"vulnerableSBOMArtifactsByNameVersion": null, | |
"hostConfiguration": { | |
"hostConfigurationFrameworks": null, | |
"hostConfigurationFindings": null, | |
"analytics": null | |
}, | |
"failedPolicyMatches": [ | |
{ | |
"policy": { | |
"id": "9bf73b16-99e7-4a54-af1e-dcfa1436a8f2", | |
"name": "test Default vulnerabilities policy ( Updated )", | |
"description": "Default built-in policy", | |
"type": "VULNERABILITIES", | |
"builtin": false, | |
"projects": null, | |
"policyLifecycleEnforcements": [ | |
{ | |
"enforcementMethod": "BLOCK", | |
"deploymentLifecycle": "CLI", | |
"enforcementConfig": null | |
} | |
], | |
"ignoreRules": null, | |
"lifecycleTargets": null, | |
"Default": false, | |
"params": { | |
"__typename": "cicdscanpolicyparamsvulnerabilities", | |
"severity": "HIGH", | |
"packageCountThreshold": 1, | |
"ignoreUnfixed": true, | |
"packageAllowList": [], | |
"detectionMethods": [ | |
"PACKAGE", | |
"LIBRARY", | |
"FILE_PATH" | |
], | |
"vulnerabilities": [], | |
"fixGracePeriodHours": 0, | |
"publishGracePeriodHours": 0, | |
"ignoreTransitiveVulnerabilities": true | |
} | |
}, | |
"ignoreReason": null, | |
"matchedIgnoreRules": null | |
}, | |
{ | |
"policy": { | |
"id": "9c6726d0-1ada-4541-b6d6-3da5ca1124f9", | |
"name": "test Default vulnerabilities policy", | |
"description": "Default built-in policy", | |
"type": "VULNERABILITIES", | |
"builtin": false, | |
"projects": null, | |
"policyLifecycleEnforcements": [ | |
{ | |
"enforcementMethod": "BLOCK", | |
"deploymentLifecycle": "CLI", | |
"enforcementConfig": null | |
} | |
], | |
"ignoreRules": null, | |
"lifecycleTargets": null, | |
"Default": false, | |
"params": { | |
"__typename": "cicdscanpolicyparamsvulnerabilities", | |
"severity": "HIGH", | |
"packageCountThreshold": 1, | |
"ignoreUnfixed": true, | |
"packageAllowList": [], | |
"detectionMethods": [], | |
"vulnerabilities": [], | |
"fixGracePeriodHours": 0, | |
"publishGracePeriodHours": 0, | |
"ignoreTransitiveVulnerabilities": true | |
} | |
}, | |
"ignoreReason": null, | |
"matchedIgnoreRules": null | |
}, | |
{ | |
"policy": { | |
"id": "5a03dfb5-99ff-49b6-8a48-a9b65b13bf9a", | |
"name": "test Default secrets policy", | |
"description": "Default built-in policy for secret scanning", | |
"type": "SECRETS", | |
"builtin": false, | |
"projects": null, | |
"policyLifecycleEnforcements": [ | |
{ | |
"enforcementMethod": "BLOCK", | |
"deploymentLifecycle": "CLI", | |
"enforcementConfig": null | |
} | |
], | |
"ignoreRules": null, | |
"lifecycleTargets": null, | |
"Default": false, | |
"params": { | |
"__typename": "cicdscanpolicyparamssecrets", | |
"countThreshold": 1, | |
"pathAllowList": [ | |
"/.git/config", | |
".git/config" | |
], | |
"secretFindingSeverityThreshold": "INFORMATIONAL" | |
} | |
}, | |
"ignoreReason": null, | |
"matchedIgnoreRules": null | |
} | |
], | |
"analytics": { | |
"vulnerabilities": { | |
"infoCount": 0, | |
"lowCount": 2, | |
"mediumCount": 14, | |
"highCount": 9, | |
"criticalCount": 3, | |
"unfixedCount": 2, | |
"totalCount": 28 | |
}, | |
"secrets": { | |
"privateKeyCount": 0, | |
"publicKeyCount": 0, | |
"passwordCount": 0, | |
"certificateCount": 0, | |
"cloudKeyCount": 1, | |
"sshAuthorizedKeyCount": 0, | |
"dbConnectionStringCount": 0, | |
"gitCredentialCount": 0, | |
"presignedURLCount": 0, | |
"saasAPIKeyCount": 0, | |
"infoCount": 0, | |
"lowCount": 0, | |
"mediumCount": 0, | |
"highCount": 0, | |
"criticalCount": 0, | |
"totalCount": 1 | |
}, | |
"hostConfiguration": null, | |
"malware": { | |
"infoCount": 0, | |
"lowCount": 0, | |
"mediumCount": 0, | |
"highCount": 0, | |
"criticalCount": 0, | |
"totalCount": 0 | |
}, | |
"softwareSupplyChain": null, | |
"filesScannedCount": 2666, | |
"directoriesScannedCount": 161 | |
}, | |
"sbomOutput": "", | |
"malwares": null, | |
"softwareSupplyChain": null | |
}, | |
"reportUrl": "https://app.wiz.io/findings/cicd-scans#~%2528cicd_scan~%25278001d6bd-2b30-419d-8819-a3e962c90d42%252A2c2025-05-07T13%2525%25252A3a46%2525%25252A3a31.95780963Z%2527%2529" | |
} |
All finding details can be found in the DryRun Security Dashboard.
✅ Test Scan Results – Parser Behavior & Deduplication1.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @OsamaMahmood for your extensive PR. We do have some feedback:
- Could you look at updating the tests/samples scans to reflect the updates to the parsers?
- Could you look using the hash code configuration for deduplication?
I just raised #12463 to clarify the use of the unique_id_from_tool
field. It's intended/accepted use is to contain value present in the report that can be used to recognize the finding inside the tool. And for strong and exact deduplication.
We will discuss internally if/how we can accomodate values computed by the parser that might be useful for deduplication.
Hi @valentijnscholten i have updated the |
Release: Merge release into master from: release/2.47.1
This pull request contains a potential Denial of Service (DoS) vulnerability in the
Potential DoS via Unbounded File Read in
|
Vulnerability | Potential DoS via Unbounded File Read |
---|---|
Description | The get_findings method reads the entire file content without size limitations. An attacker could potentially provide an extremely large JSON file, causing excessive memory consumption and risking application unresponsiveness. This is a real concern that could lead to a Denial of Service condition. |
django-DefectDojo/dojo/tools/wizcli_img/parser.py
Lines 1 to 73 in 16812ad
import json | |
import logging | |
from dojo.tools.wizcli_common_parsers.parsers import WizcliParsers # Adjust import path | |
logger = logging.getLogger(__name__) | |
class WizcliImgParser: | |
"""Wiz CLI Container Image Scan results in JSON file format.""" | |
def get_scan_types(self): | |
# Use a distinct name for image scans | |
return ["Wizcli Img Scan"] | |
def get_label_for_scan_types(self, scan_type): | |
return "Wiz CLI Scan (Image)" | |
def get_description_for_scan_types(self, scan_type): | |
return "Parses Wiz CLI Container Image scan results in JSON format." | |
def get_findings(self, file, test): | |
try: | |
scan_data = file.read() | |
if isinstance(scan_data, bytes): | |
try: | |
scan_data = scan_data.decode("utf-8-sig") | |
except UnicodeDecodeError: | |
scan_data = scan_data.decode("utf-8") | |
data = json.loads(scan_data) | |
except json.JSONDecodeError as e: | |
msg = f"Invalid JSON format: {e}" | |
logger.error(msg) | |
raise ValueError(msg) from e | |
except Exception as e: | |
msg = f"Error processing report file: {e}" | |
logger.error(msg) | |
raise ValueError(msg) from e | |
findings = [] | |
results_data = data.get("result", {}) | |
if not results_data: | |
logger.warning("No 'result' key found in the Wiz report.") | |
return findings | |
# Parse OS Packages - Key difference for image scans | |
os_packages = results_data.get("osPackages") | |
if os_packages: | |
logger.debug(f"Parsing {len(os_packages)} OS package entries.") | |
findings.extend(WizcliParsers.parse_os_packages(os_packages, test)) | |
else: | |
logger.debug("No 'osPackages' data found in results.") | |
# Parse Libraries (if present in image scans) | |
libraries = results_data.get("libraries") | |
if libraries: | |
logger.debug(f"Parsing {len(libraries)} library entries.") | |
findings.extend(WizcliParsers.parse_libraries(libraries, test)) | |
else: | |
logger.debug("No 'libraries' data found in results.") | |
# Parse Secrets (if present in image scans) | |
secrets = results_data.get("secrets") | |
if secrets: | |
logger.debug(f"Parsing {len(secrets)} secret entries.") | |
findings.extend(WizcliParsers.parse_secrets(secrets, test)) | |
else: | |
logger.debug("No 'secrets' data found in results.") | |
logger.info(f"WizcliImgParser processed {len(findings)} findings.") | |
return findings |
All finding details can be found in the DryRun Security Dashboard.
"Wizcli Img Scan": ["title", "description", "file_path", "line", "component_name", "component_version"], | ||
"Wizcli Dir Scan": ["title", "description", "file_path", "line", "component_name", "component_version"], | ||
"Wizcli IAC Scan": ["title", "description", "file_path", "line", "component_name"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The description contains values that can sometimes change over time. Is it really needed for dedupe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks two more things:
- see my comment on the hash code fields
- because the dedupe config has changed AND the title is not set differently, this needs some docs in the upgrade notes for 2.47.3.
Can you add instructions on how to recalculate the hash codes (see other releases to get a starting point). And line that states dedupe can mismatch between findings imported by the new parser versus the old parser (because of the change in values for the title field).
Description
_generate_unique_id
method to ensure consistent finding deduplication usingunique_id_from_tool
:Checklist
This checklist is for your information.
dev
.dev
.bugfix
branch.