GitHub Advisoryadvisory · Packagist
GHSA-cv2g-8cj8-vgc7 phpMyFAQ: Stored XSS via Regex Bypass in Filter::removeAttributes()
MEDIUM
Official page ↗
- Aliases
- CVE-2026-34729
- Ecosystem
- Packagist
- Packages
- phpmyfaq/phpmyfaq
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N
- Weaknesses
- CWE-79
- Reviewed
- 2026-04-01
Affected packages
• Packagist phpmyfaq/phpmyfaq: < 4.1.1 → fixed in 4.1.1
Description
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
{{ question | raw }}
```
How the bypass works:
1. Attacker submits: ``
2. After `FILTER_SANITIZE_SPECIAL_CHARS`: `<img src=x onerror=alert(1)>`
3. After `html_entity_decode()`: ``
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: `` (XSS payload intact)
6. Template renders with `|raw`: JavaScript executes in browser
Why double-quoted attributes are (partially) protected:
For ``:
• 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: `` (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": "",
"answer": "Test answer",
"lang": "en",
"categories[]": 1,
"active": "yes",
"tags": "test",
"keywords": "test",
"author": "test",
"email": "[email protected]"
}
}'
```
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
```
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.
Related
Alias · CVE (1)
- CVE-2026-34729 phpMyFAQ is an open source FAQ web application. vulnerability
Weakness · CWE (1)
- CWE-79 Improper Neutralization of Input During Web Page Generation weakness