-
Notifications
You must be signed in to change notification settings - Fork 120
tools: import a conversion tool to and from VuXML #237
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: main
Are you sure you want to change the base?
Changes from all commits
017174a
27caec0
10dcb14
9aea535
8d9615b
9f4a519
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| # VuXML advisory converter | ||
|
|
||
| This is relevant to FreeBSD's ports, and possibly any other project using VuXML | ||
| in order to track vulnerabilities. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| Clone the following repository: | ||
| - https://git.freebsd.org/ports.git | ||
|
|
||
| Install the following packages or modules: | ||
| - vuxml | ||
| - python-lxml | ||
|
|
||
| ## Running the converter | ||
|
|
||
| ### Usage | ||
|
|
||
| From VuXML to OSV format: | ||
|
|
||
| ``` | ||
| Usage: convert_vuxml.py [-e ecosystem][-o output_directory] path/to/vuln.xml | ||
| ``` | ||
|
|
||
| Where the VuXML vulnerabilities are either provided in a sequence of JSON data | ||
| on the standard output, or output to individual files in the output directory. | ||
|
|
||
| From OSV format to VuXML: | ||
|
|
||
| ``` | ||
| Usage: convert_osv.py [-o output_file] path/to/osv.json... | ||
| ``` | ||
|
|
||
| Where the OSV files provided are consolidated into a single VuXML file. | ||
|
|
||
| #### Options | ||
|
|
||
| `-e`: | ||
| Set a specific ecosystem in the converted output to OSV files (default: | ||
| FreeBSD:ports) | ||
|
|
||
| `-o`: | ||
| Output directory to place the converted OSV `.json` files (the directory must | ||
| exist and have write permissions), or output filename where to write the | ||
| converted VuXML file. | ||
|
|
||
| ### Example | ||
|
|
||
| ``` | ||
| $ python3.9 convert_vuxml.py /usr/ports/security/vuxml/vuln.xml | ||
| $ python3.9 convert_osv.py 002432c8-ef6a-11ea-ba8f-08002728f74c.json | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,272 @@ | ||
| #!/usr/bin/env python | ||
| # SPDX-License-Identifier: BSD-2-Clause | ||
| # | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just suggesting to have SPDX-header for license also
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for the suggestion, I have now added the SPDX headers.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SPDX header seems to be correct |
||
| # Copyright (C) 1994-2024 The FreeBSD Project. | ||
| # | ||
| # Redistribution and use in source and binary forms, with or without | ||
| # modification, are permitted provided that the following conditions | ||
| # are met: | ||
| # 1. Redistributions of source code must retain the above copyright | ||
| # notice, this list of conditions and the following disclaimer. | ||
| # 2. Redistributions in binary form must reproduce the above copyright | ||
| # notice, this list of conditions and the following disclaimer in the | ||
| # documentation and/or other materials provided with the distribution. | ||
| # | ||
| # THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND | ||
| # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
| # ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE | ||
| # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
| # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | ||
| # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | ||
| # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||
| # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | ||
| # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | ||
| # SUCH DAMAGE. | ||
| # | ||
| # Copyright (c) 2024 The FreeBSD Foundation | ||
| # | ||
| # Portions of this software were developed by Pierre Pronchery | ||
| # <pierre@defora.net> at Defora Networks GmbH under sponsorship | ||
| # from the FreeBSD Foundation. | ||
|
|
||
| """VuXML to OSV converter.""" | ||
| import getopt | ||
| import json | ||
| from lxml import etree | ||
| import sys | ||
|
|
||
| namespace_vuxml = "{http://www.vuxml.org/apps/vuxml-1}" | ||
| namespace_xhtml = "{http://www.w3.org/1999/xhtml}" | ||
|
|
||
| url_bid = "https://www.securityfocus.com/bid/" | ||
| url_certsa = "https://www.cert.org/advisories/" | ||
| url_certvu = "https://www.kb.cert.org/vuls/id/" | ||
| url_cve = "https://api.osv.dev/v1/vulns/" | ||
| url_freebsd_bugzilla = "https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=" | ||
| url_freebsd_sa = "https://www.freebsd.org/security/advisories/FreeBSD-" | ||
|
|
||
|
|
||
| # convert | ||
| def convert(filename, vuxml): | ||
| ret = 0 | ||
|
|
||
| try: | ||
| with open(filename, "r") as f: | ||
| j = json.load(f) | ||
| vuln = etree.Element("vuln", vid=j["id"]) | ||
| vuxml.append(vuln) | ||
|
|
||
| # topic | ||
| topic = etree.Element("topic") | ||
| if "summary" in j: | ||
| topic.text = j["summary"] | ||
| vuln.append(topic) | ||
|
|
||
| # description | ||
| if "details" in j: | ||
| description = etree.Element("description") | ||
| body = etree.Element(namespace_xhtml+"body") | ||
| body.text = j["details"] | ||
| description.append(body) | ||
| vuln.append(description) | ||
|
|
||
| # affects | ||
| if "affected" in j: | ||
| for affected in j["affected"]: | ||
| affects = None | ||
| package = None | ||
| if "package" in affected \ | ||
| and "name" in affected["package"]: | ||
| affects = etree.Element("affects") | ||
| package = etree.Element("package") | ||
| name = etree.Element("name") | ||
| name.text = affected["package"]["name"] | ||
| package.append(name) | ||
| affects.append(package) | ||
| if affects is not None \ | ||
| and "package" in affected \ | ||
| and "name" in affected["package"] \ | ||
| and "versions" in affected: | ||
| for version in affected["versions"]: | ||
| rnge = etree.Element("range") | ||
| eq = etree.Element("eq") | ||
| eq.text = version | ||
| rnge.append(eq) | ||
| package.append(rnge) | ||
| if affects is not None \ | ||
| and "package" in affected \ | ||
| and "name" in affected["package"] \ | ||
| and "ranges" in affected: | ||
| for r in affected["ranges"]: | ||
| if "type" in r \ | ||
| and r["type"] == "SEMVER" \ | ||
| and "events" in r: | ||
| rnge = etree.Element("range") | ||
| for event in r["events"]: | ||
| for k, v in event.items(): | ||
| if k == "introduced" and v != "0": | ||
| ge = etree.Element("ge") | ||
| ge.text = v | ||
| rnge.append(ge) | ||
| elif k == "fixed": | ||
| lt = etree.Element("lt") | ||
| lt.text = v | ||
| rnge.append(lt) | ||
| elif k == "last_affected": | ||
| le = etree.Element("le") | ||
| le.text = v | ||
| rnge.append(le) | ||
| if len(rnge) >= 1: | ||
| package.append(rnge) | ||
| if affects is not None: | ||
| vuln.append(affects) | ||
|
|
||
| # references | ||
| references = etree.Element("references") | ||
| if "references" in j: | ||
| for ref in j["references"]: | ||
| if ref["type"] == "ADVISORY": | ||
| if ref["url"].startswith(url_bid): | ||
| r = etree.Element("bid") | ||
| url = ref["url"][len(url_bid):] | ||
| if url.endswith("/info"): | ||
| url = url[:-5] | ||
| r.text = url | ||
| references.append(r) | ||
| elif ref["url"].startswith(url_freebsd_sa): | ||
| r = etree.Element("freebsdsa") | ||
| url = ref["url"][len(url_freebsd_sa):] | ||
| if url.endswith(".asc"): | ||
| url = url[:-4] | ||
| r.text = url | ||
| references.append(r) | ||
| elif ref["url"].startswith(url_certsa): | ||
| r = etree.Element("certsa") | ||
| url = ref["url"][len(url_certsa):] | ||
| if url.endswith(".html"): | ||
| url = url[:-5] | ||
| r.text = url | ||
| references.append(r) | ||
| elif ref["url"].startswith(url_certvu): | ||
| r = etree.Element("certvu") | ||
| r.text = ref["url"][len(url_certvu):] | ||
| references.append(r) | ||
| elif ref["url"].startswith(url_cve): | ||
| r = etree.Element("cvename") | ||
| r.text = ref["url"][len(url_cve):] | ||
| references.append(r) | ||
| else: | ||
| r = etree.Element("url") | ||
| r.text = ref["url"] | ||
| references.append(r) | ||
| elif ref["type"] == "REPORT": | ||
| if ref["url"].startswith(url_freebsd_bugzilla): | ||
| r = etree.Element("freebsdpr") | ||
| r.text = ref["url"][len(url_freebsd_bugzilla):] | ||
| references.append(r) | ||
| else: | ||
| r = etree.Element("url") | ||
| r.text = ref["url"] | ||
| references.append(r) | ||
| else: | ||
| r = etree.Element("url") | ||
| r.text = ref["url"] | ||
| references.append(r) | ||
| if len(references): | ||
| vuln.append(references) | ||
|
|
||
| # dates | ||
| dates = etree.Element("dates") | ||
| entry = j["modified"][0:10] | ||
| discovery = entry | ||
| modified = None | ||
| if "published" in j: | ||
| modified = entry | ||
| entry = j["published"][0:10] | ||
| if "database_specific" in j \ | ||
| and "discovery" in j["database_specific"]: | ||
| discovery = j["database_specific"]["discovery"][0:10] | ||
| date = etree.Element("discovery") | ||
| date.text = discovery | ||
| dates.append(date) | ||
| date = etree.Element("entry") | ||
| date.text = entry | ||
| dates.append(date) | ||
| if modified is not None: | ||
| date = etree.Element("modified") | ||
| date.text = modified | ||
| dates.append(date) | ||
| vuln.append(dates) | ||
|
|
||
| # cancelled | ||
| if "withdrawn" in dates: | ||
| cancelled = etree.Element("cancelled") | ||
| vuln.append(cancelled) | ||
| except Exception as e: | ||
| ret = error(e) | ||
| return ret | ||
|
|
||
|
|
||
| # error | ||
| def error(string): | ||
| print(f"{sys.argv[0]}: error: {string}", file=sys.stderr) | ||
| return 2 | ||
|
|
||
|
|
||
| # usage | ||
| def usage(e=None): | ||
| if e is not None: | ||
| print(e, file=sys.stderr) | ||
| print("Usage: %s [-o output.xml] vuln.json..." | ||
| % sys.argv[0], file=sys.stderr) | ||
| return 1 | ||
|
|
||
|
|
||
| # warn | ||
| def warn(string): | ||
| print(f"{sys.argv[0]}: warning: {string}", file=sys.stderr) | ||
|
|
||
|
|
||
| # main | ||
| def main(): | ||
| ret = 0 | ||
|
|
||
| try: | ||
| opts, args = getopt.getopt(sys.argv[1:], "o:") | ||
| except getopt.GetoptError as e: | ||
| return usage(e) | ||
| output = None | ||
| for name, optarg in opts: | ||
| if name == "-o": | ||
| output = optarg | ||
| else: | ||
| return usage("%s: Unsupported option" % name) | ||
|
|
||
| if len(args) < 1: | ||
| return usage() | ||
|
|
||
| vuxml = etree.Element(namespace_vuxml+"vuxml") | ||
| for arg in args: | ||
| if convert(arg, vuxml) != 0: | ||
| ret = 2 | ||
| break | ||
|
|
||
| if ret == 0: | ||
| try: | ||
| xml = etree.tostring(vuxml, pretty_print=True) | ||
| if output is not None: | ||
| with open(output, "w") as f: | ||
| print("""<?xml version="1.0" encoding="utf-8"?> | ||
| <!DOCTYPE vuxml PUBLIC "-//vuxml.org//DTD VuXML 1.1//EN" "http://www.vuxml.org/dtd/vuxml-1/vuxml-11.dtd">"""+xml.decode(), file=f) | ||
| else: | ||
| print("""<?xml version="1.0" encoding="utf-8"?> | ||
| <!DOCTYPE vuxml PUBLIC "-//vuxml.org//DTD VuXML 1.1//EN" "http://www.vuxml.org/dtd/vuxml-1/vuxml-11.dtd">"""+xml.decode()) | ||
| except Exception as e: | ||
| ret = error(e) | ||
|
|
||
| return ret | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) | ||
Uh oh!
There was an error while loading. Please reload this page.