PAN-OS_Security_Advisories_Enrichment
This script enriches CVEs published by Palo Alto Networks with detailed vulnerability information from the official Palo Alto Networks Security Advisories website https://security.paloaltonetworks.com.
python · PAN-OS by Palo Alto Networks
Details
| ID | PAN-OS_Security_Advisories_Enrichment |
|---|---|
| Language | python |
| From Version | 6.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
README
PANOSSecurityAdvisories_Enrichment
This script enriches CVEs published by Palo Alto Networks with detailed vulnerability information from the official Palo Alto Networks Security Advisories website https://security.paloaltonetworks.com.
Description
The script retrieves comprehensive vulnerability data including CVSS scores, affected products, version information, exploits, workarounds, and solutions from Palo Alto Networks’ security advisories. It supports both CVE identifiers and PAN-SA advisory IDs, automatically determining the appropriate data source and format.
Inputs
| Argument Name | Description | Required |
|---|---|---|
| cve_id | CVE ID(s) or PAN-SA advisory ID(s) to enrich (array) | Required |
Outputs
| Path | Type | Description |
|---|---|---|
| PANOSSecurityAdvisories.Advisory.cve_id | String | CVE ID |
| PANOSSecurityAdvisories.Advisory.title | String | CVE Title |
| PANOSSecurityAdvisories.Advisory.description | String | Vulnerability description |
| PANOSSecurityAdvisories.Advisory.cve_url | String | Link to the PANW Security Advisories page |
| PANOSSecurityAdvisories.Advisory.cvss_score | Number | Base score of CVE |
| PANOSSecurityAdvisories.Advisory.cvss_severity | String | Base severity of CVE (LOW, MEDIUM, HIGH, CRITICAL) |
| PANOSSecurityAdvisories.Advisory.cvethreatscore | Number | Threat Score of the CVE |
| PANOSSecurityAdvisories.Advisory.cvethreatseverity | String | Threat Severity of CVE (LOW, MEDIUM, HIGH, CRITICAL) |
| PANOSSecurityAdvisories.Advisory.cvss_vector_string | String | CVSS Vector indicating metrics of attack |
| PANOSSecurityAdvisories.Advisory.cvss_table | Unknown | Metrics of the vulnerability |
| PANOSSecurityAdvisories.Advisory.affected_list | Unknown | List of affected products, their versions and changes introduced with fixes |
| PANOSSecurityAdvisories.Advisory.cveproductstatus | Unknown | List of affected products with platform information and fixed versions |
| PANOSSecurityAdvisories.Advisory.cpes | Unknown | Affected products defined by CPE |
| PANOSSecurityAdvisories.Advisory.published_date | Date | Date when it was published to the advisories page |
| PANOSSecurityAdvisories.Advisory.last_updated_date | Date | Date when it was last updated on the advisories page |
| PANOSSecurityAdvisories.Advisory.solution | String | Solution provided for the CVE |
| PANOSSecurityAdvisories.Advisory.workaround | String | Workaround for the CVE |
| PANOSSecurityAdvisories.Advisory.configurations | String | Required configurations for exploit |
| PANOSSecurityAdvisories.Advisory.exploits | String | Known exploits of this vulnerability in the field |
| PANOSSecurityAdvisories.Advisory.impact | String | Impact description of the vulnerability |
| PANOSSecurityAdvisories.Advisory.external_cve_list | Unknown | If input CVE is a PAN-SA advisory then list of related non-PANW CVEs |
Context Example
{
"PANOSSecurityAdvisories": {
"Advisory": {
"cve_id": "CVE-2024-1234",
"title": "OS Command Injection Vulnerability in PAN-OS",
"description": "A command injection vulnerability in PAN-OS allows...",
"cve_url": "https://security.paloaltonetworks.com/CVE-2024-1234",
"cvss_score": 9.8,
"cvss_severity": "CRITICAL",
"cvethreatscore": 9.8,
"cvethreatseverity": "CRITICAL",
"cvss_vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"affected_list": [
{
"product": "PAN-OS",
"platforms": [""],
"versions": [
{
"version": "10.2.0",
"lessThan": "10.2.4-h16",
"status": "affected",
"changes": [
{
"at": "10.2.4-h16",
"status": "unaffected"
}
]
}
]
}
],
"published_date": "2024-04-10T16:00:00.000Z",
"last_updated_date": "2024-04-10T16:00:00.000Z",
"solution": "This issue is fixed in PAN-OS 10.2.4-h16, PAN-OS 11.0.1, and all later PAN-OS versions.",
"workaround": "Enable Threat Prevention on all security rules...",
"external_cve_list": []
}
}
}
Human Readable Output
| Field | Value |
|---|---|
| CVE ID | CVE-2024-1234 |
| Title | OS Command Injection Vulnerability in PAN-OS |
| CVSS Score | 9.8 |
| Severity | CRITICAL |
| Published Date | 2024-04-10T16:00:00.000Z |
| Solution | This issue is fixed in PAN-OS 10.2.4-h16, PAN-OS 11.0.1, and all later PAN-OS versions. |
Notes
- The script automatically handles both CVE and PAN-SA format inputs.
- For PAN-SA advisories, the script attempts to retrieve additional CSAF (Common Security Advisory Framework) data.
- Version information is parsed and sorted to provide clear affected/unaffected status.
- CVSS metrics are prioritized by score (highest first) when multiple metrics are available.
- External CVE references are included for PAN-SA advisories when available.
- The script connects to the Palo Alto Networks Security Advisories page for data retrieval.
import pytest from unittest.mock import Mock, patch from CommonServerPython import * # Import the module under test from PANOSSecurityAdvisoriesEnrichment import ( Client, enrich_cve, flatten_advisory_dict, parse_version, sort_versions_and_changes, get_external_cves, ) # Global fixtures that can be used across all test classes @pytest.fixture def mock_client(): """Create a mock client for testing""" client = Mock(spec=Client) return client @pytest.fixture def sample_cve_data(): """Sample CVE data response""" return { "cveMetadata": {"cveId": "CVE-2072-1234", "datePublished": "2023-01-15T00:00:00Z"}, "containers": { "cna": { "title": "Sample CVE Title", "descriptions": [{"value": "Sample CVE description for testing purposes"}], "metrics": [ { "cvssV3_1": { "baseScore": 7.5, "baseSeverity": "HIGH", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", } } ], "affected": [ { "product": "PAN-OS", "platforms": ["Panorama"], "versions": [{"version": "10.0.0", "status": "affected", "lessThan": "10.0.5"}], } ], "workarounds": [{"value": "Apply the latest updates"}], "solutions": [{"value": "Upgrade to the latest version"}], "providerMetadata": {"dateUpdated": "2023-01-20T00:00:00Z"}, } }, } @pytest.fixture def sample_pan_sa_data(): """Sample PAN-SA CSAF data response""" return { "vulnerabilities": [ { "cve": "CVE-2023-5678", "references": [{"category": "external", "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-5678"}], "notes": [{"category": "description", "text": "External CVE description"}], } ] } class TestClient: """Test class for the Client class""" def test_client_initialization(self): """Test client initialization with correct URLs""" client = Client() assert client.base_url == "https://security.paloaltonetworks.com" assert client.advisories_url == "https://security.paloaltonetworks.com/json/" assert client.csaf_url == "https://security.paloaltonetworks.com/csaf/" @patch("requests.get") def test_get_cve_success(self, mock_get): """Test successful CVE retrieval""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = {"cveId": "CVE-2023-1234"} mock_get.return_value = mock_response client = Client() result = client.get_cve("CVE-2023-1234") assert result == {"cveId": "CVE-2023-1234"} mock_get.assert_called_once_with("https://security.paloaltonetworks.com/json/CVE-2023-1234") @patch("PANOSSecurityAdvisoriesEnrichment.requests.get") def test_get_cve_error(self, mock_get): """Test error handling in get_cve""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = {"error": "no such entry"} mock_get.return_value = mock_response client = Client() result = client.get_cve("INVALID-CVE") # Verify that return_results was called with the expected message assert result == {"error": "no such entry"} mock_get.assert_called_once_with(f"{client.advisories_url}INVALID-CVE") @patch("requests.get") def test_get_pan_sa_advisories_success(self, mock_get): """Test successful PAN-SA advisory retrieval""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = {"vulnerabilities": []} mock_get.return_value = mock_response client = Client() result = client.get_pan_sa_advisories("PAN-SA-2023-0001") assert result == {"vulnerabilities": []} @patch("requests.get") def test_get_pan_sa_advisories_404(self, mock_get): """Test 404 handling in get_pan_sa_advisories""" mock_response = Mock() mock_response.status_code = 404 mock_get.return_value = mock_response client = Client() result = client.get_pan_sa_advisories("PAN-SA-2023-0001") assert result == "CSAF not available for PAN-SA-2023-0001" class TestEnrichCVE: """Test class for the enrich_cve function""" def test_enrich_cve_standard_cve(self, mock_client, sample_cve_data): """Test enriching a standard CVE (not PAN-SA)""" # Setup cve_id = "CVE-2072-1234" mock_client.get_cve.return_value = sample_cve_data # Execute result = enrich_cve(mock_client, cve_id) # Assert assert isinstance(result, dict) assert result["Type"] == entryTypes["note"] assert result["ContentsFormat"] == formats["json"] # Check EntryContext structure assert "PANOSSecurityAdvisories.Advisory" in result["EntryContext"] advisory = result["EntryContext"]["PANOSSecurityAdvisories.Advisory"] # Assert specific advisory content assert advisory["cve_id"] == "CVE-2072-1234" assert advisory["title"] == "Sample CVE Title" assert advisory["description"] == "Sample CVE description for testing purposes" assert advisory["cvss_score"] == 7.5 assert advisory["cvss_severity"] == "HIGH" assert advisory["external_cve_list"] == [] # Should be empty for non-PAN-SA # Verify client was called correctly mock_client.get_cve.assert_called_once_with(cve_id) def test_enrich_cve_pan_sa_with_fixture(self, mock_client, sample_cve_data, sample_pan_sa_data): """Test enriching a PAN-SA CVE""" # Setup cve_id = "PAN-SA-2023-0001" mock_client.get_cve.return_value = sample_cve_data mock_client.get_pan_sa_advisories.return_value = sample_pan_sa_data # Execute result = enrich_cve(mock_client, cve_id) # Assert assert isinstance(result, dict) advisory = result["EntryContext"]["PANOSSecurityAdvisories.Advisory"] # Should contain the external CVE from sample_pan_sa_data assert len(advisory["external_cve_list"]) == 1 assert advisory["external_cve_list"][0]["id"] == "CVE-2023-5678" assert advisory["external_cve_list"][0]["link"] == "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-5678" assert advisory["external_cve_list"][0]["description"] == "External CVE description" # Verify both client methods were called mock_client.get_cve.assert_called_once_with(cve_id) mock_client.get_pan_sa_advisories.assert_called_once_with(cve_id) def test_enrich_cve_invalid_cve(self, mock_client): """Test handling of error when CVE not found""" cve_id = "CVE-2023-INVALID" mock_client.get_cve.side_effect = {"error": "no such entry"} result = enrich_cve(mock_client, cve_id) assert isinstance(result, dict) assert result["Contents"] == {"error": "This is not a valid Palo Alto Networks CVE ID"} class TestVersionParsing: """Test class for version parsing functions""" @pytest.mark.parametrize( "version,expected", [ ("10.0.0", (10, 0, 0, 0)), ("9.1.5-h1", (9, 1, 5, 1)), ("11.2.3-h10", (11, 2, 3, 10)), ("invalid-version", (0, 0, 0, 0)), ("", (0, 0, 0, 0)), ], ) def test_parse_version(self, version, expected): """Test version parsing with various inputs""" result = parse_version(version) assert result == expected def test_sort_versions_and_changes(self): """Test sorting of versions and changes""" data = [ { "versions": [ {"version": "10.0.0", "changes": [{"at": "10.0.5"}, {"at": "10.0.2"}]}, {"version": "9.1.0"}, {"version": "11.0.0"}, ] } ] result = sort_versions_and_changes(data) # Should be sorted in descending order versions = result[0]["versions"] assert versions[0]["version"] == "11.0.0" assert versions[1]["version"] == "10.0.0" assert versions[2]["version"] == "9.1.0" class TestFlattenAdvisoryDict: """Test class for flatten_advisory_dict function""" def test_flatten_advisory_dict_empty_data(self): """Test with empty or malformed data""" result = flatten_advisory_dict({}, []) assert result == {} def test_flatten_advisory_dict_missing_fields(self): """Test with missing optional fields""" minimal_data = {"cveMetadata": {"cveId": "CVE-2023-1234"}, "containers": {"cna": {}}} result = flatten_advisory_dict(minimal_data, []) assert result["cve_id"] == "CVE-2023-1234" assert result["title"] == "" assert result["description"] == "" assert result["cvss_score"] is None def test_flatten_advisory_dict_multiple_metrics(self): """Test with multiple CVSS metrics (should pick highest score)""" data_with_multiple_metrics = { "cveMetadata": {"cveId": "CVE-2023-1234"}, "containers": { "cna": { "metrics": [ {"cvssV3_1": {"baseScore": 5.0, "baseSeverity": "MEDIUM"}}, {"cvssV3_1": {"baseScore": 9.0, "baseSeverity": "CRITICAL"}}, {"cvssV3_1": {"baseScore": 7.0, "baseSeverity": "HIGH"}}, ] } }, } result = flatten_advisory_dict(data_with_multiple_metrics, []) assert result["cvss_score"] == 9.0 assert result["cvss_severity"] == "CRITICAL" class TestGetExternalCves: """Test class for get_external_cves function""" def test_get_external_cves_invalid_format(self, mock_client): """Test with invalid PAN-SA ID format""" with pytest.raises(ValueError, match="Invalid PAN-SA ID format"): get_external_cves(mock_client, "INVALID-FORMAT") def test_get_external_cves_csaf_not_available(self, mock_client): """Test when CSAF data is not available""" mock_client.get_pan_sa_advisories.return_value = "CSAF not available for PAN-SA-2023-0001" result = get_external_cves(mock_client, "PAN-SA-2023-0001") assert result == [] def test_get_external_cves_no_vulnerabilities(self, mock_client): """Test with response containing no vulnerabilities""" mock_client.get_pan_sa_advisories.return_value = {"other_data": "value"} result = get_external_cves(mock_client, "PAN-SA-2023-0001") assert result == [] def test_get_external_cves_incomplete_data(self, mock_client): """Test with incomplete vulnerability data""" incomplete_data = { "vulnerabilities": [ {"cve": "CVE-2023-1234"}, # Missing references and notes { # Missing CVE ID "references": [{"category": "external", "url": "http://example.com"}], "notes": [{"category": "description", "text": "Description"}], }, ] } mock_client.get_pan_sa_advisories.return_value = incomplete_data result = get_external_cves(mock_client, "PAN-SA-2023-0001") assert result == [] # Should return empty list for incomplete data