Summary
The authorization helper QuestionController::isAddingQuestionsAllowed() returns true for unauthenticated callers whenever the global feature toggle main.enableAskQuestions is enabled, completely ignoring the separate records.allowQuestionsForGuests setting. This allows unauthenticated users to submit questions via the question/create API endpoint even when the administrator has explicitly disabled guest submissions.
Details
In phpmyfaq/src/phpMyFAQ/Controller/Frontend/Api/QuestionController.php, the private isAddingQuestionsAllowed() method contains a logic flaw where two independent configuration checks both independently grant full access:
private function isAddingQuestionsAllowed(): bool
{
if ($this->configuration->get(item: 'records.allowQuestionsForGuests')) {
return true;
}
if ($this->configuration->get(item: 'main.enableAskQuestions')) {
return true; // ← grants access to ALL callers, regardless of login state
}
return $this->currentUser->perm->hasPermission(
$this->currentUser->getUserId(),
PermissionType::QUESTION_ADD->value,
);
}
The intended semantics of these two settings are:
main.enableAskQuestions — master on/off toggle for the "Ask a Question" feature.
records.allowQuestionsForGuests — whether unauthenticated users may use this feature.
However, the implementation treats enableAskQuestions=true as "grant access to everyone," making the guest-control setting irrelevant. When the feature is enabled (the expected state for any deployment using the feature), the permission check on the final return statement is dead code — it is only reached when the feature is disabled entirely.
The controller proceeds to persist the question and trigger admin notifications after this check:
$this->question->add($questionEntity);
$this->notification->sendQuestionSuccessMail($questionEntity, $categories);
Note on severity: This is a borderline finding. The practical impact is limited to unauthorized data creation in the question moderation queue. An attacker cannot read, modify, or delete existing data, and submitted questions still go through the admin review workflow. However, it represents a clear contradiction of the admin's configured intent and could be abused for spam injection.
Suggested fix — restructure the method so the feature toggle gates the entire feature, login state determines whether the guest setting applies, and authenticated users are checked for the QUESTION_ADD permission:
private function isAddingQuestionsAllowed(): bool
{
- if ($this->configuration->get(item: 'records.allowQuestionsForGuests')) {
- return true;
+ if (!$this->configuration->get(item: 'main.enableAskQuestions')) {
+ return false;
}
- if ($this->configuration->get(item: 'main.enableAskQuestions')) {
- return true;
+ if (!$this->currentUser->isLoggedIn()) {
+ return (bool) $this->configuration->get(item: 'records.allowQuestionsForGuests');
}
return $this->currentUser->perm->hasPermission(
$this->currentUser->getUserId(),
- PermissionType::QUESTION_ADD->value,
+ PermissionType::QUESTION_ADD->value
);
}
PoC
Prerequisites:
- A running phpMyFAQ instance (any supported 4.x version).
- Admin access to configure settings.
Step 1: In the admin panel, set the following configuration:
main.enableAskQuestions = true (enable the "Ask a Question" feature)
records.allowQuestionsForGuests = false (disable guest submissions)
Step 2: Open a private/incognito browser window (no login session, no cookies).
Step 3: Send an unauthenticated POST request to the question creation endpoint:
curl -i -X POST 'https://<host>/api/question/create' \
-H 'Content-Type: application/json' \
--data '{"lang":"en","question":"test from guest","name":"guest","email":"guest@example.org","category":1}'
Expected result (with fix): HTTP 403 Forbidden — the unauthenticated caller is rejected because records.allowQuestionsForGuests is false.
Actual result (vulnerable): HTTP 200 OK with a success message. The question is persisted via $this->question->add(...) and an admin notification email is triggered via $this->notification->sendQuestionSuccessMail(...).
Root cause verification: You can confirm the logic flaw by reading isAddingQuestionsAllowed() in QuestionController.php — the second if branch (main.enableAskQuestions) returns true unconditionally, without checking whether the caller is authenticated. This means the records.allowQuestionsForGuests=false setting on the first branch is never relevant when the feature is enabled, because the second branch short-circuits before the permission check is reached.
Impact
This is a low-severity authorization bypass. The records.allowQuestionsForGuests configuration setting is rendered ineffective whenever the ask-questions feature is enabled, allowing unauthenticated users to inject questions into the admin moderation queue.
- Who is impacted: Any phpMyFAQ deployment that has
main.enableAskQuestions=true (the standard state for deployments using the feature) while relying on records.allowQuestionsForGuests=false to restrict question submission to authenticated users.
- What an attacker can do: Submit arbitrary questions to the moderation queue without authentication, bypassing the admin's configured guest restriction. This could be used for spam injection or to generate unwanted admin notification emails.
- What an attacker cannot do: Read, modify, or delete existing data. Submitted questions still enter the review queue and require admin action to be published.
Affected versions
= 4.0.0-alpha.2, <= 4.2.0-alpha (latest pre-release), and <= 4.1.7 (latest stable release)
The vulnerable authorization logic was introduced in commit 51d80d33a ("feat(api): refactored private API to add questions"), first included in version 4.0.0-alpha.2. The flaw is present in every subsequent release through 4.1.7 (latest stable) and 4.2.0-alpha (latest pre-release). The current development HEAD is also affected. Per SECURITY.md, versions 4.1.x and 4.2.x are supported for security fixes; versions < 4.1 are end of life.
Pavel Kohout
Aisle Research
Summary
The authorization helper
QuestionController::isAddingQuestionsAllowed()returnstruefor unauthenticated callers whenever the global feature togglemain.enableAskQuestionsis enabled, completely ignoring the separaterecords.allowQuestionsForGuestssetting. This allows unauthenticated users to submit questions via thequestion/createAPI endpoint even when the administrator has explicitly disabled guest submissions.Details
In
phpmyfaq/src/phpMyFAQ/Controller/Frontend/Api/QuestionController.php, the privateisAddingQuestionsAllowed()method contains a logic flaw where two independent configuration checks both independently grant full access:The intended semantics of these two settings are:
main.enableAskQuestions— master on/off toggle for the "Ask a Question" feature.records.allowQuestionsForGuests— whether unauthenticated users may use this feature.However, the implementation treats
enableAskQuestions=trueas "grant access to everyone," making the guest-control setting irrelevant. When the feature is enabled (the expected state for any deployment using the feature), the permission check on the finalreturnstatement is dead code — it is only reached when the feature is disabled entirely.The controller proceeds to persist the question and trigger admin notifications after this check:
Note on severity: This is a borderline finding. The practical impact is limited to unauthorized data creation in the question moderation queue. An attacker cannot read, modify, or delete existing data, and submitted questions still go through the admin review workflow. However, it represents a clear contradiction of the admin's configured intent and could be abused for spam injection.
Suggested fix — restructure the method so the feature toggle gates the entire feature, login state determines whether the guest setting applies, and authenticated users are checked for the
QUESTION_ADDpermission:private function isAddingQuestionsAllowed(): bool { - if ($this->configuration->get(item: 'records.allowQuestionsForGuests')) { - return true; + if (!$this->configuration->get(item: 'main.enableAskQuestions')) { + return false; } - if ($this->configuration->get(item: 'main.enableAskQuestions')) { - return true; + if (!$this->currentUser->isLoggedIn()) { + return (bool) $this->configuration->get(item: 'records.allowQuestionsForGuests'); } return $this->currentUser->perm->hasPermission( $this->currentUser->getUserId(), - PermissionType::QUESTION_ADD->value, + PermissionType::QUESTION_ADD->value ); }PoC
Prerequisites:
Step 1: In the admin panel, set the following configuration:
main.enableAskQuestions = true(enable the "Ask a Question" feature)records.allowQuestionsForGuests = false(disable guest submissions)Step 2: Open a private/incognito browser window (no login session, no cookies).
Step 3: Send an unauthenticated POST request to the question creation endpoint:
Expected result (with fix): HTTP 403 Forbidden — the unauthenticated caller is rejected because
records.allowQuestionsForGuestsisfalse.Actual result (vulnerable): HTTP 200 OK with a success message. The question is persisted via
$this->question->add(...)and an admin notification email is triggered via$this->notification->sendQuestionSuccessMail(...).Root cause verification: You can confirm the logic flaw by reading
isAddingQuestionsAllowed()inQuestionController.php— the secondifbranch (main.enableAskQuestions) returnstrueunconditionally, without checking whether the caller is authenticated. This means therecords.allowQuestionsForGuests=falsesetting on the first branch is never relevant when the feature is enabled, because the second branch short-circuits before the permission check is reached.Impact
This is a low-severity authorization bypass. The
records.allowQuestionsForGuestsconfiguration setting is rendered ineffective whenever the ask-questions feature is enabled, allowing unauthenticated users to inject questions into the admin moderation queue.main.enableAskQuestions=true(the standard state for deployments using the feature) while relying onrecords.allowQuestionsForGuests=falseto restrict question submission to authenticated users.Affected versions
The vulnerable authorization logic was introduced in commit
51d80d33a("feat(api): refactored private API to add questions"), first included in version4.0.0-alpha.2. The flaw is present in every subsequent release through4.1.7(latest stable) and4.2.0-alpha(latest pre-release). The current development HEAD is also affected. PerSECURITY.md, versions 4.1.x and 4.2.x are supported for security fixes; versions < 4.1 are end of life.Pavel Kohout
Aisle Research