All vulnerabilities
GHSA-qfv2-x8g4-qg27

yeswiki/yeswiki: YesWiki BazarListeAction unauthenticated within-namespace template traversal via GET parameter

yeswiki/yeswiki
CVSS
5.3Medium
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed in4.7.0Fixed versionGitHub
Published12 Oct 2026Updated 12 Oct 2026GitHub

What fixes it

  • 4.7.0yeswiki/yeswiki, composerGitHubcomposer require "yeswiki/yeswiki:^4.7.0"

A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.

Description

Summary

YesWiki'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.

Root Cause

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.

// tools/bazar/actions/BazarListeAction.php, lines 78-81
$template = $get->get('template') ?? $arg['template'] ?? null;
if ($template) {
    $template = htmlspecialchars($template);
}

The 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.

Impact

An 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.

The 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.

Plausible 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.

Proof of Concept

Prerequisites: A YesWiki instance with at least one page containing a {{bazarliste}} action.
No authentication is required.
  1. Identify a page containing {{bazarliste}}, for example the default Bazar listing page.
  2. Append ?template=blog to the URL.
  3. Observe that entries render using the blog layout (tools/bazar/presentation/templates/blog.tpl.html) instead of the page's intended template.
# Baseline request (default template)
curl -s 'https://target.example/?PageWithBazarliste'

# Forced template selection (blog layout)
curl -s 'https://target.example/?PageWithBazarliste&template=blog'

# Within-namespace traversal on dynamic path
curl -s 'https://target.example/?PageWithBazarliste&dynamic=1&template=../../entries'

# Namespace escape attempt (blocked by Twig validateName)
curl -s 'https://target.example/?PageWithBazarliste&template=../../../../../../etc/passwd'
# Returns: Twig LoaderError "Looks like you try to load a template outside configured directories"
The response to the second request renders entries in blog format (card layout with dates
and excerpts) instead of the configured list format, confirming template selection bypass.
The fourth request demonstrates the namespace escape protection is effective.

Attack Chain

  1. 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.
  2. 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.
  3. 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.
  4. 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).
  5. 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.
  6. 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.

Bypass Evidence

No 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.

The 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.

A 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.

Affected Versions

  • Ecosystem: Composer
  • Package: yeswiki/yeswiki
  • Confirmed affected range: <= 4.6.7
  • Latest release checked: v4.6.7 (Packagist, 2026-08-31)
  • Fix status: not fixed in any released version; hardening commit d49140452 (unreleased, HEAD only) adds validation to other actions but does not fix BazarListeAction

The 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.

Suggested Fix

Apply 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.

As 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.

Reported by zx (GitHub: @manus-pi).

Text from GitHub Advisory Database

Affected versions 1

  • yeswiki/yeswiki, composer< 4.7.0GitHub