diff --git a/utilities/vulndata/README.md b/utilities/vulndata/README.md index 3eed9c7..c88ab32 100644 --- a/utilities/vulndata/README.md +++ b/utilities/vulndata/README.md @@ -1,5 +1,117 @@ -# Bash +# vulndata -This is where you can find bash scripts related to the Anchore Enterprise API. +Both scripts here do the same thing — pull the **full NVD description** for every CVE affecting a given Application Version. The standard Enterprise vulnerability report only returns match metadata (severity, CVSS, etc.) with little or no description text, so these dig out the fuller `nvd_data[].description` text that the UI and `anchorectl` don't normally surface. -These scripts call the Anchore Enterprise REST API directly using `curl` and `jq`. Credentials are loaded from an `anchore.env` file and should never be hardcoded. +Pick the one that matches your Enterprise version — the underlying API changed between 5.x and 6.x. + +| Script | Enterprise version | API used | +|---|---|---| +| [`vulndata.sh`](#vulndatash-enterprise-5x) | 5.x | `/applications` (legacy Application Management) | +| [`vulndata_apps.py`](#vulndata_appspy-enterprise-6x) | 6.x | `/apps` | + +--- + +## `vulndata.sh` (Enterprise 5.x) + +> [!IMPORTANT] +> This targets **Anchore Enterprise 5.x** only. It calls the legacy `/applications` API for [Application Management](https://docs.anchore.com/current/docs/application_management/), which does not exist in the same form in Enterprise 6.x — use `vulndata_apps.py` instead. + +### Prerequisites + +- `curl` and `jq` installed +- An Anchore Enterprise 5.x deployment with API access +- The target Application and Application Version must already exist in Anchore (created via SBOM import) — this script only reads existing data, it does not create or scan anything + +### Configuration + +Credentials come from your environment first, then fall back to an `anchore.env` file — never hardcode them in the script. + +If you already have `anchorectl` configured in your shell (`ANCHORECTL_USERNAME`, `ANCHORECTL_PASSWORD`, `ANCHORECTL_URL`), the script reuses those automatically — no extra setup needed. Otherwise, create an `anchore.env` file next to `vulndata.sh`: + +```bash +# anchore.env +USERNAME=admin +PASSWORD=foobar +ANCHORE_URL=https://anchore.example.com/v2 +``` + +> `ANCHORECTL_URL` typically points at the base API (e.g. `http://anchore.example.com:8228`) without a version suffix. This script hits the `/applications` endpoint directly, so make sure whichever URL is used (env var or `anchore.env`) includes the correct base path (often `/v2`) for your deployment. + +Then edit the two variables near the top of `vulndata.sh` to point at the Application and Version you want a report for: + +```bash +APP_NAME="rust" +TARGET_VERSION="2.0" +``` + +> In a GitLab CI job you can set `APP_NAME` from `$CI_PROJECT_NAME` and `TARGET_VERSION` from a git tag instead of hardcoding them. + +### Usage + +```bash +chmod +x vulndata.sh +./vulndata.sh +``` + +The script will: + +1. Look up the `application_id` and `application_version_id` for `APP_NAME` / `TARGET_VERSION`. +2. Fetch the vulnerability report for that Application Version. +3. Print each CVE ID and its NVD description, and save the same output to a file named `vulndata` in the current directory (overwritten on each run). + +### Notes + +- Exits early with an error if the Application/Version name pair can't be found, or if credentials are missing. +- Only vulnerabilities with an `nvd` entry are included — findings without NVD data (e.g. GHSA-only advisories) won't appear in the output. + +--- + +## `vulndata_apps.py` (Enterprise 6.x) + +> [!IMPORTANT] +> This targets **Anchore Enterprise 6.x**, which replaced `/applications` with the `/apps` API. Use `vulndata.sh` for 5.x deployments. + +Enterprise 6's `/apps/{app_id}/versions/{version_id}/vulnerabilities` endpoint only returns match metadata (severity, CVSS, EPSS, KEV, `anchore_score`) — no description text at all. Getting the description back requires an extra round trip to the standalone vulnerability catalog, which is more than is comfortable to chain together in `bash`/`jq`, so this one's Python. + +### What it does + +1. `GET /apps?name=` → resolves the app ID +2. `GET /apps/{app_id}/versions?name=` → resolves the version ID +3. `GET /apps/{app_id}/versions/{version_id}/vulnerabilities` → collects every matched CVE ID (paginated via cursor) +4. `GET /query/vulnerabilities?id=` → looks up each CVE in batches of 50 and pulls `nvd_data[].description` + +### Prerequisites + +- `python3` (3.8+, standard library only — nothing to `pip install`) +- An Anchore Enterprise 6.x deployment with API access +- The target App and App Version must already exist in Anchore — this script only reads existing data + +### Configuration + +No `anchore.env` file here — being Python, it reads credentials the same way `anchorectl` itself does: + +1. `ANCHORECTL_USERNAME` / `ANCHORECTL_PASSWORD` / `ANCHORECTL_URL` environment variables, if set +2. Otherwise, your `anchorectl` config file (`.anchorectl.yaml`, `~/.anchorectl.yaml`, `$XDG_CONFIG_HOME/anchorectl/config.yaml`, etc. — same search order anchorectl uses), reading its `url` / `username` / `password` keys + +If you already use `anchorectl` against this deployment, there's nothing to configure. + +It also normalizes the base URL itself — it appends `/v2` automatically if the URL doesn't already end in it, so a bare `ANCHORECTL_URL` (e.g. `http://anchore.example.com:8228`) or config `url:` works as-is. + +### Usage + +```bash +chmod +x vulndata_apps.py +./vulndata_apps.py + +# example +./vulndata_apps.py rust 2.0 + +# custom output file (default: vulndata) +./vulndata_apps.py rust 2.0 --output vulndata-rust.txt +``` + +### Notes + +- Exits early with an error if the app/version name pair can't be found, if credentials are missing, or on any non-2xx API response (the error includes the response body). +- If a CVE has multiple `nvd_data` entries, the first with a non-empty `description` is used; if there's no NVD data at all, it falls back to the record's generic `description`, or prints `(no description available)`. +- App and version names are matched exactly (case-sensitive), same as the API's `name` filter. diff --git a/utilities/vulndata/vulndata b/utilities/vulndata/vulndata deleted file mode 100644 index 4ee2f28..0000000 --- a/utilities/vulndata/vulndata +++ /dev/null @@ -1,46 +0,0 @@ -CVE-2010-4756: The glob implementation in the GNU C Library (aka glibc or libc6) allows remote authenticated users to cause a denial of service (CPU and memory consumption) via crafted glob expressions that do not match any pathnames, as demonstrated by glob expressions in STAT commands to an FTP daemon, a different vulnerability than CVE-2010-2632. - -CVE-2019-1010024: GNU Libc current is affected by: Mitigation bypass. The impact is: Attacker may bypass ASLR using cache of thread stack and heap. The component is: glibc. NOTE: Upstream comments indicate "this is being treated as a non-security bug and no real threat. - -CVE-2007-5686: initscripts in rPath Linux 1 sets insecure permissions for the /var/log/btmp file, which allows local users to obtain sensitive information regarding authentication attempts. NOTE: because sshd detects the insecure permissions and does not log certain events, this also prevents sshd from logging failed authentication attempts by remote attackers. - -CVE-2021-45346: A Memory Leak vulnerability exists in SQLite Project SQLite3 3.35.1 and 3.37.0 via maliciously crafted SQL Queries (made via editing the Database File), it is possible to query a record, and leak subsequent bytes of memory that extend beyond the record, which could let a malicious user obtain sensitive information. NOTE: The developer disputes this as a vulnerability stating that If you give SQLite a corrupted database file and submit a query against the database, it might read parts of the database that you did not intend or expect. - -CVE-2023-31439: An issue was discovered in systemd 253. An attacker can modify the contents of past events in a sealed log file and then adjust the file such that checking the integrity shows no error, despite modifications. NOTE: the vendor reportedly sent "a reply denying that any of the finding was a security vulnerability." - -CVE-2019-1010023: GNU Libc current is affected by: Re-mapping current loaded library with malicious ELF file. The impact is: In worst case attacker may evaluate privileges. The component is: libld. The attack vector is: Attacker sends 2 ELF files to victim and asks to run ldd on it. ldd execute code. NOTE: Upstream comments indicate "this is being treated as a non-security bug and no real threat. - -CVE-2019-1010022: GNU Libc current is affected by: Mitigation bypass. The impact is: Attacker may bypass stack guard protection. The component is: nptl. The attack vector is: Exploit stack buffer overflow vulnerability and use this bypass vulnerability to bypass stack guard. NOTE: Upstream comments indicate "this is being treated as a non-security bug and no real threat. - -CVE-2019-9192: In the GNU C Library (aka glibc or libc6) through 2.29, check_dst_limits_calc_pos_1 in posix/regexec.c has Uncontrolled Recursion, as demonstrated by '(|)(\\1\\1)*' in grep, a different issue than CVE-2018-20796. NOTE: the software maintainer disputes that this is a vulnerability because the behavior occurs only with a crafted pattern - -CVE-2025-5278: A flaw was found in GNU Coreutils. The sort utility's begfield() function is vulnerable to a heap buffer under-read. The program may access memory outside the allocated buffer if a user runs a crafted command using the traditional key format. A malicious input could lead to a crash or leak sensitive data. - -CVE-2011-4116: _is_safe in the File::Temp module for Perl does not properly handle symlinks. - -CVE-2023-31438: An issue was discovered in systemd 253. An attacker can truncate a sealed log file and then resume log sealing such that checking the integrity shows no error, despite modifications. NOTE: the vendor reportedly sent "a reply denying that any of the finding was a security vulnerability." - -CVE-2021-45710: An issue was discovered in the tokio crate before 1.8.4, and 1.9.x through 1.13.x before 1.13.1, for Rust. In certain circumstances involving a closed oneshot channel, there is a data race and memory corruption. - -CVE-2011-3374: It was found that apt-key in apt, all versions, do not correctly validate gpg keys with the master keyring, leading to a potential man-in-the-middle attack. - -CVE-2017-18018: In GNU Coreutils through 8.29, chown-core.c in chown and chgrp does not prevent replacement of a plain file with a symlink during use of the POSIX "-R -L" options, which allows local users to modify the ownership of arbitrary files by leveraging a race condition. - -CVE-2019-1010025: GNU Libc current is affected by: Mitigation bypass. The impact is: Attacker may guess the heap addresses of pthread_created thread. The component is: glibc. NOTE: the vendor's position is "ASLR bypass itself is not a vulnerability. - -CVE-2022-0563: A flaw was found in the util-linux chfn and chsh utilities when compiled with Readline support. The Readline library uses an "INPUTRC" environment variable to get a path to the library config file. When the library cannot parse the specified file, it prints an error message containing data from the file. This flaw allows an unprivileged user to read root-owned files, potentially leading to privilege escalation. This flaw affects util-linux versions prior to 2.37.4. - -CVE-2013-4392: systemd, when updating file permissions, allows local users to change the permissions and SELinux security contexts for arbitrary files via a symlink attack on unspecified files. - -CVE-2018-20796: In the GNU C Library (aka glibc or libc6) through 2.29, check_dst_limits_calc_pos_1 in posix/regexec.c has Uncontrolled Recursion, as demonstrated by '(\227|)(\\1\\1|t1|\\\2537)+' in grep. - -CVE-2005-2541: Tar 1.15.1 does not properly warn the user when extracting setuid or setgid files, which may allow local users or remote attackers to gain privileges. - -CVE-2023-31437: An issue was discovered in systemd 253. An attacker can modify a sealed log file such that, in some views, not all existing and sealed log messages are displayed. NOTE: the vendor reportedly sent "a reply denying that any of the finding was a security vulnerability." - -CVE-2025-62518: astral-tokio-tar is a tar archive reading/writing library for async Rust. Versions of astral-tokio-tar prior to 0.5.6 contain a boundary parsing vulnerability that allows attackers to smuggle additional archive entries by exploiting inconsistent PAX/ustar header handling. When processing archives with PAX-extended headers containing size overrides, the parser incorrectly advances stream position based on ustar header size (often zero) instead of the PAX-specified size, causing it to interpret file content as legitimate tar headers. This issue has been patched in version 0.5.6. There are no workarounds. - -CVE-2025-59825: astral-tokio-tar is a tar archive reading/writing library for async Rust. In versions 0.5.3 and earlier of astral-tokio-tar, tar archives may extract outside of their intended destination directory when using the Entry::unpack_in_raw API. Additionally, the Entry::allow_external_symlinks control (which defaults to true) could be bypassed via a pair of symlinks that individually point within the destination but combine to point outside of it. These behaviors could be used individually or combined to bypass the intended security control of limiting extraction to the given directory. This in turn would allow an attacker with a malicious tar archive to perform an arbitrary file write and potentially pivot into code execution. This issue has been patched in version 0.5.4. There is no workaround other than upgrading. - -CVE-2026-22863: Deno is a JavaScript, TypeScript, and WebAssembly runtime. Before 2.6.0, node:crypto doesn't finalize cipher. The vulnerability allows an attacker to have infinite encryptions. This can lead to naive attempts at brute forcing, as well as more refined attacks with the goal to learn the server secrets. This vulnerability is fixed in 2.6.0. - diff --git a/utilities/vulndata/vulndata.sh b/utilities/vulndata/vulndata.sh index 205988c..b6f46e4 100755 --- a/utilities/vulndata/vulndata.sh +++ b/utilities/vulndata/vulndata.sh @@ -9,11 +9,19 @@ # --------------------------------------------------------------------------- # --- Configuration --- -# put variables for username, password, and Anchore_URL in anchore.env file -if [ -f ./anchore.env ]; then +# Reuse ANCHORECTL_USERNAME / ANCHORECTL_PASSWORD / ANCHORECTL_URL if you already have +# anchorectl configured in your shell. Otherwise fall back to an anchore.env file with +# USERNAME, PASSWORD, and ANCHORE_URL (see README). +USERNAME="${ANCHORECTL_USERNAME:-$USERNAME}" +PASSWORD="${ANCHORECTL_PASSWORD:-$PASSWORD}" +ANCHORE_URL="${ANCHORECTL_URL:-$ANCHORE_URL}" + +if [[ -z "$USERNAME" || -z "$PASSWORD" || -z "$ANCHORE_URL" ]] && [ -f ./anchore.env ]; then source ./anchore.env -else - echo "Error: anchore.env file not found." +fi + +if [[ -z "$USERNAME" || -z "$PASSWORD" || -z "$ANCHORE_URL" ]]; then + echo "Error: missing credentials. Set ANCHORECTL_USERNAME, ANCHORECTL_PASSWORD, and ANCHORECTL_URL in your environment, or create an anchore.env file." exit 1 fi diff --git a/utilities/vulndata/vulndata_apps.py b/utilities/vulndata/vulndata_apps.py new file mode 100755 index 0000000..ca18460 --- /dev/null +++ b/utilities/vulndata/vulndata_apps.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +# --------------------------------------------------------------------------- +# Part of the Anchore Examples Repository. +# Licensed under the Apache License, Version 2.0 (the "License"). +# +# THIS FILE IS UNMAINTAINED AND PROVIDED "AS IS", WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND. USE AT YOUR OWN RISK. +# --------------------------------------------------------------------------- +""" +vulndata_apps.py — full NVD vulnerability descriptions for an Anchore +Enterprise 6.x Application Version. + +Enterprise 6's /apps/{app_id}/versions/{version_id}/vulnerabilities endpoint +only returns match metadata (severity, CVSS, EPSS, KEV, anchore_score) - no +description text. This script chains three calls to get from an app/version +name to full descriptions: + + 1. GET /apps?name= -> app_id + 2. GET /apps/{app_id}/versions?name= -> version_id + 3. GET /apps/{app_id}/versions/{version_id}/vulnerabilities -> vulnerability IDs + 4. GET /query/vulnerabilities?id= -> nvd_data[].description + +Prerequisites: python3 (3.8+, stdlib only - nothing to pip install). + +Usage: + vulndata_apps.py APP_NAME VERSION_NAME + vulndata_apps.py rust 2.0 + vulndata_apps.py rust 2.0 --output vulndata-rust.txt +""" + +import argparse +import base64 +import json +import os +import sys +import urllib.error +import urllib.parse +import urllib.request + +BATCH_SIZE = 50 + +# Same search order anchorectl itself uses for its config file. +ANCHORECTL_CONFIG_PATHS = [ + "./.anchorectl.yaml", + "./anchorectl.yaml", + "./.anchorectl/config.yaml", + os.path.expanduser("~/.anchorectl.yaml"), + os.path.expanduser("~/anchorectl.yaml"), + os.path.join( + os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), + "anchorectl", + "config.yaml", + ), +] + + +def load_anchorectl_config(): + """Pull url/username/password out of an anchorectl config file. + + Only reads top-level (unindented) `key: value` lines - good enough for + the flat keys we need without pulling in a YAML dependency. + """ + for path in ANCHORECTL_CONFIG_PATHS: + if not os.path.isfile(path): + continue + values = {} + with open(path) as f: + for line in f: + if not line.strip() or line[0] in " \t#" or ":" not in line: + continue + key, _, value = line.partition(":") + values[key.strip()] = value.split("#")[0].strip().strip("'\"") + return values + return {} + + +def resolve_config(): + config = load_anchorectl_config() + username = os.environ.get("ANCHORECTL_USERNAME") or config.get("username") + password = os.environ.get("ANCHORECTL_PASSWORD") or config.get("password") + base_url = os.environ.get("ANCHORECTL_URL") or config.get("url") + + if not all([username, password, base_url]): + sys.exit( + "Error: missing credentials. Set ANCHORECTL_USERNAME, ANCHORECTL_PASSWORD, " + "and ANCHORECTL_URL in your environment, or configure them in ~/.anchorectl.yaml." + ) + + base_url = base_url.rstrip("/") + if not base_url.endswith("/v2"): + base_url += "/v2" + return base_url, username, password + + +def api_get(base_url, username, password, path, params=None): + url = f"{base_url}{path}" + if params: + url += "?" + urllib.parse.urlencode(params) + token = base64.b64encode(f"{username}:{password}".encode()).decode() + req = urllib.request.Request( + url, headers={"Accept": "application/json", "Authorization": f"Basic {token}"} + ) + try: + with urllib.request.urlopen(req, timeout=30) as resp: + return json.loads(resp.read()) + except urllib.error.HTTPError as e: + sys.exit(f"Error: {e.code} {e.reason} for {url}\n{e.read().decode(errors='replace')}") + except urllib.error.URLError as e: + sys.exit(f"Error: could not reach {url}: {e.reason}") + + +def get_app_id(base_url, username, password, app_name): + data = api_get(base_url, username, password, "/apps", {"name": app_name}) + items = data.get("items", []) + if not items: + sys.exit(f"Error: no app named {app_name!r} found") + return items[0]["system_metadata"]["id"] + + +def get_version_id(base_url, username, password, app_id, version_name): + data = api_get( + base_url, username, password, f"/apps/{app_id}/versions", {"name": version_name} + ) + items = data.get("items", []) + if not items: + sys.exit(f"Error: no version named {version_name!r} found") + return items[0]["system_metadata"]["id"] + + +def get_vulnerability_ids(base_url, username, password, app_id, version_id): + ids = [] + seen = set() + cursor = None + while True: + params = {"limit": 1000} + if cursor: + params["cursor"] = cursor + data = api_get( + base_url, + username, + password, + f"/apps/{app_id}/versions/{version_id}/vulnerabilities", + params, + ) + for item in data.get("items", []): + vid = item["vulnerability_id"] + if vid not in seen: + seen.add(vid) + ids.append(vid) + cursor = data.get("pagination", {}).get("next_cursor") + if not cursor: + break + return ids + + +def get_descriptions(base_url, username, password, vuln_ids): + descriptions = {} + for i in range(0, len(vuln_ids), BATCH_SIZE): + batch = vuln_ids[i : i + BATCH_SIZE] + data = api_get( + base_url, username, password, "/query/vulnerabilities", {"id": ",".join(batch)} + ) + for record in data.get("vulnerabilities", []): + vid = record.get("id") + if not vid or vid in descriptions: + continue + nvd_desc = next( + (n.get("description") for n in record.get("nvd_data") or [] if n.get("description")), + None, + ) + descriptions[vid] = nvd_desc or record.get("description") or "" + return descriptions + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.strip().splitlines()[0]) + parser.add_argument("app_name", help="Application name (exact match)") + parser.add_argument("version_name", help="Application version name (exact match)") + parser.add_argument("-o", "--output", default="vulndata", help="Output file (default: vulndata)") + args = parser.parse_args() + + base_url, username, password = resolve_config() + + app_id = get_app_id(base_url, username, password, args.app_name) + version_id = get_version_id(base_url, username, password, app_id, args.version_name) + vuln_ids = get_vulnerability_ids(base_url, username, password, app_id, version_id) + + if not vuln_ids: + print(f"No vulnerabilities found for {args.app_name} {args.version_name}.") + return + + descriptions = get_descriptions(base_url, username, password, vuln_ids) + + header = "-" * 58 + lines = [header, f"Vulnerability Report for {args.app_name} {args.version_name}", header] + for vid in vuln_ids: + desc = descriptions.get(vid) or "(no description available)" + lines.append(f"{vid}: {desc}\n") + + output = "\n".join(lines) + print(output) + with open(args.output, "w") as f: + f.write(output + "\n") + + +if __name__ == "__main__": + main()