GHSA-pqh6-8fxf-jx22: phpMyFAQ has stored XSS via | raw Filter in search.twig — html_entity_decode(strip_tags()) Bypass in Search Result Rendering
SummaryThe search result rendering template (`search.twig`) outputs FAQ content fields `result.question` and `result.answerPreview` using Twig's `| raw` filter, which completely disables the template engine's built-in auto-escaping.A user with FAQ editor/contributor privileges can store a payload encoded as HTML entities. During search result construction, `html_entity_decode(strip_tags(...))` restores the raw HTML tags — bypassing `strip_tags()` — and the restored payload is injected into every visitor's browser via the `| raw` output.This vulnerability is distinct from GHSA-cv2g-8cj8-vgc7 (affects `faq.twig`, bypass via regex mismatch in `Filter::removeAttributes()`) and is not addressed by the 4.1.1 patch.Affected Files| File | Location | Issue |
|---|---|---|
| `phpmyfaq/assets/templates/default/search.twig` | lines rendering `result.question`, `result.answerPreview` |  `(Vertical Bar) raw` disables autoescape |
| `phpmyfaq/src/phpMyFAQ/Controller/Api/SearchController.php` | search result processing loop | `html_entity_decode(strip_tags(...))` restores encoded payloads |
| `phpmyfaq/src/phpMyFAQ/Search.php` | `logSearchTerm()` | No HTML sanitization on stored search term (secondary, preventive) |DetailsVulnerability A (Primary): `search.twig` — `| raw` Disables Autoescape**File:** `phpmyfaq/assets/templates/default/search.twig`Twig's autoescape encodes all variables by default. The `| raw` filter unconditionally disables this protection. Both `result.question` and `result.answerPreview` are populated from database content (FAQ records and custom pages) that can contain attacker-controlled data.Seven (7) instances of `| raw` exist in `search.twig`:Each of these constitutes an independent XSS surface if its data source is compromised.Vulnerability B (Amplifier): `SearchController.php` — `html_entity_decode(strip_tags())` Bypass**File:** `phpmyfaq/src/phpMyFAQ/Controller/Api/SearchController.php`This pattern is a known security anti-pattern. When a payload is stored as HTML entities, `strip_tags()` passes it through unmodified (it sees no actual tags), and `html_entity_decode()` then restores the original HTML tags — reintroducing executable markup that was thought to be neutralized.**Bypass walkthrough:**Attack Chain**Prerequisites:** Attacker has FAQ editor / contributor role (low privilege).**Step 1 — Payload injection**Attacker creates or edits a FAQ entry or custom page with an HTML-entity-encoded XSS payload in the question or answer body:**Step 2 — Persistence**The payload is stored in the DB without HTML sanitization at the storage layer.**Step 3 — Victim triggers the XSS**Any user (including unauthenticated visitors and administrators) searches for a keyword matching the poisoned FAQ. The server:Retrieves the record from the databaseApplies `strip_tags()` → entity-encoded payload passes throughApplies `html_entity_decode()` → raw `<svg onload=...>` is restoredPasses the value to `search.twig` as `result.answerPreview`Template renders with `| raw` → XSS executes**Step 4 — Impact**Session cookie exfiltration → full account takeoverAdministrator session hijacking (admin visiting search page)Persistent attack: payload fires for every visitor until manually removedPotential for worm propagation via auto-created FAQ entriesPoC**Prerequisites:** Attacker has FAQ editor / contributor role (low privilege).**Step 1 — Inject payload via FAQ editor:****Step 2 — Trigger XSS as victim:**The search result page renders the restored `<svg onload=...>` payload. The attacker's server receives the victim's session cookie.**Alternative payloads (for WAF bypass):**Impact**Confidentiality :** Session cookie exfiltration and credential theft
via JavaScript execution in victim's browser context.**Integrity :** DOM manipulation, phishing overlay injection.**Scope :** Attack crosses from contributor privilege context
to all site visitors, including administrators.Recommended FixFix 1 (Critical) — Remove `| raw` from user-controlled fields in `search.twig`If HTML formatting must be preserved, apply a whitelist-based sanitizer (e.g., `ezyang/htmlpurifier`) **before** passing data to the template, then retain `| raw` only for purified output.Fix 2 (Critical) — Remove `html_entity_decode()` from search result pipeline `SearchController.php`Fix 3 (Recommended) — Audit all `| raw` usages in `search.twig`The following additional `| raw` instances should be reviewed and sanitized:Fix 4 (Preventive) — Add `htmlspecialchars()` in `logSearchTerm()`
CVSS v3.1: 6.9