All vulnerabilities
GHSA-fg92-v5rr-4xjq

phpMyFAQ: Stored XSS via Over-Permissive HTML Sanitizer Allowing `<iframe src="data:text/html,...">`

phpMyFAQ
CVSS
5.4Medium
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed in4.1.9Fixed versionGitHub
Published3 Oct 2026Updated 3 Oct 2026GitHub

What fixes it

A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.

Description

Summary :

phpMyFAQ's HTML sanitization configuration allows both the <iframe> element and the data: URI scheme.

Because data: URLs do not contain a hostname, the configured media-host allowlist does not restrict them. As a result, an attacker with permission to submit or create rich-text FAQ/News content may store an iframe whose src contains a data:text/html document with embedded JavaScript.

The malicious content survives sanitization and is later rendered using Twig's |raw filter, causing arbitrary JavaScript execution inside the embedded iframe when a victim views the affected FAQ or News page.

Severity :

Medium

CVSS 3.1:

CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N

Score: 6.1 (Medium)

CWE:

  • CWE-79 — Improper Neutralization of Input During Web Page Generation
  • CWE-80 — Improper Neutralization of Script-Related HTML Tags

Confidence:

High

The issue was reproduced at runtime against the real symfony/html-sanitizer configuration used by phpMyFAQ.

Affected Version :

phpMyFAQ 4.2.x

Confirmed on:

phpMyFAQ 4.2.0-beta

Reviewed Git commit:

0fb7ecd

Affected Component :

FAQ and News rich-text content rendering.

Affected files include:

phpmyfaq/src/phpMyFAQ/Helper/FaqHelper.php

phpmyfaq/assets/templates/default/faq.twig

phpmyfaq/assets/templates/default/news.twig

Relevant entry points include:

FAQ creation API

POST /api/.../faqs

and the Administration News API.

Description :

phpMyFAQ uses Symfony's HTML Sanitizer to clean rich-text content before displaying it.

The sanitizer configuration explicitly permits the <iframe> element:

->allowElement(
    'iframe',
    ['title', 'src', 'width', 'height', 'allow', 'allowfullscreen']
)

At the same time, the following media schemes are allowed:

->allowMediaSchemes([
    'https',
    'http',
    'mailto',
    'data'
])

and link schemes include:

->allowLinkSchemes([
    'https',
    'http',
    'mailto',
    'data'
])

The combination of:

iframe
+
data:

is unsafe.

An attacker can supply content such as:

<iframe src="data:text/html,<script>alert(document.domain)</script>"></iframe>

The sanitizer produces output similar to:

<iframe src="data:text/html,&lt;script&gt;alert(document.domain)&lt;/script&gt;"></iframe>

Although the <script> characters are HTML-encoded inside the attribute, the browser decodes the attribute value when navigating the iframe.

The resulting iframe therefore loads:

data:text/html,<script>alert(document.domain)</script>

and executes the JavaScript.

The host allowlist does not prevent this because data: URLs are hostless.

The sanitized content is later rendered into templates using unescaped output:

{{ answer|raw }}

for FAQ content and:

{{ mainPageContent|raw }}

for News content.

This converts the sanitizer weakness into stored script execution.

Proof of Concept :

A minimal payload is:

<iframe src="data:text/html,<script>alert(document.domain)</script>"></iframe>

The audit reproduced the sanitizer behavior with the exact phpMyFAQ configuration.

Sanitizer input:

<iframe src="data:text/html,<script>alert(document.domain)</script>"></iframe>

Observed sanitizer output:

<iframe src="data:text/html,&lt;script&gt;alert(document.domain)&lt;/script&gt;"></iframe>

When this markup is interpreted by the browser, the iframe navigates to a data:text/html document and executes the embedded JavaScript.

Other common payloads such as:

<script>alert(1)</script>
<svg onload=alert(1)>

and:

<img src=x onerror=alert(1)>

were stripped by the sanitizer.

An external iframe URL such as:

<iframe src="https://evil.example/"></iframe>

also loses its src when the hostname is not in the configured allowlist.

This demonstrates that the vulnerability specifically results from the interaction between the permitted iframe element and the allowed hostless data: scheme.

Steps To Reproduce :

  1. Deploy phpMyFAQ 4.2.x in a controlled test environment.
  1. Ensure that FAQ rich-text submission is enabled.

For the frontend FAQ flow, this can apply when:

main.enableWysiwygEditorFrontend = true

and the current account is permitted to create FAQ content.

  1. Submit an FAQ answer containing:
<iframe src="data:text/html,<script>alert(document.domain)</script>"></iframe>
  1. Save the FAQ.
  1. Open the public page that renders the affected FAQ.
  1. The FAQ content is processed through:
FaqHelper::cleanUpContent()
  1. Observe that the <iframe> element remains present and its data: URL is preserved.
  1. The content is rendered in:
faq.twig

using:

{{ answer|raw }}
  1. The victim's browser loads the data:text/html document inside the iframe.
  1. The embedded JavaScript executes.

The same sanitizer configuration also affects News content rendered through:

{{ mainPageContent|raw }}

Impact :

An attacker who can inject rich-text FAQ or News content can store arbitrary JavaScript that executes when another user views the affected public page.

Potential consequences include:

Phishing UI

The injected iframe can display arbitrary HTML and JavaScript-controlled interfaces designed to imitate the legitimate application.

Top-Frame Redirect

JavaScript executing in the iframe may attempt to redirect the top-level page to an attacker-controlled destination.

External Resource Loading

The iframe can load attacker-controlled resources, enabling tracking or interaction with external infrastructure.

Persistent Execution

Because the payload is stored as FAQ or News content, it can affect multiple users until the malicious content is removed.

The impact is lower than traditional same-origin stored XSS.

The script executes inside a data: iframe with an opaque origin.

Therefore it cannot directly access:

phpMyFAQ session cookies
same-origin DOM
CSRF tokens from the parent page

under normal browser origin isolation rules.

Nevertheless, attacker-controlled JavaScript execution remains possible on a trusted phpMyFAQ page and can be used for phishing or navigation-based attacks.

Root Causes :

The vulnerability is caused by an unsafe sanitizer policy.

The configuration simultaneously allows:

iframe

and:

data:

media URLs.

The data: scheme is likely intended to support inline images, but it is allowed generically rather than being restricted to safe MIME types such as:

data:image/png
data:image/jpeg
data:image/gif
data:image/webp

Because data: URLs do not have a hostname, the following host restriction:

->allowMediaHosts($allowedHosts)

does not constrain them.

The resulting sanitized content is then passed into Twig templates using:

|raw

which relies entirely on the sanitizer to remove dangerous content.

Therefore the security failure is caused by the combination of:

  1. <iframe> being permitted.
  2. data: being permitted in media schemes.
  3. Host validation not applying to hostless data: URLs.
  4. Sanitized rich-text content being rendered with |raw.

Suggested Fix :

Remove the data: scheme from allowed media and link schemes.

For example:

->allowElement(
    'iframe',
    ['title', 'src', 'width', 'height', 'allow', 'allowfullscreen']
)

->allowMediaSchemes([
    'https',
    'http',
    'mailto'
])

->allowMediaHosts($allowedHosts)

->allowLinkSchemes([
    'https',
    'http',
    'mailto'
]);

If inline images are required, do not enable generic data: URLs.

Instead, validate them separately and allow only explicitly supported image MIME types, for example:

^data:image/(png|jpeg|gif|webp);base64,

A data: URI containing:

text/html

must never be accepted as the source of an iframe.

Additional hardening should include:

  • Add a sandbox attribute to allowed iframes.
  • Restrict iframe sources to a strict allowlist of trusted hosts.
  • Deploy a strong Content-Security-Policy.
  • Restrict frame-src.
  • Avoid unsafe-inline in script-src.
  • Add regression tests for dangerous data: iframe payloads.

A suitable regression test should verify that:

<iframe src="data:text/html,<script>1</script>"></iframe>

is either completely removed or has its src attribute removed.

At the same time, a legitimate trusted embed such as:

<iframe src="https://www.youtube.com/embed/example"></iframe>

may remain allowed when its host is explicitly trusted.

References :

Affected source locations:

  • phpmyfaq/src/phpMyFAQ/Helper/FaqHelper.php
  • phpmyfaq/assets/templates/default/faq.twig
  • phpmyfaq/assets/templates/default/news.twig
  • phpmyfaq/src/phpMyFAQ/Controller/Frontend/Api/FaqController.php
  • phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/NewsController.php

Relevant locations:

FaqHelper.php:134-137

faq.twig:38

news.twig:9

Frontend/Api/FaqController.php:123

Administration/Api/NewsController.php:178,192

Weakness classifications:

  • CWE-79 — Cross-Site Scripting
  • CWE-80 — Improper Neutralization of Script-Related HTML Tags

Relevant technology:

  • Symfony HTML Sanitizer
  • Twig raw rendering
  • data: URI scheme
  • HTML <iframe> embedding

Text from GitHub Advisory Database

Affected versions 1

  • phpMyFAQ, php <= 4.1.8GitHub