diff options
| -rw-r--r-- | security/vuxml/Makefile | 4 | ||||
| -rw-r--r-- | security/vuxml/files/euvd_provider.sh | 69 | ||||
| -rw-r--r-- | security/vuxml/files/mitre_provider.sh | 61 | ||||
| -rw-r--r-- | security/vuxml/files/newentry.sh | 174 | ||||
| -rw-r--r-- | security/vuxml/files/nvd_provider.sh | 72 | 
5 files changed, 330 insertions, 50 deletions
| diff --git a/security/vuxml/Makefile b/security/vuxml/Makefile index 9a3ef8b7a291..243b5cd5723e 100644 --- a/security/vuxml/Makefile +++ b/security/vuxml/Makefile @@ -102,10 +102,6 @@ newentry:  	@${ECHO_CMD} 'Also, <gt> tags are usually wrong in ranges. Use <ge> where adequate.'  	@${ECHO_CMD}  	@${SH} ${FILESDIR}/newentry.sh "${VUXML_CURRENT_FILE}" "CVE_ID=${CVE_ID}" "SA_ID=${SA_ID}" -	@${ECHO_CMD} -	@${ECHO_CMD} 'Be sure to get versioning right for PORTEPOCH and remember possible linux-* ports!' -	@${ECHO_CMD} 'Also, <gt> tags are usually wrong in ranges. Use <ge> where adequate.' -	@${ECHO_CMD}  .if defined(VID) && !empty(VID)  html: work/${VID}.html diff --git a/security/vuxml/files/euvd_provider.sh b/security/vuxml/files/euvd_provider.sh new file mode 100644 index 000000000000..821d2fcc06a2 --- /dev/null +++ b/security/vuxml/files/euvd_provider.sh @@ -0,0 +1,69 @@ +# Provider for the European Union Vulnerability Database +# https://euvd.enisa.europa.eu/ + +tmp_euvd="" + +init_euvd() { +	tmp_euvd=$(mktemp "${TMPDIR:-/tmp}"/euvd_json_data.XXXXXXXXXX) || exit 1 +	fetch -q -o "${tmp_euvd}" "https://euvdservices.enisa.europa.eu/api/enisaid?id=${CVE_ID}" || exit 1 +} + +cleanup_euvd() { +	rm -f "${tmp_euvd}" 2>/dev/null +} + +get_cvename_from_euvd() { +	# EUVD response includes "aliases" (CVE ID if available) +	jq -r '.aliases // .id' "${tmp_euvd}" +} + +get_cveurl_from_euvd() { +	echo "https://euvd.enisa.europa.eu/ui/vuln/${CVE_ID}" +} + +get_details_from_euvd() { +	jq -r '.description // empty | @html' "${tmp_euvd}" | fmt -p -s | sed '1!s/^/\t/' +} + +get_discovery_date_from_euvd() { +	raw=$(jq -r '.datePublished // empty' "${tmp_euvd}") +	if [ -n "$raw" ]; then +		trimmed=$(echo "$raw" | cut -d, -f1-2) +		if date -d "$trimmed" "+%Y-%m-%d" >/dev/null 2>&1; then +			date -d "$trimmed" "+%Y-%m-%d" +		else +			date -j -f "%b %d, %Y" "$trimmed" "+%Y-%m-%d" +		fi +	fi +} + +get_entry_date_from_euvd() { +	echo "${entry_date}" +} + + +get_product_name_from_euvd() { +	jq -r ' .enisaIdProduct[]?.product?.name ' "${tmp_euvd}" +} + +get_product_range_from_euvd() { +	jq -r '.enisaIdProduct[]?  | "\(.product_version? | gsub("<";"<") | gsub(">";">") | gsub("&";"&"))"' "${tmp_euvd}" +} + +get_package_name_from_euvd() { +	jq -r '.enisaIdProduct[0]?.product?.name // empty' "${tmp_euvd}" +} + +get_references_from_euvd() { +	jq -r '.references // empty | @html' "${tmp_euvd}" | tr " " "\n" +} + +get_source_from_euvd() { +	jq -r '.assigner // empty | @html' "${tmp_euvd}" +} + +get_topic_from_euvd() { +	# Use first sentence of description +	jq -r '.description // empty' "${tmp_euvd}" | cut -f1 -d. +} + diff --git a/security/vuxml/files/mitre_provider.sh b/security/vuxml/files/mitre_provider.sh new file mode 100644 index 000000000000..c2b1f8ffa73e --- /dev/null +++ b/security/vuxml/files/mitre_provider.sh @@ -0,0 +1,61 @@ +# Provider for MITRE +# https://www.mitre.org/ + +tmp_mitre="" + +init_mitre() +{ +	tmp_mitre=$(mktemp "${TMPDIR:-/tmp}"/mitre.XXXXXXXXXX) || exit 1 +	fetch -q -o "${tmp_mitre}" https://cveawg.mitre.org/api/cve/"${CVE_ID}" +} + +cleanup_mitre() +{ +	rm "${tmp_mitre}" 2>/dev/null +} + +get_cvename_from_mitre() +{ +	cvename="${CVE_ID}" +	echo "${cvename}" +} + +get_cveurl_from_mitre() { +	echo https://cveawg.mitre.org/api/cve/"${CVE_ID}" +} + +get_details_from_mitre() { +	jq -r '.containers?.cna?.descriptions[0]?.value' "${tmp_mitre}" | fmt -p -s +} + +get_discovery_date_from_mitre() { +	jq -r '.cveMetadata?.datePublished?' "${tmp_mitre}" | cut -f1 -dT +} + +get_entry_date_from_mitre() { +	echo "${entry_date}" +} + +get_product_name_from_mitre() { +	jq -r '.containers?.cna?.affected[]?.product' "${tmp_mitre}" +} + +get_product_range_from_mitre() { +	jq -r '.containers?.cna?.affected[]??.versions[0]?.lessThan' "${tmp_mitre}" +} + +get_package_name_from_mitre() { +	jq -r '.containers?.cna?.affected[0]?.product' "${tmp_mitre}" +} + +get_references_from_mitre() { +	jq -r '.containers?.cna?.references[0]?.url' "${tmp_mitre}" | fmt -p -s +} + +get_source_from_mitre() { +	jq -r '.containers?.cna?.references[0]?.url' "${tmp_mitre}" +} + +get_topic_from_mitre() { +	jq -r ".containers?.cna?.problemTypes[0]?.descriptions[0]?.description" "${tmp_mitre}" +} diff --git a/security/vuxml/files/newentry.sh b/security/vuxml/files/newentry.sh index 0298a5376a9e..4c8b09636112 100644 --- a/security/vuxml/files/newentry.sh +++ b/security/vuxml/files/newentry.sh @@ -15,6 +15,9 @@ if [ -z "${vuxml_file}" ]; then  	show_usage  fi +# ----------------- +# Process arguments +# -----------------  shift  while [ $# -gt 0 ]; do  case "$1" in @@ -34,27 +37,45 @@ case "$1" in  esac  done -tmp="`mktemp ${TMPDIR:-/tmp}/vuxml.XXXXXXXXXX`" || exit 1 +tmp=$(mktemp "${TMPDIR:-/tmp}"/vuxml.XXXXXXXXXX) || exit 1  tmp_fbsd_sa="" -tmp_mitre="" -tmp_nvd="" +# ------------------------------------- +# Define how to clean up temporal files +# ------------------------------------- +#  doclean="yes"  cleanup() {    if [ "${doclean}" = "yes" ]; then -	rm -f "${tmp}" "${tmp_fbsd_sa}" "${tmp_mitre}" "${tmp_nvd}" > /dev/null +	rm -f "${tmp}" "${tmp_fbsd_sa}" > /dev/null    fi + +  # Call cleaners for providers +  for provider in ${providers}; do +	cleanup_"${provider}" +	cleanup_"${provider}" +  done  } -trap cleanup EXIT 1 2 13 15 +trap cleanup EXIT HUP INT PIPE TERM -vid="`uuidgen | tr '[:upper:]' '[:lower:]'`" +# ----------------------------- +# Variables with default values +# ----------------------------- +vid="$(uuidgen | tr '[:upper:]' '[:lower:]')"  [ -z "$vid" ] && exit 1 + +discovery_date=""  cvename="INSERT CVE RECORD IF AVAILABLE"  cveurl="INSERT BLOCKQUOTE URL HERE"  details="." -discovery="`date -u '+%Y-%m'`-FIXME" || exit 1 -entry="`date -u '+%Y-%m-%d'`" || exit 1 +discovery_date="$(date -u '+%Y-%m')-FIXME" || exit 1 +entry_date="$(date -u '+%Y-%m-%d')" || exit 1  package_name="" +product_name="" +product_range="" +package_list="<package> +<name></name> +<range><lt></lt></range>"  references="INSERT URL HERE"  topic=""  source="SO-AND-SO" @@ -67,38 +88,65 @@ DESC_BODY="<body xmlns=\"http://www.w3.org/1999/xhtml\">  	</blockquote>  	</body>" - -# Try to retrieve information if a CVE identifier was provided -if [ -n "${CVE_ID}" ]; then +# -------------------------------- +# Check we have everything we need +# -------------------------------- +check_dependencies() +{  	if ! command -v jq > /dev/null; then  		echo textproc/jq is needed for CVE automatic entry fill  		exit 1  	fi +} + +# ------------------------------------------ +# List of CVE providers sorted by preference +# ------------------------------------------ +providers="mitre nvd euvd" + +# ------------------------------------------ +# List of fields to query for every provider +# ------------------------------------------ +fields="cvename cveurl details discovery_date entry_date product_name product_range package_name references source topic" -	# NVD database only accepts uppercase CVE ids, like CVE-2022-39282, NOT -	# cve-2022-39282. -	CVE_ID=$(echo "${CVE_ID}" | tr '[:lower:]' '[:upper:]') || exit 1 - -	# Get information from the NVD database JSON format -	tmp_nvd="`mktemp ${TMPDIR:-/tmp}/nvd_json_data.XXXXXXXXXX`" || exit 1 -	fetch -q -o "${tmp_nvd}" https://services.nvd.nist.gov/rest/json/cves/2.0?cveId="${CVE_ID}" || exit 1 -	# Get information from MITRE database (they provide a nice "topic") -	tmp_mitre="`mktemp ${TMPDIR:-/tmp}/mitre.XXXXXXXXXX`" || exit 1 -	fetch -q -o "${tmp_mitre}" https://cveawg.mitre.org/api/cve/"${CVE_ID}" - -	# Create variables from input and online sources -	cvename="${CVE_ID}" -	cveurl=https://nvd.nist.gov/vuln/detail/${CVE_ID} -	pref=.vulnerabilities[0].cve -	details=$(jq -r "${pref}.descriptions[0].value|@html" "${tmp_nvd}" | fmt -p -s | sed '1!s/^/\t/') || exit 1 -	discovery=$(jq -r "${pref}.published|@html" "${tmp_nvd}" | cut -f1 -dT) || exit 1 -	pref=.vulnerabilities[0].cve.configurations[0].nodes[0].cpeMatch[0] -	package_name=$(jq -r "${pref}.criteria|@html" "${tmp_nvd}" | cut -f4 -d:) || exit 1 -	upstream_fix=$(jq -r "${pref}.versionEndExcluding|@html" "${tmp_nvd}") || exit 1 -	pref=.vulnerabilities[0].cve.references[0] -	references=$(jq -r "${pref}.url|@html" "${tmp_nvd}" | tr " " "\n") || exit 1 -	source=$(jq -r "${pref}.source|@html" "${tmp_nvd}" | tr " " "\n") || exit 1 -	topic=$(jq -r ".containers.cna.title|@html" "${tmp_mitre}" ) || exit 1 +# Some providers only allow for upper case identifiers +CVE_ID=$(echo "${CVE_ID}" | tr '[:lower:]' '[:upper:]') || exit 1 + +# ----------------------------------------------------------------------------- +# Generic resolver +# +# Gets a variable name and the list of providers and returns the value of the +# variable. If the first defined provider returns empty or nullm, it tries with +# the next one until one provider returns a value or we run out of providers +# ----------------------------------------------------------------------------- +resolve_field() { +    field="${1}" +    shift +    providers="$@" + +    for provider in $providers; do +        func="get_${field}_from_${provider}" +        if command -v "${func}" >/dev/null 2>&1; then +            value="$($func)" +            if [ -n "${value}" ] && [ "${value}" != "null" ] && [ "${value}" != "n/a" ]; then +                echo "${value}" +                return 0 +            fi +	else +		echo "Warning: function ${func} not implemented in provider ${provider}" +        fi +    done +    echo "null" +} + +# -------------------------------------------------- +# Fill global variables with data from CVE databases +# -------------------------------------------------- +get_cve_info() { +	for field in ${fields}; do +	    value=$(resolve_field "${field}" ${providers}) +	    eval "${field}=\$value" +	done  DESC_BODY="<body xmlns=\"http://www.w3.org/1999/xhtml\">  	<p>${source} reports:</p> @@ -106,14 +154,17 @@ DESC_BODY="<body xmlns=\"http://www.w3.org/1999/xhtml\">  	  <p>${details}</p>  	</blockquote>  	</body>" -fi +} -if [ -n "${SA_ID}" ]; then +# ---------------------------------------------------------------- +# Fill global variables with data from FreeBSD Security Advisories +# ---------------------------------------------------------------- +get_sa_info() {  	SA_URL_BASE=https://www.freebsd.org/security/advisories/  	# Get information from the Project's SA site -	tmp_fbsd_sa="$(mktemp ${TMPDIR:-/tmp}/fbsd_sa_data.XXXXXXXXXX)" || exit 1 -	fetch -q -o "${tmp_fbsd_sa}" ${SA_URL_BASE}${SA_ID} || exit 1 +	tmp_fbsd_sa=$(mktemp "${TMPDIR:-/tmp}/fbsd_sa_data.XXXXXXXXXX") || exit 1 +	fetch -q -o "${tmp_fbsd_sa}" "${SA_URL_BASE}${SA_ID}" || exit 1  	# Create variables from SA note  	if grep -q 'CVE Name' "${tmp_fbsd_sa}"; then @@ -148,6 +199,40 @@ DESC_BODY="<body xmlns=\"http://www.w3.org/1999/xhtml\">  	<h1>Impact:</h1>  	  ${impact}        </body>" +} + +init_providers() { +	for provider in files/*_provider.sh; do +		provider_name=$(basename "${provider}" | cut -f1 -d_) +		. "files/${provider_name}_provider.sh" +		init_"${provider_name}" +	done +} + +create_packages_list() { +	tmp_prod=$(mktemp "${TMPDIR:-/tmp}"/vuxml.prod.XXXXXXXXXX) || exit 1 +	tmp_ver=$(mktemp "${TMPDIR:-/tmp}"/vuxml.ver.XXXXXXXXXX) || exit 1 +	printf "%s" "${product_name}" > "${tmp_prod}" +	printf "%s" "${product_range}" > "${tmp_ver}" + +	package_list=$(paste "${tmp_prod}" "${tmp_ver}" | sed \ +	-e 's|\t|</name>\n\t<range><lt>|g'  \ +	-e 's|^|    <package>\n\t<name>|g' \ +	-e 's|$|</lt></range>\n    </package>|g') + +	rm "${tmp_prod}" "${tmp_ver}" 2>/dev/null +} + +# Try to retrieve information if a CVE identifier was provided +if [ -n "${CVE_ID}" ]; then +	check_dependencies +	init_providers +	get_cve_info "${CVE_ID}" +	create_packages_list +fi + +if [ -n "${SA_ID}" ]; then +	get_sa_info  fi  awk '/^<\?/,/^<vuxml/ { print }' "${vuxml_file}" >> "${tmp}" || exit 1 @@ -155,21 +240,18 @@ cat << EOF >> "${tmp}" || exit 1    <vuln vid="${vid}">      <topic>${package_name} -- ${topic}</topic>      <affects> -      <package> -	<name>${package_name}</name> -	<range><lt>${upstream_fix}</lt></range> -      </package> +${package_list}      </affects>      <description> -	${DESC_BODY} +        ${DESC_BODY}      </description>      <references>        <cvename>${cvename}</cvename>        <url>${cveurl}</url>      </references>      <dates> -      <discovery>${discovery}</discovery> -      <entry>${entry}</entry> +      <discovery>${discovery_date}</discovery> +      <entry>${entry_date}</entry>      </dates>    </vuln> diff --git a/security/vuxml/files/nvd_provider.sh b/security/vuxml/files/nvd_provider.sh new file mode 100644 index 000000000000..8a383a7d6752 --- /dev/null +++ b/security/vuxml/files/nvd_provider.sh @@ -0,0 +1,72 @@ +# Provider for the National Vulnerability Database +# https://nvd.nist.gov/ + +tmp_nvd="" + +init_nvd() +{ +	tmp_nvd=$(mktemp "${TMPDIR:-/tmp}"/nvd_json_data.XXXXXXXXXX) || exit 1 +	fetch -q -o "${tmp_nvd}" https://services.nvd.nist.gov/rest/json/cves/2.0?cveId="${CVE_ID}" || exit 1 +} + +cleanup_nvd() +{ +	rm "${tmp_nvd}" 2>/dev/null +} + +get_cvename_from_nvd() +{ +	cvename="${CVE_ID}" +	echo "${cvename}" +} + +get_cveurl_from_nvd() { +	cveurl=https://nvd.nist.gov/vuln/detail/${CVE_ID} +	echo "${cveurl}" +} + +get_details_from_nvd() { +	pref=".vulnerabilities[0]?.cve?" +	jq -r "${pref}.descriptions[0]?.value|@html" "${tmp_nvd}" | fmt -p -s | sed '1!s/^/\t/' +} + +get_discovery_date_from_nvd() { +	pref=".vulnerabilities[0]?.cve?" +	jq -r "${pref}.published|@html" "${tmp_nvd}" | cut -f1 -dT +} + +get_entry_date_from_nvd() { +	echo "${entry_date}" +} + +get_product_name_from_nvd() { +	jq -r '.vulnerabilities[]?.cve?.configurations[]?.nodes[]?.cpeMatch[]? | +		       (.criteria | split(":")[4])' "${tmp_nvd}" +} + +get_product_range_from_nvd() { +	jq -r '.vulnerabilities[]?.cve.configurations[]?.nodes[]?.cpeMatch[]?.versionEndExcluding ' "${tmp_nvd}" +} + +get_package_name_from_nvd() { +	jq -r '.vulnerabilities[]?.cve?.configurations[]?.nodes[]?.cpeMatch[0]?.criteria' "${tmp_nvd}" | cut -f5 -d: +} + +get_references_from_nvd() { +	pref=".vulnerabilities[0]?.cve?.references[0]?" +	jq -r "${pref}.url|@html" "${tmp_nvd}" | tr " " "\n" +} + +get_source_from_nvd() +{ +	pref=".vulnerabilities[0]?.cve?.references[0]?" +	jq -r "${pref}.source|@html" "${tmp_nvd}" | tr " " "\n" +} + +get_topic_from_nvd() { +	# NVD does not provide a nice summary. Let's use the first sentence from +	# the details instead +	pref=".vulnerabilities[0]?.cve?" +	jq -r "${pref}.descriptions[0]?.value|@html" "${tmp_nvd}" | cut -f1 -d. +} + | 
