phpMyFAQ Ships with an Empty API Token — And Yes, It's as Bad as It Sounds
A default empty API client token in phpMyFAQ lets any unauthenticated user create and modify FAQ entries, categories, and questions via the REST API. Here's what happened, why it matters, and how to fix it.
An Empty String Is Not a Secret
If you're running phpMyFAQ and you never touched the API client token configuration, congratulations — your FAQ system's REST API has been wide open to the internet. Not "misconfigured" open. Not "weak password" open. Default empty string as the authentication token open.
This is CVE-worthy, it's tracked as GHSA-gp95-j463-vv28, and it affects all versions since the API was introduced. Let that sink in.
What Happened
phpMyFAQ's REST API uses a client token to authenticate requests. The problem: the default value for that token is an empty string. And the authentication check doesn't treat an empty token as "not configured" — it treats it as a valid credential.
So if you send a request with an empty token (or in some implementations, no token at all), the API says "looks good to me" and lets you:
- Create new FAQ entries
- Modify existing FAQ entries
- Create and modify categories
- Submit and manipulate questions
No login. No credentials. Just hit the endpoint.
Why This Is Worse Than a Typical Auth Bypass
I see a lot of auth bypass vulnerabilities. Most of them require some chain of conditions — a specific header, a race condition, a particular version of a dependency. This one is different because:
- It's the default state. Every fresh install is vulnerable unless the admin explicitly sets a token.
- It's been there since the API was introduced. This isn't a regression. It's a design flaw that shipped and stayed.
- The attack surface is write access. This isn't just information disclosure. An attacker can inject content into your FAQ, which means potential XSS, SEO spam, phishing content, or just defacement.
Think about who runs phpMyFAQ — small to mid-size organizations, internal knowledge bases, customer-facing help centers. These are exactly the targets that don't have a WAF or API gateway sitting in front of their FAQ tool.
The Authentication "Logic"
Here's the conceptual pattern that makes this possible:
// Simplified representation of the flawed logic
$configuredToken = $faqConfig->get('api.apiClientToken'); // Returns '' by default
$requestToken = $request->headers->get('x-pmf-token');
if ($requestToken === $configuredToken) {
// Authenticated! ...except $configuredToken is ''
$this->processApiRequest();
}The fix is straightforward — you need to reject authentication when the configured token is empty:
// Proper check
$configuredToken = $faqConfig->get('api.apiClientToken');
if (empty($configuredToken)) {
// No token configured = API disabled, not API open
return new JsonResponse(['error' => 'API not configured'], 403);
}
$requestToken = $request->headers->get('x-pmf-token');
if (!hash_equals($configuredToken, $requestToken)) {
return new JsonResponse(['error' => 'Unauthorized'], 401);
}Notice I'm also using hash_equals instead of === — timing-safe comparison should be the default for any token check.
What You Should Do Right Now
If you're running phpMyFAQ:
- Set an API client token immediately. Go to your admin panel, find the API configuration, and set a strong random token. 32+ characters, generated with a CSPRNG.
- Check your FAQ content for tampering. Look for recently created or modified entries you don't recognize. Check for injected links, scripts, or spam content.
- Update to the patched version. Once available, apply the fix. Don't just set a token and call it done — the underlying logic needs to be corrected.
- Audit your access logs. Look for API requests from unexpected IPs. The endpoints to watch are anything under
/api/v2.1/or similar versioned API paths.
- Consider disabling the API entirely if you're not using it. If your FAQ is managed through the web UI only, there's no reason to have the REST API exposed.
The Bigger Lesson
This is a textbook example of why "secure by default" isn't just a slogan. Every feature that ships in an insecure default state is a vulnerability waiting to be discovered. The API should have shipped disabled, or with a required setup step that forces token generation before the first request is accepted.
I keep seeing this pattern in self-hosted PHP applications: a feature gets added, the config has a permissive default for "ease of development," and nobody goes back to lock it down before release. The developer's convenience becomes the attacker's opportunity.
If you're building APIs — any API, in any language — treat an empty or unset credential as "deny all," never as "allow all." Your future self and your users will thank you.
Related posts
- Security
How I got free cinema credit by ordering -2 popcorns
A missing input validation on M-Tix Cinema XXI's food ordering API let me increase my account balance by submitting negative quantities. No tools needed — just a browser.
May 19, 2026 · 6 min - Security
How I analyze API security headers in 30 seconds
A quick checklist for reading HTTP response headers and spotting security misconfigurations before you even look at the response body.
May 18, 2026 · 7 min - Security
Common auth mistakes I find when reverse-engineering APIs
After years of poking at APIs that weren't meant to be poked at, these are the auth patterns that break most often — and why.
May 18, 2026 · 9 min