| Gói / sản phẩm | Phiên bản bị ảnh hưởng | Phiên bản đã vá |
|---|---|---|
| phpmyfaq/phpmyfaq | < 4.1.2 | 4.1.2 |
| thorsten/phpmyfaq | < 4.1.2 | 4.1.2 |
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.
phpmyfaq/src/phpMyFAQ/Instance/Client.php:583-591:
public function deleteClientFolder(string $sourceUrl): bool
{
if (!$this->isMultiSiteWriteable()) {
return false;
}
$sourcePath = str_replace(search: 'https://', replace: '', subject: $sourceUrl);
return $this->filesystem->deleteDirectory($this->clientFolder . $sourcePath);
}
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.
Callers feed the URL from the HTTP request body:
phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/InstanceController.php:184:
if (1 !== $instanceId && $client->deleteClientFolder($clientData->url) && $client->delete($instanceId)) {
$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().
InstanceController.php:144 (edit path) and Controller/Administration/InstanceController.php:151 (form path) both reach the same sink through different entry points.
GHSA-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.
Prerequisites: 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.
Step 1: Alice authenticates and retrieves the CSRF token for the instance admin page.
Step 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:
curl -sS -b "$ALICE_COOKIE" -X POST "$BASE/admin/api/instance" \
-H "Content-Type: application/json" -H "x-csrf-token: $CSRF" \
-d '{"url":"https://../../../tmp/pmf-poc/","instance":"poc","comment":"poc","email":"a@b","admin":"alice","password":"poc1234!"}'
Step 3: Alice deletes the instance. The request body names the instance id to delete; the controller hands clientData->url directly to deleteClientFolder:
curl -sS -b "$ALICE_COOKIE" -X POST "$BASE/admin/api/instance/2" \
-H "Content-Type: application/json" -H "x-csrf-token: $CSRF" \
-d '{"url":"https://../../../tmp/pmf-poc/"}'
The server computes $sourcePath = '../../../tmp/pmf-poc/', concatenates to <clientFolder>/, and recursively deletes the resulting path.
Live 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.
Any phpMyFAQ admin holding INSTANCE_ADD + INSTANCE_DELETE but not SUPER_ADMIN can delete arbitrary directories writable by the PHP process. Outcomes:
clientFolder into peer directories.content/, config/, or cache directories and lock the install out.phpMyFAQ'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.
Canonicalize and validate the URL before forming the filesystem path.
phpmyfaq/src/phpMyFAQ/Instance/Client.php:583:
public function deleteClientFolder(string $sourceUrl): bool
{
if (!$this->isMultiSiteWriteable()) {
return false;
}
$parsed = parse_url($sourceUrl);
if (!is_array($parsed) || !isset($parsed['host']) || ($parsed['scheme'] ?? '') !== 'https') {
return false;
}
$host = $parsed['host'];
if (!preg_match('/^[a-z0-9][a-z0-9.-]*$/i', $host)) {
return false;
}
$target = realpath($this->clientFolder . $host);
$root = realpath($this->clientFolder);
if ($target === false || $root === false || !str_starts_with($target, $root . DIRECTORY_SEPARATOR)) {
return false;
}
return $this->filesystem->deleteDirectory($target);
}
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.
Found by aisafe.io
Bạn đang dùng một trong các sản phẩm này? Quét site để biết có đang lộ phiên bản có lỗ hổng không.
Quét site miễn phí