File Path Traversal Vulnerability in froxlor/froxlor

Valid

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)

POC video

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.
We are processing your report and will contact the froxlor team within 24 hours. 6 months ago
ColaKumi modified the report
6 months ago
ColaKumi
6 months ago

Researcher


The domain in the video is edited in /etc/hosts, for me it's just for testing convenience

We have contacted a member of the froxlor team and are waiting to hear back 6 months ago
Michael
6 months ago

Maintainer


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.

ColaKumi
6 months ago

Researcher


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.

Michael
6 months ago

Maintainer


So admin privileges are not considered high then?

ColaKumi
6 months ago

Researcher


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

Michael Kaufmann modified the Severity from High (8.1) to Medium (6.5) 6 months ago
The researcher has received a minor penalty to their credibility for miscalculating the severity: -1
Michael Kaufmann validated this vulnerability 6 months ago
ColaKumi has been awarded the disclosure bounty
The fix bounty is now up for grabs
The researcher's credibility has increased: +7
Michael
6 months ago

Maintainer


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)) {
ColaKumi
6 months ago

Researcher


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

Michael
6 months ago

Maintainer


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

ColaKumi
6 months ago

Researcher


Yes, You're right about that

ColaKumi
6 months ago

Researcher


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

Michael
6 months ago

Maintainer


Yes I do

Michael Kaufmann marked this as fixed in 2.0.20 with commit da810e 6 months ago
The fix bounty has been dropped
This vulnerability has been assigned a CVE
This vulnerability is scheduled to go public on Jun 9th 2023
Michael Kaufmann published this vulnerability 6 months ago
to join this conversation