From b624a2edcaf270e48d10b791b7286b000b6c7daa Mon Sep 17 00:00:00 2001
From: Maalfer
Date: Wed, 7 Jan 2026 15:05:41 +0100
Subject: [PATCH] CSRF and SSRF vulnerabilities fixed
---
tinyfilemanager.php | 192 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 179 insertions(+), 13 deletions(-)
diff --git a/tinyfilemanager.php b/tinyfilemanager.php
index 642f0730..5c7d4b7c 100644
--- a/tinyfilemanager.php
+++ b/tinyfilemanager.php
@@ -604,17 +604,77 @@ function get_file_path()
$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];
+ // 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();
+ }
- 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");
+ // 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 @@ function get_file_path()
@$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 @@ function get_file_path()
$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 @@ function get_file_path()
}
// 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 @@ function get_file_path()
}
$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 @@ function getUploadExt()
Destination folder:
- Copy
- Move
- Cancel
+
+
+
+
+
@@ -2357,6 +2471,58 @@ class="edit-file">