CheckFirewallAndGPForCVEs
- Type
- python
- Pack
- PAN-OS
Source
import demistomock as demisto # noqa: F401
from CommonServerPython import * # noqa: F401
import traceback
import re
import itertools
def version_compare(v1: str, v2: str) -> int:
"""
Compare two version strings and return their relative ordering.
This function compares two version strings by parsing them into numeric components
and performing a lexicographic comparison. It handles PAN-OS version formats that
use dots (.) and hyphens (-) as separators.
Args:
v1 (str): The first version string to compare (e.g., "10.2.3", "11.0.1-h2").
v2 (str): The second version string to compare (e.g., "10.2.4", "11.0.0").
Returns:
int:
-1 if v1 < v2 (v1 is older than v2)
0 if v1 == v2 (versions are equal)
1 if v1 > v2 (v1 is newer than v2)
Note:
- Versions are split on dots (.) and hyphens (-) into components
- Only the first 3 components are considered for comparison
- Non-numeric components are treated as 0
- Missing components are treated as 0 (e.g., "10.2" is treated as "10.2.0")
Examples:
version_compare("10.2.3", "10.2.4") returns -1
version_compare("11.0.0", "10.2.9") returns 1
version_compare("10.2.3", "10.2.3") returns 0
version_compare("10.2.3-h1", "10.2.3-h2") returns 0 (hotfix part ignored)
"""
v1_parts = [int(x) if x.isdigit() else 0 for x in re.split(r"[\.\-]", v1)[:3]]
v2_parts = [int(x) if x.isdigit() else 0 for x in re.split(r"[\.\-]", v2)[:3]]
for v1_part, v2_part in itertools.zip_longest(v1_parts, v2_parts, fillvalue=0):
if v1_part < v2_part:
return -1
elif v1_part > v2_part:
return 1
return 0
""" FUNCTION TO CHECK IF THE PAN-OS VERSION IS AFFECTED BY ANY CVE"""
def check_fw_version(
fw_versions_checked: list[str], pan_os_affected_versions: list[dict], pan_os_system_info: dict[str, str]
) -> bool: # noqa: E501
"""
Check if a PAN-OS firewall version is affected by a CVE.
This function determines whether the PAN-OS software version from the system
information is affected by a given CVE by comparing it against the affected
version ranges and applying any status changes (hotfixes/patches). It implements
the algorithm provided on https://cveproject.github.io/cve-schema/schema/docs.
Args:
fw_versions_checked (list[str]): A list to track PAN-OS versions that
have already been checked. The current version will be appended to this
list if it falls back to the default status check.
pan_os_affected_versions (list[dict]): A list of dictionaries containing affected
version information for PAN-OS. Each dictionary should contain:
- 'versions' (list): List of version entries with status information
- 'defaultStatus' (str): Default status if version doesn't match any entry
pan_os_system_info (dict[str, str]): Dictionary containing system information
with 'sw_version' key containing the PAN-OS software version to check.
Returns:
bool: True if the PAN-OS version is affected by the CVE, False otherwise.
Note:
- Version entries can contain exact matches or range specifications using
'lessThan'/'lessThanOrEqual' fields
- Status changes (hotfixes) are applied in chronological order, with hotfix
versions (containing '-') requiring exact matches
- If no matching version entry is found, the defaultStatus is used
- An empty string is used as fallback if 'sw_version' is not found in system info
"""
v = pan_os_system_info.get("sw_version", "")
fw_versions_checked.append(v)
product_versions = pan_os_affected_versions[0].get("versions", []) if len(pan_os_affected_versions) > 0 else []
default_status = (
pan_os_affected_versions[0].get("defaultStatus", "unknown") if len(pan_os_affected_versions) > 0 else "unknown"
)
for entry in product_versions:
if "lessThan" not in entry and "lessThanOrEqual" not in entry and v == entry["version"]:
return entry["status"] == "affected"
if ("lessThan" in entry and version_compare(entry["version"], v) <= 0 and version_compare(v, entry["lessThan"]) < 0) or (
"lessThanOrEqual" in entry
and version_compare(entry["version"], v) <= 0
and version_compare(v, entry["lessThanOrEqual"]) <= 0
):
status = entry["status"]
for change in entry.get("changes", []):
if "-" in change["at"]: # Check if change['at'] is a hotfix
if change["at"] == v: # Exact match for hotfix
status = change["status"]
break
elif version_compare(change["at"], v) <= 0:
status = change["status"]
return status == "affected"
return default_status == "affected"
def check_gp_version(gp_version_checked: list[str], gp_affected_versions: list[dict], pan_os_system_info: dict[str, str]) -> bool: # noqa: E501
"""
Check if a GlobalProtect version is affected by a CVE.
This function determines whether the GlobalProtect client package version from the
system information is affected by a given CVE by comparing it against the affected
version ranges and applying any status changes (hotfixes/patches). It implements
the algorithm provided on https://cveproject.github.io/cve-schema/schema/docs.
Args:
gp_version_checked (list[str]): A list to track GlobalProtect versions that
have already been checked. The current version will be appended to this
list if it falls back to the default status check.
gp_affected_versions (list[dict]): A list of dictionaries containing affected
version information for GlobalProtect. Each dictionary should contain:
- 'versions' (list): List of version entries with status information
- 'defaultStatus' (str): Default status if version doesn't match any entry
pan_os_system_info (dict[str, str]): Dictionary containing system information
with 'global_protect_client_package_version' key containing the version
to check.
Returns:
bool: True if the GlobalProtect version is affected by the CVE, False otherwise.
Note:
- Version entries can contain exact matches or range specifications using
'lessThan'/'lessThanOrEqual' fields
- Status changes (hotfixes) are applied in chronological order, with hotfix
versions (containing '-') requiring exact matches
- If no matching version entry is found, the defaultStatus is used
"""
v = pan_os_system_info.get("global_protect_client_package_version", "")
gp_version_checked.append(v)
product_versions = gp_affected_versions[0].get("versions", []) if gp_affected_versions else []
default_status = gp_affected_versions[0].get("defaultStatus", "unknown") if gp_affected_versions else "unknown"
for entry in product_versions:
if "lessThan" not in entry and "lessThanOrEqual" not in entry and v == entry["version"]:
return entry["status"] == "affected"
if ("lessThan" in entry and version_compare(entry["version"], v) <= 0 and version_compare(v, entry["lessThan"]) < 0) or (
"lessThanOrEqual" in entry
and version_compare(entry["version"], v) <= 0
and version_compare(v, entry["lessThanOrEqual"]) <= 0
):
status = entry["status"]
for change in entry.get("changes", []):
if "-" in change["at"]: # Check if change['at'] is a hotfix
if change["at"] == v: # Exact match for hotfix
status = change["status"]
break
elif version_compare(change["at"], v) <= 0:
status = change["status"]
return status == "affected"
return default_status == "affected"
def main():
args = demisto.args()
pan_os_system_info_list = argToList(args.get("pan_os_system_info_list"))
cve_json_list = argToList(args.get("cve_json"))
try:
for cve_json in cve_json_list:
fw_versions_checked: list[str] = []
gp_version_checked: list[str] = []
result = {
"CVE_ID": cve_json.get("cve_id", ""),
"Severity": cve_json.get("cvethreatseverity") or cve_json.get("cvss_severity"),
"Result": [],
}
affected_versions_in_cve = cve_json.get("affected_list", [])
pan_os_affected_versions = [item for item in affected_versions_in_cve if item.get("product") == "PAN-OS"]
gp_affected_versions = [item for item in affected_versions_in_cve if item.get("product") == "GlobalProtect App"]
if len(pan_os_affected_versions) == 0 and len(gp_affected_versions) == 0:
# return_results("CVE is not applicable to PAN-OS or GlobalProtect")
for firewall in pan_os_system_info_list:
result["Result"].append(
{
"Hostname": firewall.get("hostname", ""),
"IPAddress": firewall.get("ip_address", ""),
"SWVersion": firewall.get("sw_version", ""),
"IsFirewallVersionAffected": False,
"GlobalProtectVersion": firewall.get("global_protect_client_package_version", ""),
"IsGlobalProtectVersionAffected": False,
}
)
else:
for firewall in pan_os_system_info_list:
""" USE fw_versions_checked TO SEE IF THIS VERSION WAS ALREADY CHECKED """
if firewall.get("sw_version") in fw_versions_checked:
IsFirewallVersionAffected = [
r.get("IsFirewallVersionAffected")
for r in result["Result"]
if r.get("SWVersion") == firewall.get("sw_version")
][0]
else:
IsFirewallVersionAffected = check_fw_version(fw_versions_checked, pan_os_affected_versions, firewall)
""" USE gp_version_checked TO SEE IF THIS VERSION WAS ALREADY CHECKED """
if (
firewall.get("global_protect_client_package_version", "") in gp_version_checked
and len(result["Result"]) != 0
):
IsGlobalProtectVersionAffected = [
r.get("IsGlobalProtectVersionAffected")
for r in result["Result"]
if r.get("GlobalProtectVersion") == firewall.get("global_protect_client_package_version", "")
][0]
else:
IsGlobalProtectVersionAffected = check_gp_version(gp_version_checked, gp_affected_versions, firewall)
result["Result"].append(
{
"Hostname": firewall.get("hostname", ""),
"IPAddress": firewall.get("ip_address", ""),
"SWVersion": firewall.get("sw_version", ""),
"IsFirewallVersionAffected": IsFirewallVersionAffected,
"GlobalProtectVersion": firewall.get("global_protect_client_package_version", ""),
"IsGlobalProtectVersionAffected": IsGlobalProtectVersionAffected,
}
)
context_output = result
str_result = [{k: str(v) if isinstance(v, bool) else v for k, v in item.items()} for item in result["Result"]]
md = tableToMarkdown(
result["CVE_ID"],
str_result,
headers=[
"Hostname",
"IPAddress",
"SWVersion",
"IsFirewallVersionAffected",
"GlobalProtectVersion",
"IsGlobalProtectVersionAffected",
],
is_auto_json_transform=True,
)
command_results = CommandResults(
outputs_prefix="CVE_Check",
outputs_key_field="",
outputs=context_output,
raw_response=context_output,
readable_output=md,
)
return_results(command_results)
except Exception as ex:
# print the traceback
demisto.error(traceback.format_exc())
return_error(f"Failed to execute CheckFirewallVersionForCVEs_feed. Error: {str(ex)}")
""" ENTRY POINT """
if __name__ in ("__main__", "__builtin__", "builtins"):
main()
README
CheckFirewallAndGPForCVEs
This script checks if PAN-OS firewall and GlobalProtect versions are affected by specific CVEs (Common Vulnerabilities and Exposures).
Description
The script analyzes firewall system information against CVE data to determine if the current PAN-OS software version or GlobalProtect client package version is vulnerable to known security issues. It implements the CVE schema algorithm for version comparison and status determination.
Inputs
| Argument Name | Description | Required |
|---|---|---|
| pan_os_system_info_list | System info of firewalls (array). Output of pan-os-platform-get-system-info command | Required |
| cve_json | List of CVE with detailed json (array). Output of PAN_OS_Security_Advisories_Enrichment script | Required |
pan_os_system_info_list Format
Each firewall entry should contain:
hostname: Firewall hostnameip_address: Firewall IP addresssw_version: PAN-OS software versionglobal_protect_client_package_version: GlobalProtect client version (optional)
cve_json Format
Each CVE entry should contain:
cve_id: CVE identifiercvethreatseverityorcvss_severity: CVE severity levelaffected_list: List of affected products with version information
Outputs
| Path | Type | Description |
|---|---|---|
| CVE_Check.CVE_ID | String | The CVE identifier |
| CVE_Check.Result.Hostname | String | Firewall hostname |
| CVE_Check.Result.IPAddress | String | Firewall IP address |
| CVE_Check.Result.SWVersion | String | PAN-OS software version |
| CVE_Check.Result.IsFirewallVersionAffected | Boolean | Whether PAN-OS version is affected by the CVE |
| CVE_Check.Result.GlobalProtectVersion | String | GlobalProtect client version |
| CVE_Check.Result.IsGlobalProtectVersionAffected | Boolean | Whether GlobalProtect version is affected by the CVE |
| CVE_Check.Severity | String | CVE severity level |
Context Example
{
"CVE_Check":{
"CVE_ID": "CVE-2072-1234",
"Result": [
{
"Hostname": "fw-affected",
"IPAddress": "1.1.1.1",
"SWVersion": "10.2.3",
"IsFirewallVersionAffected": true,
"GlobalProtectVersion": "6.0.1",
"IsGlobalProtectVersionAffected": true
},
{
"Hostname": "fw-patched",
"IPAddress": "2.2.2.2",
"SWVersion": "10.2.3-h2",
"IsFirewallVersionAffected": false,
"GlobalProtectVersion": "6.0.3",
"IsGlobalProtectVersionAffected": false
}],
"Severity": "MEDIUM"
}
}
Human Readable Output
CVE-2072-1234
| Hostname | IPAddress | SWVersion | IsFirewallVersionAffected | GlobalProtectVersion | IsGlobalProtectVersionAffected |
|---|---|---|---|---|---|
| fw-affected | 1.1.1.1 | 10.2.3 | True | 6.0.1 | True |
| fw-patched | 2.2.2.2 | 10.2.3-2 | False | 6.0.3 | False |
Notes
- The script implements the CVE schema algorithm from https://cveproject.github.io/cve-schema/schema/docs.
- Hotfix versions require exact matches for status changes.
- If a CVE doesn’t apply to the PAN-OS software version or the GlobalProtect client package version, the output will specify the affected status on these 2 versions. For any other installed package versions, please inspect the CVE details.