From 22fe270adb49481674e58e7540c8d2807eeae23f Mon Sep 17 00:00:00 2001 From: VirtualHotBar Date: Tue, 2 Jun 2026 21:07:36 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=89=E5=85=A8=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20-=20=E8=B7=AF=E5=BE=84=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E3=80=81=E5=AF=86=E7=A0=81=E7=BC=96=E7=A0=81=E3=80=81Zip=20Sli?= =?UTF-8?q?p=E9=98=B2=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/locales/zh-cn.json | 9 +++---- src-tauri/locales/zh-hant.json | 4 ++++ src-tauri/src/fs.rs | 44 +++++++++++++++++++++------------- src/utils/passwordEncoding.ts | 25 +++++++++++++++---- 4 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src-tauri/locales/zh-cn.json b/src-tauri/locales/zh-cn.json index 176dc73..c1c1214 100644 --- a/src-tauri/locales/zh-cn.json +++ b/src-tauri/locales/zh-cn.json @@ -161,12 +161,9 @@ "filter_rules": "过滤规则", "filter_rules_placeholder": "每行一条规则,例如:\n+ *.jpg\n+ *.png\n- *.tmp\n- *.log", "filter_rules_help": "rclone 过滤规则:+ 表示包含,- 表示排除。支持通配符 * 和 ?。留空表示不过滤。", - "resync": "重新同步", - "force_resync": "强制重新同步", - "force_resync_tip": "使用 --resync 标志强制完全重新同步。首次设置或同步状态损坏时使用。", - "resync": "重新同步", - "force_resync": "强制重新同步", - "force_resync_tip": "使用 --resync 标志强制完全重新同步。首次设置或同步状态损坏时使用。", + "resync": "重新同步", + "force_resync": "强制重新同步", + "force_resync_tip": "使用 --resync 标志强制完全重新同步。首次设置或同步状态损坏时使用。", "add_storage": "添加存储", "add_mount": "添加挂载", "add_task": "添加任务", diff --git a/src-tauri/locales/zh-hant.json b/src-tauri/locales/zh-hant.json index 86f70bd..2261e17 100644 --- a/src-tauri/locales/zh-hant.json +++ b/src-tauri/locales/zh-hant.json @@ -674,6 +674,10 @@ "unable_to_obtain_transmission_speed": "當前可能無法獲取具體傳送速率,但傳輸仍在進行。", "temp_path": "臨時路徑", "cache_path": "緩存路徑", + "clear_cache": "清理緩存", + "clear_cache_confirm": "確定要清理所有緩存文件嗎?這將刪除所有臨時緩存數據。", + "cache_cleared": "緩存已清理", + "no_cache_to_clear": "沒有需要清理的緩存", "please_select_cache_dir": "請選擇緩存路徑", "select": "選擇", "ask_restartself": "是否重啓軟件?", diff --git a/src-tauri/src/fs.rs b/src-tauri/src/fs.rs index 6baf3bf..7f4464c 100644 --- a/src-tauri/src/fs.rs +++ b/src-tauri/src/fs.rs @@ -129,8 +129,10 @@ fn app_data_dir(app: &tauri::AppHandle) -> anyhow::Result { #[tauri::command] pub fn fs_exist_dir(app: tauri::AppHandle, path: &str) -> anyhow_tauri::TAResult { - let path = resolve_path(&app, path)?; - let exists = std::fs::metadata(path) + let resolved_path = resolve_path(&app, path)?; + // 安全:验证路径在允许目录内 + let validated_path = validate_path_in_allowed_dir(&app, &resolved_path)?; + let exists = std::fs::metadata(validated_path) .map_err(anyhow::Error::from)? .is_dir(); Ok(exists) @@ -138,8 +140,10 @@ pub fn fs_exist_dir(app: tauri::AppHandle, path: &str) -> anyhow_tauri: #[tauri::command] pub fn fs_make_dir(app: tauri::AppHandle, path: &str) -> anyhow_tauri::TAResult<()> { - let path = resolve_path(&app, path)?; - std::fs::create_dir_all(path).map_err(anyhow::Error::from)?; + let resolved_path = resolve_path(&app, path)?; + // 安全:验证路径在允许目录内 + let validated_path = validate_path_in_allowed_dir(&app, &resolved_path)?; + std::fs::create_dir_all(validated_path).map_err(anyhow::Error::from)?; Ok(()) } @@ -344,6 +348,10 @@ pub fn export_config( } let out = resolve_tilde(app, out_path)?; + + // 安全:验证输出路径在允许目录内 + validate_path_in_allowed_dir(app, &out)?; + if let Some(parent) = out.parent() { if !parent.as_os_str().is_empty() { fs::create_dir_all(parent).map_err(anyhow::Error::from)?; @@ -465,18 +473,12 @@ pub fn import_config( let target_path = temp_dir.join(&entry_name); // 安全:验证目标路径在临时目录内(防止 Zip Slip) - let temp_dir_canonical = temp_dir.canonicalize().unwrap_or_else(|_| temp_dir.clone()); - // 对于新创建的文件,需要检查父目录 - if let Some(parent) = target_path.parent() { - if parent.exists() { - let parent_canonical = parent.canonicalize().unwrap_or_else(|_| parent.to_path_buf()); - if !parent_canonical.starts_with(&temp_dir_canonical) { - return Err(anyhow::anyhow!( - "安全警告:ZIP 条目路径 '{}' 试图逃逸目标目录", - entry_name - )); - } - } + // 首先检查路径字符串是否包含可疑字符 + if entry_name.contains("..") || entry_name.contains('\\') || entry_name.starts_with('/') { + return Err(anyhow::anyhow!( + "安全警告:ZIP 文件包含可疑路径 '{}'", + entry_name + )); } // 确保父目录存在 @@ -484,6 +486,16 @@ pub fn import_config( fs::create_dir_all(parent)?; } + // 验证目标路径在临时目录内 + let temp_dir_canonical = temp_dir.canonicalize().unwrap_or_else(|_| temp_dir.clone()); + let target_canonical = target_path.canonicalize().unwrap_or_else(|_| target_path.clone()); + if !target_canonical.starts_with(&temp_dir_canonical) { + return Err(anyhow::anyhow!( + "安全警告:ZIP 条目路径 '{}' 试图逃逸目标目录", + entry_name + )); + } + // 如果是目录,创建它 if entry.is_dir() { fs::create_dir_all(&target_path)?; diff --git a/src/utils/passwordEncoding.ts b/src/utils/passwordEncoding.ts index c764663..a52d1fa 100644 --- a/src/utils/passwordEncoding.ts +++ b/src/utils/passwordEncoding.ts @@ -8,9 +8,26 @@ const ENCODING_PREFIX = 'nmenc:' +/** + * 生成机器特定的密钥 + * 使用hostname和应用路径组合生成密钥,使配置文件在不同机器上不可移植 + */ +function getMachineKey(): string { + try { + // 在浏览器环境中,使用 navigator 信息 + const hostname = window.location.hostname || 'localhost' + const origin = window.location.origin || 'file://' + // 组合生成机器特定密钥 + return `NetMount_${hostname}_${origin}_2024!` + } catch { + // 回退到固定密钥(向后兼容) + return 'NetMount2024!' + } +} + /** * 简单的 XOR 编码函数 - * 使用固定的密钥对密码进行 XOR 编码,然后 base64 编码 + * 使用机器特定的密钥对密码进行 XOR 编码,然后 base64 编码 */ function xorEncode(input: string, key: string): string { let result = '' @@ -34,8 +51,8 @@ export function encodePassword(plainPassword: string): string { return plainPassword } - // 使用 XOR 编码 + base64 - const key = 'NetMount2024!' // 固定密钥 + // 使用机器特定密钥进行 XOR 编码 + base64 + const key = getMachineKey() const encoded = xorEncode(plainPassword, key) const base64 = btoa(unescape(encodeURIComponent(encoded))) @@ -58,7 +75,7 @@ export function decodePassword(encodedPassword: string): string { try { const base64 = encodedPassword.slice(ENCODING_PREFIX.length) const encoded = decodeURIComponent(escape(atob(base64))) - const key = 'NetMount2024!' + const key = getMachineKey() return xorEncode(encoded, key) // XOR 编码和解码是同一个操作 } catch { // 解码失败,返回原值(可能是旧格式)