OpenWrt cgi-io: ACL bypass and arbitrary root file read via cgi-download

In OpenWrt cgi-io the cgi-download handler does not canonicalize the requested path before ACL authorization, so an authenticated user with a path-scoped file ACL uses a ../ sequence to bypass the restriction and read any root-readable file such as /etc/shadow.

Advisory ID: TP-2026-034
Product: OpenWrt cgi-io (privileged CGI helper for file up/download via rpcd/LuCI)
Vulnerability type: Path traversal / ACL bypass through missing path canonicalization before authorization (CWE-22)
CVE: CVE-2026-62947
CVSS 3.1: 4.9 (Medium) · CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N
Affected versions: cgi-io <= 25.12.4 (HEAD 658b14b)
Fixed in: fix commit 72990b74 (master, 27 June 2026)
Vendor advisory: GHSA-jw5r-xhf5-2xcq
Reported: 27 June 2026

Summary

OpenWrt cgi-io is a privileged CGI helper that handles file up- and downloads for rpcd and LuCI and runs as root. The cgi-download handler reads the path POST field and, in the vulnerable version, passes it unmodified to the ACL check and then to open(). The ACL authorization in rpcd uses fnmatch() without the FNM_PATHNAME flag, so a * wildcard in an allowed file glob spans /. An authenticated user with the cgi-io download permission and a wildcard file read grant (for example /etc/apk/keys/*) bypasses the path restriction and reads any root-readable file. turingpoint verified and reported the vulnerability; the vendor fixed it by canonicalizing the path before the ACL check.

Root cause

main_download reads the path POST field and, in the vulnerable version, passed it unchecked to session_access() and open() (main.c:525, :549, :566). Authorization runs through the rpcd access call and uses fnmatch() without FNM_PATHNAME, so a * in an allowed file glob also matches / (main.c:105, session_access). A path like /etc/apk/keys/../../../../etc/shadow matches the ACL against the allowed prefix, while the kernel resolves the embedded .. and open() reaches a file outside the granted scope. The upload handler and rpcd-mod-file canonicalize the path before the ACL check and therefore reject the same input. The fix adds canonicalize_path() before the ACL check in main_download (main.c:547, commit 72990b74), aligning the download path with the sibling handlers.

Proof of Concept

# Authenticated session with the `cgi-io download` permission and a
# wildcard `file` read grant, e.g. /etc/apk/keys/*

curl http://target/cgi-bin/cgi-download \
  --data-urlencode sessionid=<SID> \
  --data-urlencode 'path=/etc/apk/keys/../../../../etc/shadow' \
  --data-urlencode filename=shadow

# -> contents of /etc/shadow, although the ACL only allows /etc/apk/keys/*

The ACL glob /etc/apk/keys/* matches the literal prefix of the path (fnmatch without FNM_PATHNAME), while open() resolves the .. sequences to /etc/shadow. rpcd denies the same restricted session direct access to /etc/shadow; only the non-canonicalized download path bypasses the check.

Impact

  • Read of any file readable by root (uid 0) despite a path-scoped file ACL.
  • Disclosure of the root password hash (/etc/shadow, crackable offline).
  • Extraction of /etc/config/* (Wi-Fi PSKs, VPN keys, credentials) and SSH and TLS private keys.
  • Requires an authenticated session with cgi-io download and a wildcard file read grant; no user interaction.

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.