Skip to content

Commit ac1a7e8

Browse files
feat: Add CSAF Generation (Fixes intel#2401)
1 parent 9e7c281 commit ac1a7e8

File tree

5 files changed

+351
-0
lines changed

5 files changed

+351
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ Output:
218218
Lists backported fixes if available from Linux distribution
219219
<a href="https://github.com/intel/cve-bin-tool/blob/main/doc/MANUAL.md#--affected-versions">--affected-versions</a> Lists versions of product affected by a given CVE (to facilitate upgrades)
220220
<a href="https://github.com/intel/cve-bin-tool/blob/main/doc/MANUAL.md#--vex-vex_file">--vex VEX</a> Provide vulnerability exchange (vex) filename
221+
<a href="https://github.com/intel/cve-bin-tool/blob/main/doc/MANUAL.md#--csaf-csaf_file">--csaf CSAF</a> Provide Common Security Advisory Framework (CSAF) filename
221222

222223
Merge Report:
223224
Arguments related to Intermediate and Merged Reports

cve_bin_tool/cli.py

+8
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,13 @@ def main(argv=None):
285285
default="",
286286
)
287287

288+
output_group.add_argument(
289+
"--csaf",
290+
action="store",
291+
help="Provide Common Security Advisory Framework (CSAF) filename",
292+
default="",
293+
)
294+
288295
parser.add_argument(
289296
"-e",
290297
"--exclude",
@@ -828,6 +835,7 @@ def main(argv=None):
828835
exploits=args["exploits"],
829836
detailed=args["detailed"],
830837
vex_filename=args["vex"],
838+
csaf_filename=args["csaf"],
831839
)
832840

833841
if not args["quiet"]:

cve_bin_tool/output_engine/__init__.py

+184
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ def __init__(
531531
all_cve_version_info=None,
532532
detailed: bool = False,
533533
vex_filename: str = "",
534+
csaf_filename: str = "",
534535
exploits: bool = False,
535536
all_product_data=None,
536537
):
@@ -551,6 +552,7 @@ def __init__(
551552
self.all_cve_data = all_cve_data
552553
self.detailed = detailed
553554
self.vex_filename = vex_filename
555+
self.csaf_filename = csaf_filename
554556
self.exploits = exploits
555557
self.all_product_data = all_product_data
556558

@@ -627,6 +629,9 @@ def output_cves(self, outfile, output_type="console"):
627629
if self.vex_filename != "":
628630
self.generate_vex(self.all_cve_data, self.vex_filename)
629631

632+
if self.csaf_filename != "":
633+
self.generate_csaf(self.all_cve_data, self.csaf_filename)
634+
630635
def generate_vex(self, all_cve_data: Dict[ProductInfo, CVEData], filename: str):
631636
analysis_state = {
632637
Remarks.NewFound: "under_review",
@@ -710,6 +715,185 @@ def generate_vex(self, all_cve_data: Dict[ProductInfo, CVEData], filename: str):
710715
with open(filename, "w") as outfile:
711716
json.dump(vex_output, outfile, indent=" ")
712717

718+
def generate_csaf(self, all_cve_data: Dict[ProductInfo, CVEData], filename: str):
719+
time_now = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
720+
header = dict()
721+
header["category"] = "csaf_vex"
722+
header["csaf_version"] = "2.0"
723+
note_info = dict()
724+
note_info["category"] = "summary"
725+
note_info["text"] = "Auto generated CSAF document"
726+
note_info["title"] = "Summary of identified vulnerabilities."
727+
header["notes"] = [note_info]
728+
publisher_info = dict()
729+
publisher_info["category"] = "other"
730+
publisher_info["name"] = "CVE BIN TOOL"
731+
publisher_info["namespace"] = "https://github.com/intel/cve-bin-tool"
732+
header["publisher"] = publisher_info
733+
header["title"] = "CSAF Document - Affected"
734+
tracking_info = dict()
735+
tracking_info["current_release_date"] = time_now
736+
# Tracking id is based on filename, appended with time and date
737+
tracking_info["id"] = Path(filename).stem.upper() + datetime.now().strftime(
738+
"%Y%m%d%H%M%S"
739+
)
740+
tracking_info["initial_release_date"] = time_now
741+
revision_info = dict()
742+
revision_info["date"] = time_now
743+
revision_info["number"] = "1"
744+
revision_info["summary"] = "Initial version"
745+
tracking_info["revision_history"] = [revision_info]
746+
tracking_info["status"] = "final"
747+
tracking_info["version"] = "1"
748+
generator_info = dict()
749+
generator_info["date"] = time_now
750+
generator_engine = dict()
751+
generator_engine["name"] = "cve_bin_tool"
752+
generator_engine["version"] = VERSION
753+
generator_info["engine"] = generator_engine
754+
tracking_info["generator"] = generator_info
755+
header["tracking"] = tracking_info
756+
header["lang"] = "en-US"
757+
758+
# Build up a product tree
759+
760+
product_tree = dict()
761+
vendor_info = dict()
762+
product_info = dict()
763+
vendor_info["branches"] = []
764+
product_info["branches"] = []
765+
product_tree["branches"] = []
766+
767+
product_id_list = dict()
768+
769+
product_id = 0
770+
771+
for product_info, cve_data in all_cve_data.items():
772+
product = product_info.product
773+
vendor = product_info.vendor
774+
version = product_info.version
775+
# Process releases - only a single release for the product
776+
version_branch = []
777+
version_info = dict()
778+
version_info["category"] = "product_version"
779+
version_info["name"] = str(version)
780+
product_note = dict()
781+
product_note["product_id"] = "CSAFPID_" + str(product_id).zfill(4)
782+
product_note["name"] = vendor + " " + product + " " + version_info["name"]
783+
product_id += 1
784+
version_info["product"] = product_note
785+
product_id_list[product + "_" + version_info["name"]] = {
786+
"name": product_note["name"],
787+
"release": version_info["name"],
788+
"id": product_note["product_id"],
789+
}
790+
version_branch.append(version_info)
791+
# Then product name
792+
product_data = dict()
793+
product_data["category"] = "product_name"
794+
product_data["name"] = product
795+
product_data["branches"] = version_branch
796+
# And finally vendor
797+
vendor_info = dict()
798+
vendor_info["category"] = "vendor"
799+
vendor_info["name"] = vendor
800+
vendor_info["branches"] = [product_data]
801+
product_tree["branches"].append(vendor_info)
802+
803+
# Vulnerabilities
804+
vulnerabilities = []
805+
806+
for product_info, cve_data in all_cve_data.items():
807+
product_name = product_info.product
808+
version = product_info.version
809+
for cve in cve_data["cves"]:
810+
vulnerability = dict()
811+
vulnerability["cve"] = cve.cve_number
812+
note_info = dict()
813+
note_info["category"] = "description"
814+
note_info["text"] = cve.description
815+
note_info["title"] = "CVE description"
816+
vulnerability["notes"] = [note_info]
817+
product_data = dict()
818+
# At this stage don't know if product is affected
819+
product_data["under_investigation"] = []
820+
product_id = product_id_list[product_name + "_" + version]["id"]
821+
product_data["under_investigation"].append(product_id)
822+
vulnerability["product_status"] = product_data
823+
remediation_info = dict()
824+
# Default remediation. Following triage, the category should be
825+
# updated to describe how vulnerability is being handled.
826+
remediation_info["category"] = "vendor_fix"
827+
remediation_info[
828+
"details"
829+
] = f"Customers should consider updating the version of {product_name} in order to address the identified issue."
830+
product_id_info = []
831+
product_id_info.append(product_id)
832+
remediation_info["product_ids"] = product_id_info
833+
vulnerability["remediations"] = [remediation_info]
834+
# CSAF only supports CVSS 3 vectors
835+
if cve.cvss_version == 3:
836+
score_info = dict()
837+
score_info["products"] = product_id_info
838+
# Ignore first part of the vector string which contains CVSS version
839+
cvss_parameters = cve.cvss_vector.split("/")[1:]
840+
cvss_score = {
841+
"version": cve.cvss_vector.split(":")[1].split("/")[0],
842+
"baseScore": cve.score,
843+
"baseSeverity": cve.severity,
844+
"vectorString": cve.cvss_vector,
845+
}
846+
cvss_vector_names = {
847+
"AV": "attackVector",
848+
"AC": "attackComplexity",
849+
"PR": "privilegesRequired",
850+
"UI": "userInteraction",
851+
"S": "scope",
852+
"C": "confidentialityImpact",
853+
"I": "integrityImpact",
854+
"A": "availabilityImpact",
855+
}
856+
cvss_vector_values = {
857+
"N": "NONE",
858+
"L": "LOW",
859+
"M": "MEDIUM",
860+
"H": "HIGH",
861+
"R": "REQUIRED",
862+
"C": "CHANGED",
863+
"U": "UNCHANGED",
864+
}
865+
cvss_attack_values = {
866+
"N": "NETWORK",
867+
"A": "ADJACENT_NETWORK",
868+
"L": "LOCAL",
869+
"P": "PHYSICAL",
870+
}
871+
# Decode vector string
872+
for params in cvss_parameters:
873+
name = params.split(":")[0]
874+
value = params.split(":")[1]
875+
# Attack Vector string needs specific processing
876+
if name != "AV":
877+
cvss_score[cvss_vector_names[name]] = cvss_vector_values[
878+
value
879+
]
880+
else:
881+
cvss_score[cvss_vector_names[name]] = cvss_attack_values[
882+
value
883+
]
884+
score_info["cvss_v3"] = cvss_score
885+
vulnerability["scores"] = [score_info]
886+
vulnerabilities.append(vulnerability)
887+
888+
# Build up CSAF document
889+
csaf_document = dict()
890+
csaf_document["document"] = header
891+
csaf_document["product_tree"] = product_tree
892+
csaf_document["vulnerabilities"] = vulnerabilities
893+
894+
with open(filename, "w") as outfile:
895+
json.dump(csaf_document, outfile, indent=" ")
896+
713897
def output_file_wrapper(self, output_types=["console"]):
714898
for output_type in output_types:
715899
self.output_file(output_type)

doc/MANUAL.md

+8
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
- [-b [<distro_name>-<distro_version_name>], --backport-fix [<distro_name>-<distro_version_name>]](#-b-distro_name-distro_version_name---backport-fix-distro_name-distro_version_name)
3939
- [--affected-versions](#--affected-versions)
4040
- [--vex VEX_FILE](#--vex-vex_file)
41+
- [--csaf CSAF_FILE](#--csaf-csaf_file)
4142
- [Output verbosity](#output-verbosity)
4243
- [Quiet Mode](#quiet-mode)
4344
- [Logging modes](#logging-modes)
@@ -126,6 +127,7 @@ which is useful if you're trying the latest code from
126127
Lists backported fixes if available from Linux distribution
127128
--affected-versions Lists versions of product affected by a given CVE (to facilitate upgrades)
128129
--vex VEX Provide vulnerability exchange (vex) filename
130+
--csaf CSAF Provide Common Security Advisory Framework (CSAF) filename
129131

130132
Merge Report:
131133
Arguments related to Intermediate and Merged Reports
@@ -902,6 +904,12 @@ file which contains all the reported vulnerabilities detected by the scan. This
902904
updated (outside of the CVE Binary tool) to record the results of a triage activity
903905
and can be used as a file with `--input-file` parameter.
904906

907+
### --csaf CSAF_FILE
908+
909+
This option allows you to specify the filename for a [Common Security Advisory Framework (CSAF) v2.0](https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=csaf)
910+
file which contains all the reported vulnerabilities detected by the scan.
911+
This file is typically updated (outside of the CVE Binary tool) to record the results of a triage activity.
912+
905913
### Output verbosity
906914

907915
As well as the modes above, there are two other output options to decrease or increase the number of messages printed:

0 commit comments

Comments
 (0)