diff --git a/tinyfilemanager.php b/tinyfilemanager.php index 642f073..5c7d4b7 100644 --- a/tinyfilemanager.php +++ b/tinyfilemanager.php @@ -604,17 +604,77 @@ if ((isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_ $url = !empty($_REQUEST["uploadurl"]) && preg_match("|^http(s)?://.+$|", stripslashes($_REQUEST["uploadurl"])) ? stripslashes($_REQUEST["uploadurl"]) : null; - //prevent 127.* domain and known ports - $domain = parse_url($url, PHP_URL_HOST); - $port = parse_url($url, PHP_URL_PORT); - $knownPorts = [22, 23, 25, 3306]; - - if (preg_match("/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/i", $domain) || in_array($port, $knownPorts)) { - $err = array("message" => "URL is not allowed"); + // Validate URL exists + if (!$url) { + $err = array("message" => "Invalid URL"); event_callback(array("fail" => $err)); exit(); } + // Parse URL components + $parsed_url = parse_url($url); + if (!$parsed_url || !isset($parsed_url['host'])) { + $err = array("message" => "Invalid URL format"); + event_callback(array("fail" => $err)); + exit(); + } + + $host = $parsed_url['host']; + $port = isset($parsed_url['port']) ? $parsed_url['port'] : null; + $scheme = isset($parsed_url['scheme']) ? strtolower($parsed_url['scheme']) : ''; + + // Only allow HTTP and HTTPS protocols + if (!in_array($scheme, ['http', 'https'])) { + $err = array("message" => "Only HTTP and HTTPS protocols are allowed"); + event_callback(array("fail" => $err)); + exit(); + } + + // Block dangerous ports (expanded list) + $blocked_ports = [21, 22, 23, 25, 110, 143, 445, 3306, 3389, 5432, 5984, 6379, 7001, 8020, 8888, 9200, 11211, 27017]; + if ($port && in_array($port, $blocked_ports)) { + $err = array("message" => "Access to this port is not allowed"); + event_callback(array("fail" => $err)); + exit(); + } + + // Resolve hostname to IP addresses + $ip_list = @gethostbynamel($host); + if ($ip_list === false || empty($ip_list)) { + // If DNS resolution fails, check if host is already an IP + $resolved_ip = @gethostbyname($host); + if ($resolved_ip === $host) { + // Check if it's a valid IP address + if (filter_var($host, FILTER_VALIDATE_IP)) { + $ip_list = [$host]; + } else { + $err = array("message" => "Cannot resolve hostname"); + event_callback(array("fail" => $err)); + exit(); + } + } else { + $ip_list = [$resolved_ip]; + } + } + + // Validate all resolved IPs are not private/internal + foreach ($ip_list as $ip) { + if (fm_is_ip_restricted($ip)) { + $err = array("message" => "Access to private/internal resources is not allowed"); + event_callback(array("fail" => $err)); + exit(); + } + } + + // If host is an IP address, validate it directly as well + if (filter_var($host, FILTER_VALIDATE_IP)) { + if (fm_is_ip_restricted($host)) { + $err = array("message" => "Access to private/internal resources is not allowed"); + event_callback(array("fail" => $err)); + exit(); + } + } + $use_curl = false; $temp_file = tempnam(sys_get_temp_dir(), "upload-"); $fileinfo = new stdClass(); @@ -639,7 +699,18 @@ if ((isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_ @$ch = curl_init($url); curl_setopt($ch, CURLOPT_NOPROGRESS, false); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 3); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); curl_setopt($ch, CURLOPT_FILE, $fp); + curl_setopt($ch, CURLOPT_USERAGENT, 'TinyFileManager/2.6'); + // Restrict protocols to HTTP/HTTPS only + if (defined('CURLOPT_PROTOCOLS')) { + curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + } + if (defined('CURLOPT_REDIR_PROTOCOLS')) { + curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + } @$success = curl_exec($ch); $curl_info = curl_getinfo($ch); if (!$success) { @@ -650,7 +721,24 @@ if ((isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_ $fileinfo->size = $curl_info["size_download"]; $fileinfo->type = $curl_info["content_type"]; } else { - $ctx = stream_context_create(); + // Create stream context with timeout and security options + $context_options = array( + 'http' => array( + 'timeout' => 10, + 'follow_location' => 1, + 'max_redirects' => 3, + 'user_agent' => 'TinyFileManager/2.6', + 'ignore_errors' => false + ), + 'https' => array( + 'timeout' => 10, + 'follow_location' => 1, + 'max_redirects' => 3, + 'user_agent' => 'TinyFileManager/2.6', + 'ignore_errors' => false + ) + ); + $ctx = stream_context_create($context_options); @$success = copy($url, $temp_file, $ctx); if (!$success) { $err = error_get_last(); @@ -734,9 +822,16 @@ if (isset($_POST['newfilename'], $_POST['newfile'], $_POST['token']) && !FM_READ } // Copy folder / file -if (isset($_GET['copy'], $_GET['finish']) && !FM_READONLY) { +if (isset($_POST['copy'], $_POST['finish'], $_POST['token']) && !FM_READONLY) { + // Validate CSRF token + if (!verifyToken($_POST['token'])) { + fm_set_msg(lng('Invalid Token.'), 'error'); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + } + // from - $copy = urldecode($_GET['copy']); + $copy = urldecode($_POST['copy']); $copy = fm_clean_path($copy); // empty path if ($copy == '') { @@ -753,7 +848,7 @@ if (isset($_GET['copy'], $_GET['finish']) && !FM_READONLY) { } $dest .= '/' . basename($from); // move? - $move = isset($_GET['move']); + $move = isset($_POST['move']); $move = fm_clean_path(urldecode($move)); // copy/move/duplicate if ($from != $dest) { @@ -1540,9 +1635,28 @@ if (isset($_GET['copy']) && !isset($_GET['finish']) && !FM_READONLY) { Destination folder:
+ + + +