\"@evil.com. PHP's FILTER_VALIDATE_EMAIL accepts this email as valid. The email is stored in the database without HTML sanitization and later rendered in the admin FAQ editor template using Twig's |raw filter, which bypasses auto-escaping entirely.\n\n### Details\n1. PHP FILTER_VALIDATE_EMAIL ac","image":"https://o3.security/opengraph.png","datePublished":"2026-03-31T22:48:45Z","dateModified":"2026-04-06T17:38:57.060243Z","url":"https://o3.security/vulnerability/GHSA-98gw-w575-h2ph","inLanguage":"en","author":{"@id":"https://o3.security/#organization"},"publisher":{"@id":"https://o3.security/#organization"},"mainEntityOfPage":{"@type":"WebPage","@id":"https://o3.security/vulnerability/GHSA-98gw-w575-h2ph"},"speakable":{"@type":"SpeakableSpecification","cssSelector":["h1",".vuln-summary",".vuln-severity",".vuln-mitigation"]},"about":[{"@type":"SoftwareApplication","name":"thorsten/phpmyfaq","applicationCategory":"Packagist","softwareVersion":"4.1.1"},{"@type":"SoftwareApplication","name":"phpmyfaq/phpmyfaq","applicationCategory":"Packagist","softwareVersion":"4.1.1"}],"citation":[{"@type":"WebPage","url":"https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-98gw-w575-h2ph"},{"@type":"WebPage","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32629"},{"@type":"WebPage","url":"https://github.com/thorsten/phpMyFAQ/releases/tag/4.1.1"}]}\"@evil.com. PHP's FILTER_VALIDATE_EMAIL accepts this email as valid. The email is stored in the database without HTML sanitization and later rendered in the admin FAQ editor template using Twig's |raw filter, which bypasses auto-escaping entirely.\n\n### Details\n1. PHP FILTER_VALIDATE_EMAIL ac","url":"https://o3.security/vulnerability/GHSA-98gw-w575-h2ph","identifier":"GHSA-98gw-w575-h2ph","datePublished":"2026-03-31T22:48:45Z","dateModified":"2026-04-06T17:38:57.060243Z","inLanguage":"en","license":"https://creativecommons.org/licenses/by/4.0/","keywords":["GHSA-98gw-w575-h2ph","CWE-20","CWE-79","CVE","vulnerability","security advisory"],"creator":{"@id":"https://o3.security/#organization"},"isAccessibleForFree":true}\"@evil.com. PHP's FILTER_VALIDATE_EMAIL accepts this email as valid. The email is stored in the database without HTML sanitization and later rendered in the admin FAQ editor template using Twig's |raw filter, which bypasses auto-escaping entirely.\n\n### Details\n1. PHP FILTER_VALIDATE_EMAIL accepts RFC-valid quoted local parts with dangerous characters\n\nphpmyfaq/src/phpMyFAQ/Controller/Front"}},{"@type":"Question","name":"How severe is GHSA-98gw-w575-h2ph?","acceptedAnswer":{"@type":"Answer","text":"No CVSS score has been assigned to GHSA-98gw-w575-h2ph yet. Review the advisory details and affected package list to assess your exposure."}},{"@type":"Question","name":"Which packages are affected by GHSA-98gw-w575-h2ph?","acceptedAnswer":{"@type":"Answer","text":"GHSA-98gw-w575-h2ph affects the following packages: thorsten/phpmyfaq (Packagist), phpmyfaq/phpmyfaq (Packagist). Ecosystems affected: Packagist."}},{"@type":"Question","name":"How do I fix GHSA-98gw-w575-h2ph?","acceptedAnswer":{"@type":"Answer","text":"Update thorsten/phpmyfaq to 4.1.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-98gw-w575-h2ph is resolved across your whole dependency graph."}},{"@type":"Question","name":"How do I detect GHSA-98gw-w575-h2ph in my Packagist dependencies?","acceptedAnswer":{"@type":"Answer","text":"Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for thorsten/phpmyfaq. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match."}},{"@type":"Question","name":"How do I mitigate GHSA-98gw-w575-h2ph if there is no patch (or I can't update yet)?","acceptedAnswer":{"@type":"Answer","text":"If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands."}},{"@type":"Question","name":"How does O3 Security protect against GHSA-98gw-w575-h2ph?","acceptedAnswer":{"@type":"Answer","text":"O3 pinpoints whether GHSA-98gw-w575-h2ph is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed."}},{"@type":"Question","name":"Is GHSA-98gw-w575-h2ph actively exploited in the wild?","acceptedAnswer":{"@type":"Answer","text":"No public exploit code has been indexed for GHSA-98gw-w575-h2ph yet. This does not mean the vulnerability cannot be exploited — absence of public exploits does not imply safety. Apply the recommended fix and use O3 Security to monitor your exposure."}},{"@type":"Question","name":"What type of vulnerability is GHSA-98gw-w575-h2ph?","acceptedAnswer":{"@type":"Answer","text":"GHSA-98gw-w575-h2ph is classified as Improper Input Validation (CWE-20), Cross-site Scripting (XSS) (CWE-79). These weakness types describe the underlying flaw category, which helps determine the potential impact and the right class of mitigation. This is a high-impact weakness class that often enables remote code execution or data exposure."}},{"@type":"Question","name":"When was GHSA-98gw-w575-h2ph published, and has it been updated?","acceptedAnswer":{"@type":"Answer","text":"GHSA-98gw-w575-h2ph was published on March 31, 2026 and was last updated on April 6, 2026. Advisory data evolves as severity scores, affected ranges, and exploit intelligence are revised — always check the latest version of the advisory before acting."}}]}
Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐘 Packagist

GHSA-98gw-w575-h2ph

GHSA-98gw-w575-h2ph is a Improper Input Validation vulnerability in thorsten/phpmyfaq. O3 Security confirms whether GHSA-98gw-w575-h2ph is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

phpMyFAQ is Vulnerable to Stored XSS via Unsanitized Email Field in Admin FAQ Editor

Also known asCVE-2026-32629
Published
Mar 31, 2026
Updated
Apr 6, 2026
Affected
2 pkgs
Patched
2 / 2
Exploits
None indexed

Blast Radius

2 pkgs affected
🐘thorsten/phpmyfaq🐘phpmyfaq/phpmyfaq

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

Description

Summary

An unauthenticated attacker can submit a guest FAQ with an email address that is syntactically valid per RFC 5321 (quoted local part) yet contains raw HTML — for example "<script>alert(1)</script>"@evil.com. PHP's FILTER_VALIDATE_EMAIL accepts this email as valid. The email is stored in the database without HTML sanitization and later rendered in the admin FAQ editor template using Twig's |raw filter, which bypasses auto-escaping entirely.

Details

  1. PHP FILTER_VALIDATE_EMAIL accepts RFC-valid quoted local parts with dangerous characters

phpmyfaq/src/phpMyFAQ/Controller/Frontend/Api/FaqController.php:99 $email = trim((string) Filter::filterVar($data->email, FILTER_VALIDATE_EMAIL)); PHP accepts "<script>alert(1)</script>"@evil.com as a valid email (RFC 5321 allows <, > inside quoted local parts). Confirmed: "<script>alert(1)</script>"@evil.com => string (valid, not false)

  1. Email stored raw without HTML sanitization

phpmyfaq/src/phpMyFAQ/Faq.php — email retrieved directly as $row->email from the database.

  1. Admin Twig template renders email with |raw

phpmyfaq/assets/templates/admin/content/faq.editor.twig:296 <input type="email" name="email" id="email" value="{{ faqData['email'] | raw }}" class="form-control">

Affected version: 4.2.0-alpha, commit f0dc86c8f

PoC

The reproduction of the vulnerability was implemented with the help of AI while reviewing the source code to generate the proof-of-concept. Please kindly note this for reference. Since the vulnerability has already been confirmed directly in the source code, the proof-of-concept code may be considered as a reference only.

Please extract the attached compressed file and proceed. poc.zip

  1. (docker compose -f docker-compose.yml down -v)
  2. docker compose -f docker-compose.yml up -d mariadb php-fpm nginx
  3. bash exploit.sh

  1. Access http://localhost:8888/admin/
  2. Log in with admin / Admin1234!
  3. After logging in, check whether the URL remains http://localhost:8888/admin/
  4. Go to Content → FAQ Administration → edit "poc" → alert popup should appear If it does not appear, you can also access it directly via: http://localhost:8888/admin/faq/edit/1/en
<img width="1388" height="239" alt="스크린샷 2026-03-12 오후 11 42 52" src="https://github.com/user-attachments/assets/b6d5446f-4eba-4cb2-9284-1bca4855142e" /> <img width="1171" height="92" alt="스크린샷 2026-03-12 오후 11 16 17" src="https://github.com/user-attachments/assets/3578e429-7106-4616-92ed-4167816d40f0" />

Impact

When an administrator opens /admin/faq/edit/{id}/{lang} to review the pending FAQ, the injected script executes in the admin's browser context. This allows an attacker to:

  • Steal the administrator's session cookie → full admin account takeover
  • Perform arbitrary admin actions (create users, modify content, change configuration)
  • Pivot to further attacks on the server

The attack chain requires no authentication. By default, records.allowNewFaqsForGuests=true allows unauthenticated FAQ submission, and records.defaultActivation=false guarantees the administrator must visit the edit page to review it.

Note on captcha: The built-in captcha is enabled by default when the PHP gd extension is present (spam.enableCaptchaCode=true). This prevents fully automated exploitation but does not prevent a targeted manual attack — an attacker can solve the captcha once and submit the payload.

Credits

wooseokdotkim

Affected Packages

2 total 2 fixed
EcosystemPackageVulnerable rangeFix
🐘Packagistthorsten/phpmyfaqall versions4.1.1
🐘Packagistphpmyfaq/phpmyfaqall versions4.1.1

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for thorsten/phpmyfaq. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update thorsten/phpmyfaq to 4.1.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-98gw-w575-h2ph is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-98gw-w575-h2ph is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-98gw-w575-h2ph. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### Summary An unauthenticated attacker can submit a guest FAQ with an email address that is syntactically valid per RFC 5321 (quoted local part) yet contains raw HTML — for example "<script>alert(1)</script>"@evil.com. PHP's FILTER_VALIDATE_EMAIL accepts this email as valid. The email is stored in the database without HTML sanitization and later rendered in the admin FAQ editor template using Twig's |raw filter, which bypasses auto-escaping entirely. ### Details 1. PHP FILTER_VALIDATE_EMAIL accepts RFC-valid quoted local parts with dangerous characters phpmyfaq/src/phpMyFAQ/Controller/Front
O3 Security · Impact-Aware SCA

Is GHSA-98gw-w575-h2ph in your dependencies?

O3 detects GHSA-98gw-w575-h2ph across Packagist dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.