From 96985898ee8d05be5b14b31dee959dfee1721ade Mon Sep 17 00:00:00 2001 From: imcraftsman Date: Tue, 21 Oct 2025 09:16:38 +0800 Subject: [PATCH] Enhance security and IP handling in tinyfilemanager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added support for trusting proxy IP headers, improved client IP retrieval, and enhanced path sanitization to prevent security issues. Updated HTML generation to escape directory and file names to prevent XSS attacks. Need to add globlal a new paramater into config.php file // **新增:是否信任反向代理的 IP 头 (例如 Cloudflare 或 Nginx)** // false (默认/最安全):只使用 REMOTE_ADDR,防止 IP 伪造。 // true (使用代理时):允许读取 HTTP_X_FORWARDED_FOR 等头,获取真实客户端 IP。 $trust_proxy = false; --- tinyfilemanager.php | 261 +++++++++++++++++++++++++++++--------------- 1 file changed, 171 insertions(+), 90 deletions(-) diff --git a/tinyfilemanager.php b/tinyfilemanager.php index fc1f50a..bd31855 100644 --- a/tinyfilemanager.php +++ b/tinyfilemanager.php @@ -130,7 +130,7 @@ $allowed_upload_extensions = ''; // Favicon path. This can be either a full url to an .PNG image, or a path based on the document root. // full path, e.g http://example.com/favicon.png // local path, e.g images/icons/favicon.png -$favicon_path = ''; +$favicon_path = '';//favicon.ico // Files and folders to excluded from listing // e.g. array('myfile.html', 'personal-folder', '*.php', '/path/to/folder', ...) @@ -178,6 +178,11 @@ $ip_blacklist = array( '::' // non-routable meta ipv6 ); +// **新增:是否信任反向代理的 IP 头 (例如 Cloudflare 或 Nginx)** +// false (默认/最安全):只使用 REMOTE_ADDR,防止 IP 伪造。 +// true (使用代理时):允许读取 HTTP_X_FORWARDED_FOR 等头,获取真实客户端 IP。 +$trust_proxy = false; + // if User has the external config file, try to use it to override the default config above [config.php] // sample config - https://tinyfilemanager.github.io/config-sample.txt $config_file = __DIR__ . '/config.php'; @@ -321,20 +326,59 @@ if (isset($_GET['logout'])) { // Validate connection IP if ($ip_ruleset != 'OFF') { - function getClientIP() - { - if (array_key_exists('HTTP_CF_CONNECTING_IP', $_SERVER)) { - return $_SERVER["HTTP_CF_CONNECTING_IP"]; - } else if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) { - return $_SERVER["HTTP_X_FORWARDED_FOR"]; - } else if (array_key_exists('REMOTE_ADDR', $_SERVER)) { - return $_SERVER['REMOTE_ADDR']; - } else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) { - return $_SERVER['HTTP_CLIENT_IP']; +/** + * 获取客户端 IP 地址。 + * 修复:仅在信任反向代理时才读取 X-Forwarded-For 等 HTTP 头。 + * + * @return string 客户端 IP 地址 + */ +function getClientIP() { + // 【核心改动】使用 global 关键字引入配置变量 + global $trust_proxy; + + // 如果 $trust_proxy 变量未定义,默认为 false + $trust_proxy = isset($trust_proxy) ? (bool)$trust_proxy : false; + + // 如果未启用信任代理,则直接返回 REMOTE_ADDR + if (!$trust_proxy) { + if (array_key_exists('REMOTE_ADDR', $_SERVER)) { + return $_SERVER['REMOTE_ADDR']; } return ''; } + // --- 仅在信任代理时检查 HTTP 头 --- + + // 检查 Cloudflare 代理 IP + if (array_key_exists('HTTP_CF_CONNECTING_IP', $_SERVER)) { + return $_SERVER['HTTP_CF_CONNECTING_IP']; + } + + // 检查其他常见的反向代理头 (X-Forwarded-For) + if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) { + // 取第一个 IP (最左边的,通常是客户端真实 IP) + $ip = trim(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]); + if (filter_var($ip, FILTER_VALIDATE_IP)) { + return $ip; + } + } + + // 检查 HTTP_CLIENT_IP + if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) { + return $_SERVER['HTTP_CLIENT_IP']; + } + + // 默认返回 REMOTE_ADDR + if (array_key_exists('REMOTE_ADDR', $_SERVER)) { + return $_SERVER['REMOTE_ADDR']; + } + + return ''; +} + + + + $clientIp = getClientIP(); $proceed = false; $whitelisted = in_array($clientIp, $ip_whitelist); @@ -501,14 +545,38 @@ defined('FM_DATETIME_FORMAT') || define('FM_DATETIME_FORMAT', $datetime_format); unset($p, $use_auth, $iconv_input_encoding, $use_highlightjs, $highlightjs_style); /*************************** ACTIONS ***************************/ - function sanitizePath($path) { +/** + * 清理和验证路径,强制路径在 FM_ROOT_PATH 内部。 + * + * @param string $path 待检查的路径 + * @return string|false 成功返回绝对路径,失败则终止程序。 + */ +function sanitizePath($path) { + // 1. 初始检查,确保路径以 '/' 开头 if (substr($path, 0, 1) !== '/') { die('Invalid file path.'); } - if ($path === '/') { - return '/'; + + // 2. 获取路径的真实绝对路径 + $realPath = realpath($path); + + // 3. 获取文件管理器根目录的真实绝对路径 (需要先定义 FM_ROOT_PATH) + // 假设 FM_ROOT_PATH 是在文件顶部定义的常量。 + $rootDir = realpath(FM_ROOT_PATH); + + // --- 核心安全检查 --- + // 4. 检查 $realPath 是否在 $rootDir 内部 + if ($realPath === false || strpos($realPath, $rootDir) !== 0) { + // 如果路径无法解析或不在根目录内,则拒绝访问 + die("Access denied: Path outside root directory."); } - return realpath($path); + // ---------------------- + + // 5. 如果路径在根目录内,则返回规范化后的路径 + if ($realPath === $rootDir) { + return $rootDir; + } + return $realPath; } // 忽略用户断开连接,确保脚本完成输出 @@ -716,19 +784,42 @@ function getFileList($dir) return $files; } // create file lists HTML +/** + * 生成目录列表的 HTML 页面 + * 修复:对目录名和文件名进行 htmlspecialchars 转义,防止 XSS 攻击。 + * + * @param string $dir 当前目录 + * @param array $fileList 文件/目录列表 + * @return string 生成的 HTML + */ function generateDirectoryListing($dir, $fileList) { - $html = "Index of {$dir}"; - $html .= "

Index of {$dir}