From 6ccd7b4f9c98343a481686a5c58709d163d5e449 Mon Sep 17 00:00:00 2001 From: Pablo Zmdl Date: Tue, 10 Dec 2024 09:26:57 +0100 Subject: [PATCH] Script to test remote access to `temp_dir` and `log_dir` Those two config options have been set to directories inside the document root, which is only protected if the HTTP daemon is Apache and that loads .htaccess files. So in a lot of situations out there, people's temporary files (which includes message texts and attachments during while they are edited) and log files (which might include email adresses) are very public. --- bin/test-remote-access.php | 90 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 bin/test-remote-access.php diff --git a/bin/test-remote-access.php b/bin/test-remote-access.php new file mode 100644 index 00000000000..81985a6848b --- /dev/null +++ b/bin/test-remote-access.php @@ -0,0 +1,90 @@ + curl_exec($ch), + 'headers' => curl_getinfo($ch), + ]; +} + +function test_dir($dir_name, $base_url) +{ + try { + $rcmail = rcmail::get_instance(); + $temp_folder_path = rtrim($rcmail->config->get($dir_name, ''), '/'); + if ($temp_folder_path === '') { + error("Something's wrong: couldn't get config value for '{$dir_name}'!"); + } + + $temp_folder_url_path = str_replace(RCUBE_INSTALL_PATH, '', $temp_folder_path); + + // Write something into a random file name. + $random_string = bin2hex(random_bytes(10)); + $test_filename = "{$random_string}.txt"; + $test_path = $temp_folder_path . \DIRECTORY_SEPARATOR . $test_filename; + fwrite(fopen($test_path, 'w'), 'test'); + + // Test access to that file via HTTP. + $url = rtrim($base_url, '/') . '/' . $temp_folder_url_path . '/' . $test_filename; + $data = get_http_data($url); + $status_code = $data['headers']['http_code']; + switch (intdiv($status_code, 100)) { + case 2: + say("\n\n❌ ATTENTION: Your configuration is vulnerable to massive information leakage of sensitive data from the configured '{$dir_name}' folder!\nWe strongly recommend to change this option to a directory outside of the document root (it must still be accessible for the PHP process).\n"); + return 1; + case 4: + say("✅ Ok, you're safe. Your '{$dir_name}' directory is not accessible from the outside world"); + return 0; + default: + say("⚠️ Could not determine if your server is vulnerable or not. It returned status code {$status_code} for our test URL {$url}"); + return 1; + } + } finally { + if (isset($test_path)) { + unlink($test_path); + } + } +} + +if (!isset($argv[1]) || in_array($argv[1], ['', '-h', '--help'])) { + error('Usage: ' . basename(__FILE__) . ' your_roundcubemail_base_url'); +} + +$base_url = filter_var($argv[1], \FILTER_VALIDATE_URL); + +if ($base_url === false) { + error('⚠️ The given argument not a valid base URL!'); +} + +$data = get_http_data($base_url); +// Check if this URL actually serves a Roundcubemail. Note: This check must work +// for very old versions of Roundcubemail, too! +if (!str_contains($data['body'], 'program/js/app.')) { + error('⚠️ Error: The given URL is not serving Roundcubemail!'); +} + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/'); +require_once INSTALL_PATH . 'program/include/clisetup.php'; + +$return_values = []; +$return_values[] = test_dir('temp_dir', $base_url); +$return_values[] = test_dir('log_dir', $base_url); + +exit(max($return_values));