medium

CVE-2026-52772

Packagist · yeswiki/yeswiki

Summary

YesWiki has stored XSS in Bazar form-field templates via unescaped field.label / field.hint (|raw('html'))

Severity
medium
CVSS
5.5
EPSS
0.2% (p10)
CWE
CWE-79, CWE-116
Also known as
GHSA-xc7j-3g8q-9vh4
Published
2026-07-09
Updated
2026-07-09

Advisory details

Bazar form-field templates still apply |raw('html') to field.label / field.hint in attribute and label-body contexts — stored XSS in form renders (sibling class of commit e6b66aa)

CWE: CWE-79 (Improper Neutralization of Input During Web Page Generation, "Cross-site Scripting") via CWE-116 (Improper Encoding or Escaping of Output) — same class as the partial fix at commit e6b66aa

CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N → 4.7 (Medium)

(Privileges Required = High because writing the field definitions requires saisie_formulaire, which tools/bazar/services/Guard.php:58-61 grants only to admins by default; Scope = Changed because the XSS payload set by a form-editor admin executes in the origin context of arbitrary viewers, including unauthenticated visitors.)

Summary

Commit e6b66aa ("fix(bazar): leave the twig escape placeholder as is", 2026-05-19) recognised that emitting field.label through Twig's raw('html') filter into an HTML attribute is unsafe — Twig's raw marker suppresses the attribute auto-escape, striptags removes <…> tags but not ", so a label containing " can break out of the attribute and inject event-handler attributes. The commit fixed tools/bazar/templates/inputs/text.twig:19 and tools/bazar/templates/inputs/textarea.twig:3.

At least seven additional templates have the same pattern and were not touched by the fix:

The layouts/input.twig and layouts/field.twig files are base layouts inherited by every Bazar field type, so a single malicious field.hint reaches into every form that uses that field.

Affected

Vulnerability details

[A] — Source: field.label and field.hint are populated from form definitions

tools/bazar/fields/BazarField.php:46-53:

$this->label = empty($values[self::FIELD_LABEL]) ? '' : html_entity_decode($values[self::FIELD_LABEL]);
$this->size = $values[self::FIELD_SIZE];
$this->maxChars = $values[self::FIELD_MAX_CHARS];
$this->default = $values[self::FIELD_DEFAULT];
$this->required = $values[self::FIELD_REQUIRED] == 1;
$this->searchable = $values[self::FIELD_SEARCHABLE];
$this->hint = $values[self::FIELD_HINT];                       // [A] no decoding/escaping

field.label is html_entity_decode($values[FIELD_LABEL]) — the decode actively turns HTML-entity-encoded payloads (&quot;, &#34;) back into raw ", defeating any entity-encoded mitigation a form author might apply. field.hint is the raw string from the form definition. Both flow into the field's __toString-like context unchanged. Form definitions are written by users with the saisie_formulaire ACL (tools/bazar/services/Guard.php:45-62 — admins by default; the same ACL the audit team chose to gate imported-form POST handling under in commit fe7244b).

[B] — Sink class 1: attribute-context |raw('html')|striptags (placeholder breakout)

tools/bazar/templates/inputs/range.twig:19:

placeholder="{{ field.label|raw('html')|striptags }}"

tools/bazar/templates/inputs/email.twig:13:

placeholder="{{ field.label|raw('html')|striptags }}"

raw('html') marks the value as a Markup object, which causes Twig's HTML auto-escaper to skip it (Twig\Markup::__toString). striptags removes <…> sequences but does not touch ", ', or =. A field.label of:

hi" onmouseover="alert(document.cookie)" x="

passes striptags unchanged, is marked safe by raw('html'), and lands inside the attribute as:

placeholder="hi" onmouseover="alert(document.cookie)" x=""

The injected onmouseover fires when a viewer hovers the input. Same vector as the pre-fix text.twig:19.

[C] — Sink class 2: attribute-context |raw('html') without striptags (worse)

tools/bazar/templates/layouts/input.twig:7:

{% if field.hint %}
    <img loading="lazy" class="tooltip_aide" title="{{ field.hint|raw('html') }}" alt="{{ field.hint|raw('html') }}" src="tools/bazar/presentation/images/aide.png" width="16" height="16" />
{% endif %}

Identical patterns in tools/bazar/templates/inputs/textarea.twig:14, tools/bazar/templates/inputs/user.twig:41, tools/bazar/templates/inputs/user.twig:55, tools/bazar/templates/inputs/bookmarklet.twig:4.

There is no striptags here at all, so the attacker has the full attribute-injection alphabet plus full HTML if the parser desynchronises. Setting field.hint = '"><script>alert(1)</script>' gives:

<img … title=""><script>alert(1)</script>" alt="…" …

The <script> runs at page parse time. Because layouts/input.twig is extended by every field-type template, a single malicious field.hint on any field in any form propagates into every form render.

[D] — Sink class 3: label-body |raw (direct DOM injection)

tools/bazar/templates/layouts/input.twig:9:

{{ field.label|raw }}

tools/bazar/templates/layouts/field.twig:5:

{%- block label -%}{{ field.label|raw }}{%- endblock -%}

Plus subscribe.twig:16, linked-entry.twig:4, textarea.twig:16, bookmarklet.twig:6.

These are outside any attribute, in the body of a <label> element. raw suppresses escaping, there is no striptags. field.label = '<img src=x onerror=alert(1)>' injects an <img> tag straight into the label DOM; the onerror fires the moment the page renders, with no user interaction.

Why the fix at e6b66aa is incomplete

The fix correctly replaced field.label | raw('html') | striptags with field.label | striptags | trim (no raw) in text.twig's placeholder and textarea.twig's textarea placeholder. The fix is the right pattern — drop the raw so Twig's attribute-context autoescaper does its job — but it was applied at two specific call sites instead of being treated as a class-wide replacement. The siblings above use the same |raw('html')|striptags or |raw('html') idiom and are all currently exploitable.

Proof of concept

Setup

  1. Install YesWiki and log in as admin (or as any user with the saisie_formulaire ACL).
  2. Navigate to Bazar → Formulaires → Nouveau formulaire and create a form. Add any field of type range, email, or any other field type (every field type renders through layouts/input.twig, so the title= / alt= / label-body vectors apply universally).

PoC 1 — range.twig placeholder attribute breakout (Sink class [B])

Set the field's label to:

Enter value" onmouseover="alert('XSS via field.label in range.twig')" x="

Save the form. Have any visitor (including unauthenticated guests if the form is published) open a page that renders the form. Hovering the range input fires the injected handler.

Rendered HTML:

<input type="range" … placeholder="Enter value" onmouseover="al

References

Related advisories

Is your project exposed to this? Stateward checks every dependency on every pull request and flags it only if your code actually reaches it.

Check my repo

Summarize with AI

ChatGPTClaudePerplexity

Sources: CISA KEV (public domain), OSV.dev & GitHub Advisory Database (CC-BY-4.0), FIRST EPSS, NVD/CWE (public domain). Served live from the Stateward advisory database.