{"schema":"threatline.record/2","record":{"id":"GHSA-qfv2-x8g4-qg27","aliases":["GHSA-qfv2-x8g4-qg27"],"summary":"YesWiki BazarListeAction unauthenticated within-namespace template traversal via GET parameter","details":"## Summary\r\n\r\nYesWiki's `BazarListeAction`, the handler for the `{{bazarliste}}` wiki action, reads a `template` parameter directly from the HTTP GET query string and passes it into Twig's template resolver with only `htmlspecialchars()` sanitization. Because `htmlspecialchars()` does not filter path traversal sequences and no `hasTemplate()` or `basename()` validation is applied, an unauthenticated attacker can force the page to render Bazar entries using any template within the `@bazar` Twig namespace. A hardening commit that added `hasTemplate()` and `basename()` validation to eight other actions explicitly omitted `BazarListeAction`, the only action that accepts template names from unauthenticated GET requests.\r\n\r\n## Root Cause\r\n\r\n`BazarListeAction::formatArguments()` accepts the `template` parameter from the HTTP GET query string and applies only `htmlspecialchars()`, which encodes `<`, `>`, `&`, and `\"` but does not filter directory traversal sequences (`../`) or arbitrary template names.\r\n\r\n```php\r\n// tools/bazar/actions/BazarListeAction.php, lines 78-81\r\n$template = $get->get('template') ?? $arg['template'] ?? null;\r\nif ($template) {\r\n    $template = htmlspecialchars($template);\r\n}\r\n```\r\n\r\nThe value flows without further validation into `run()` (line 312 for the dynamic path, line 335 for the non-dynamic path) and `renderEntries()` (line 395), which interpolates it into a Twig `@bazar/` namespace render call. A hardening commit (`d49140452`, unreleased) added `basename()` and extension-checking via a `safeTemplate()` helper to eight other actions (`ContactAction`, `SyndicationAction`, `moteurrecherche`, `barreredaction`, `listpagestag`, `includepages`, `abonnement`, `desabonnement`), but omitted `BazarListeAction`.\r\n\r\n## Impact\r\n\r\nAn unauthenticated attacker who can reach any YesWiki page containing a `{{bazarliste}}` action (one of the most commonly used YesWiki actions) can select an arbitrary template from the `@bazar` Twig namespace by appending a `template` GET parameter. This changes how Bazar entries are displayed for every visitor who follows the crafted URL.\r\n\r\nThe impact is bounded to display manipulation within the Bazar template namespace. Twig's `validateName()` function tracks directory depth and rejects paths that escape the namespace (level < 0), so filesystem reads or namespace escape are not possible. All templates within the namespace are application-managed code; none outputs sensitive configuration values such as database credentials. The same entry data renders regardless of which template is selected.\r\n\r\nPlausible consequences include page defacement via forced template mismatch, user confusion from unexpected layouts, and potential PHP warning disclosure (internal filesystem paths) when a template receives unexpected data variables, though the latter requires `display_errors=On`, which is off in production configurations.\r\n\r\n## Proof of Concept\r\n\r\n```text\r\nPrerequisites: A YesWiki instance with at least one page containing a {{bazarliste}} action.\r\nNo authentication is required.\r\n```\r\n\r\n1. Identify a page containing `{{bazarliste}}`, for example the default Bazar listing page.\r\n2. Append `?template=blog` to the URL.\r\n3. Observe that entries render using the blog layout (`tools/bazar/presentation/templates/blog.tpl.html`) instead of the page's intended template.\r\n\r\n```bash\r\n# Baseline request (default template)\r\ncurl -s 'https://target.example/?PageWithBazarliste'\r\n\r\n# Forced template selection (blog layout)\r\ncurl -s 'https://target.example/?PageWithBazarliste&template=blog'\r\n\r\n# Within-namespace traversal on dynamic path\r\ncurl -s 'https://target.example/?PageWithBazarliste&dynamic=1&template=../../entries'\r\n\r\n# Namespace escape attempt (blocked by Twig validateName)\r\ncurl -s 'https://target.example/?PageWithBazarliste&template=../../../../../../etc/passwd'\r\n# Returns: Twig LoaderError \"Looks like you try to load a template outside configured directories\"\r\n```\r\n\r\n```text\r\nThe response to the second request renders entries in blog format (card layout with dates\r\nand excerpts) instead of the configured list format, confirming template selection bypass.\r\nThe fourth request demonstrates the namespace escape protection is effective.\r\n```\r\n\r\n## Attack Chain\r\n\r\n1. **Exposure:** An unauthenticated user sends an HTTP GET request to any YesWiki page containing a `{{bazarliste}}` action, with a `template` query parameter set to an arbitrary template name.\r\n2. **Control:** The attacker controls the full template name string. `htmlspecialchars()` at `BazarListeAction.php:80` encodes only `<>&\"` characters; template names and path traversal sequences (`../`) pass through unchanged.\r\n3. **Path:** The template name is stored in `$this->arguments['template']` and flows to `run()`. On the non-dynamic path (default), `renderEntries()` at line 357 appends `.tpl.html` if no extension is present, then calls `$this->render(\"@bazar/{$templateName}\", $data)` at line 395. On the dynamic path, `run()` calls `$this->render(\"@bazar/entries/index-dynamic-templates/{$this->arguments['template']}.twig\", ...)` at line 312.\r\n4. **Guard:** No `hasTemplate()` or `basename()` validation exists in `BazarListeAction`. The hardening commit `d49140452` added this validation to eight other actions but omitted this one. Twig's `validateName()` prevents namespace escape (rejects paths with directory level < 0) but allows within-namespace traversal (level remains >= 0).\r\n5. **Primitive:** The Twig `FilesystemLoader` resolves the attacker-chosen template name against registered `@bazar` namespace paths (`tools/bazar/templates/` and `tools/bazar/presentation/templates/`) and renders it with the entry data.\r\n6. **Result:** All visitors of the crafted URL see Bazar entries rendered with the attacker-selected template instead of the page's intended layout. No data modification, code execution, or confidentiality breach occurs.\r\n\r\n## Bypass Evidence\r\n\r\nNo prior fix or dedicated guard exists for `BazarListeAction`'s template parameter. The hardening commit `d49140452` (\"pre-auth rce + hasTemplate method everywhere to harden security\") was explicitly intended to add template validation across all actions, but its diff modifies only ten files and does not include `BazarListeAction.php`. This was verified by examining the commit's `--stat` output and confirming zero matches for `hasTemplate` or `basename` in the current `BazarListeAction.php` source.\r\n\r\nThe strongest attempt to invalidate the finding was to demonstrate that Twig's `validateName()` blocks all meaningful exploitation. While `validateName()` does prevent namespace escape (paths with `../` sequences that reduce the directory level below zero), it permits within-namespace traversal where the level remains at or above zero. For example, `entries/index-dynamic-templates/../../target` produces directory levels `[1, 2, 1, 0, 1]`, which passes validation. Additionally, simple template name substitution (e.g., `?template=blog`) requires no traversal at all, because `renderEntries()` searches both registered namespace paths for any matching filename.\r\n\r\nA second attempt to invalidate the finding explored whether any template in the `@bazar` namespace could escalate impact beyond display manipulation. No template was found that outputs sensitive configuration values, and the `config` global (containing database credentials) is only registered on the main Twig environment, not the per-request render context.\r\n\r\n## Affected Versions\r\n\r\n- Ecosystem: Composer\r\n- Package: yeswiki/yeswiki\r\n- Confirmed affected range: <= 4.6.7\r\n- Latest release checked: v4.6.7 (Packagist, 2026-08-31)\r\n- Fix status: not fixed in any released version; hardening commit d49140452 (unreleased, HEAD only) adds validation to other actions but does not fix BazarListeAction\r\n\r\nThe `BazarListeAction` class has accepted unvalidated GET `template` parameters since its initial implementation. The exact introduction boundary was not determined, but the vulnerable pattern is confirmed present from v4.6.0 through v4.6.7. The pre-release v5.0.0-alpha1 on Packagist also contains the vulnerable code. No fix exists for this specific action in any released or unreleased version.\r\n\r\n## Suggested Fix\r\n\r\nApply the same `basename()` and extension validation to `BazarListeAction::formatArguments()` that commit `d49140452` applied to the other eight actions. The template parameter should be reduced to its base filename component, and the resulting name should be verified against the set of templates that actually exist in the expected namespace directory, using the `hasTemplate()` helper already available in the codebase.\r\n\r\nAs an interim mitigation, site administrators can use a reverse proxy or web application firewall rule to strip or reject the `template` query parameter from incoming requests to pages containing Bazar listing actions. Alternatively, overriding `BazarListeAction` in a custom extension to add `basename()` sanitization would address the issue without waiting for an upstream release.\r\n\r\nReported by zx (GitHub: @manus-pi).","categories":["Unclassified"],"product":"yeswiki/yeswiki","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/YesWiki/yeswiki/security/advisories/GHSA-qfv2-x8g4-qg27","source_modified_at":"2026-10-12T10:31:39Z","fetched_at":"2026-10-28T20:58:01.013507Z","package":{"ecosystem":"composer","name":"yeswiki/yeswiki"},"range":"< 4.7.0"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/YesWiki/yeswiki/security/advisories/GHSA-qfv2-x8g4-qg27","source_modified_at":"2026-10-12T10:31:39Z","fetched_at":"2026-10-28T20:58:01.013507Z","package":{"ecosystem":"composer","name":"yeswiki/yeswiki"},"version":"4.7.0"}],"cvss":5.3,"severity":"Medium","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-10-12T10:31:39Z","modified_at":"2026-10-12T10:31:39Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/YesWiki/yeswiki/security/advisories/GHSA-qfv2-x8g4-qg27","fetched_at":"2026-10-28T20:58:01.013507Z","source_modified_at":"2026-10-12T10:31:39Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-10-12T10:31:39Z","content_hash":"e1551c2f3a6fc6302b92cd7118d99173d43b8cb869722a0ff12f08af1071ea65"}}
