OpenVAS Scanner: The Container Image Reference Escapes the Working Directory

The scanner builds a path from registry, image and tag without rejecting .., then creates directories, writes and recursively deletes there as root.

Advisory ID: TP-2026-106
Product: OpenVAS Scanner (vulnerability scanner, part of the Greenbone Community Edition, Rust and C)
Vulnerability type: Path traversal (CWE-22)
CVE: not assigned, the vendor advisory carries the placeholder CVE-2026-0000
CVSS 3.1: 9.0 (Critical) · CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H
Affected versions: openvas-scanner <= 23.45.11, Greenbone OS <= 25.0.4
Vendor advisory: GBSA2026-01
Fixed in: openvas-scanner 23.45.12 and Greenbone OS 25.0.5, both dated 26 June 2026
Reported: 13 June 2026

Summary

The container image scanner accepts an image reference of the form oci://registry/image:tag and builds from it the directory into which it unpacks the image's layers. All three parts go into the path unchanged, and .. is rejected nowhere. An unauthenticated call therefore creates a directory as root at any point in the filesystem, and it does so before any registry is contacted at all. If the caller also runs a registry of their own, the scanner writes files there with chosen content and execute permission, and on cleanup recursively deletes any existing directory tree. Authentication is disabled by default, the service runs as root, and the affected volumes are shared with the other containers of the stack.

Root cause

Image::from_str splits the reference and filters out empty segments only, which is why .. survives in registry, image and tag (rust/src/openvasd/container_image_scanner/image/mod.rs:171-209). Extractor::initialize appends those three parts to the working directory unchanged and calls create_dir_all, and it does so before the image is fetched (image/extractor/filtered_image.rs:51-72, called from scanner.rs:221). PathBuf::join keeps .. literally, and only the operating system resolves it when the directory is created. On unpacking, entry.unpack_in(target_dir) writes the layer's content into that already escaped directory, where the tar library's confinement is useless because it is relative to the root the attacker chose (:243-263). FileSystemLocator::drop then cleans up with remove_dir_all(&self.base), where base carries the escaped path plus the architecture string from the attacker's manifest (:149-162).

Proof of Concept

# unauthenticated, default configuration, service running as root:
POST /container-image-scanner/scans
{"scan_id":"poc","target":{"hosts":["oci://../../../../../../../../CVEFARM_PWNED/img:tag"]},
 "scan_preferences":[{"id":"registry_allow_insecure","value":"true"}]}   -> 201

POST /container-image-scanner/scans/poc   {"action":"start"}              -> 204

# the scan fails for want of a registry, but create_dir_all has already run:
ls -la /CVEFARM_PWNED      -> /CVEFARM_PWNED/img/tag   drwxr-xr-x root root
ls /tmp/openvasd           -> absent, the intended working directory was
                              never created, everything escaped.

Creating the directory is unconditional and needs no reachable registry. For writing and for the recursive deletion the attacker runs a registry of their own and sets accept_invalid_certs through the scan preferences of their own request; we proved both live, including the complete removal of a pre-created directory tree and a file made persistent by a deliberately corrupt second tar entry.

Difference from the vendor's rating

The vendor advisory rates this 7.4 with the vector AV:L/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:H. We adopt AV:L, because the shipped compose file forwards the service to the local address only, but we disagree on two metrics: the flow succeeded on the first try in every attempt and involves no race, which is AC:L, and it needs no action by any user, since the attacker creates and starts the scan themselves, which is UI:N. That yields 9.0.

Impact

  • Recursive deletion of arbitrary directory trees as root, up to emptying the container's filesystem entirely.
  • Persistent writing of files with chosen content and the execute bit set as root, though the filename stays limited to six package-database names.
  • Destruction of the entire scan stack through the shared volumes, such as the feed directories and the keyring, and it survives a reboot.
  • No disclosure of foreign files: the parsers return structured fields only, which we specifically tested and ruled out.

References

Is Something Like This in Your Software?

Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.