File Path Traversal Vulnerability in froxlor/froxlor
Reported on
May 18th 2023
Description
in the file admin_autoupdate.php
elseif ($page == 'extract') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$toExtract = isset($_POST['archive']) ? $_POST['archive'] : null;
$localArchive = Froxlor::getInstallDir() . '/updates/' . $toExtract;
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Extracting " . $localArchive . " to " . Froxlor::getInstallDir());
$result = AutoUpdate::extractZip($localArchive);
if ($result > 0) {
// error
Response::redirectTo($filename, [
'page' => 'error',
'errno' => $result
]);
}
// redirect to update-page
Response::redirectTo('admin_updates.php');
} else {
$toExtract = isset($_GET['archive']) ? $_GET['archive'] : null;
$localArchive = Froxlor::getInstallDir() . '/updates/' . $toExtract;
}
if (!file_exists($localArchive)) {
Response::redirectTo($filename, [
'page' => 'error',
'errno' => 7
]);
}
$text = lng('admin.extractdownloadedzip', [$toExtract]);
$upd_formfield = [
'updates' => [
'title' => lng('update.update'),
'image' => 'fa-solid fa-download',
'sections' => [
'section_autoupd' => [
'fields' => [
'archive' => ['type' => 'hidden', 'value' => $toExtract]
]
]
],
'buttons' => [
[
'class' => 'btn-outline-secondary',
'label' => lng('panel.cancel'),
'type' => 'reset'
],
[
'label' => lng('update.proceed')
]
]
]
];
UI::view('user/form-note.html.twig', [
'formaction' => $linker->getLink(['section' => 'autoupdate', 'page' => 'extract']),
'formdata' => $upd_formfield['updates'],
// alert
'type' => 'warning',
'alert_msg' => $text
]);
} // display error
This code fails to properly validate file paths in admin_autoupdate.php.
Attackers are able to detect if any file exists and extract compressed files by manipulating the $_GET['archive']
. This allows detect files like ../../../../../../../etc/passwd
and even extract compressed files if exists.It only checks if the path exists, but does not check if it is a sensitive path, resulting in a directory traversal vulnerability.
(The test environment is 2.0.14, but I checked the code in the current main branch and found that the logic for path validation is the same - it does not check if the path is sensitive. )
Proof of Concept
step 1: set enable_webupdate=true
step 2: set page=extract&archive=../../../../../../etc/passwd
(example : admin_autoupdate.php?page=extract&archive=../../../../../../../../../../etc/passwd)
Impact
- Sensitive information disclosure (eg. /etc/passwd, database files)
- Execute arbitrary code (if ZIP files contain executable extensions)
Automated exploitation of this vulnerability is also possible using tools like Burp Suite Intruder by traversing a dictionary list of file paths.
The domain in the video is edited in /etc/hosts, for me it's just for testing convenience
Privileges required should be "high" as in order for this to be used at all, the sysadmin would need to allow webupdate in a file on the filesystem and also webupdate is also accessible for admin-privileged users.
Okay, I understand. However, once the sysadmin enables this feature, all admin users created can exploit it. So I initially understood it as a low-privileged vulnerability.
So admin privileges are not considered high then?
You are absolutely correct, please accept my apologies. I just wanted to briefly share my initial thought process on this. Thank you for correcting me
Could you verify that the following patch resolves the issue?
diff --git a/admin_autoupdate.php b/admin_autoupdate.php
index c06331f1..dcaedae8 100644
--- a/admin_autoupdate.php
+++ b/admin_autoupdate.php
@@ -28,7 +28,7 @@ require __DIR__ . '/lib/init.php';
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
-use Froxlor\Http\HttpClient;
+use Froxlor\FileDir;
use Froxlor\Install\AutoUpdate;
use Froxlor\Settings;
use Froxlor\UI\Panel\UI;
@@ -132,7 +132,7 @@ elseif ($page == 'getdownload') {
elseif ($page == 'extract') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$toExtract = isset($_POST['archive']) ? $_POST['archive'] : null;
- $localArchive = Froxlor::getInstallDir() . '/updates/' . $toExtract;
+ $localArchive = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/updates/' . $toExtract);
$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Extracting " . $localArchive . " to " . Froxlor::getInstallDir());
$result = AutoUpdate::extractZip($localArchive);
if ($result > 0) {
@@ -146,7 +146,7 @@ elseif ($page == 'extract') {
Response::redirectTo('admin_updates.php');
} else {
$toExtract = isset($_GET['archive']) ? $_GET['archive'] : null;
- $localArchive = Froxlor::getInstallDir() . '/updates/' . $toExtract;
+ $localArchive = FileDir::makeCorrectFile(Froxlor::getInstallDir() . '/updates/' . $toExtract);
}
if (!file_exists($localArchive)) {
It appears this may still not have been fully addressed. The key problem is that the $toExtract
variable contains relative paths with multiple ../
to point to files outside of /var/www/
. It would be good to validate the $localArchive variable as a whole
Well as you can see the $toExtract
is not used directly but rather used in $localArchive
which is properly validated through FileDir::makeCorrectFile()
. You can specify ../
as much as you like, it will be converted to ./
if you've checked correctly
Thank you very much for maintaining this project. May I ask if you have the authority to assign a CVE? Thank you for your time and understanding