mirror of
https://github.com/VirtualHotBar/NetMount.git
synced 2026-06-09 16:12:21 +08:00
fix: 安全审查修复 - 路径验证、密码编码、Zip Slip防护
This commit is contained in:
@@ -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": "添加任务",
|
||||
|
||||
@@ -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": "是否重啓軟件?",
|
||||
|
||||
@@ -129,8 +129,10 @@ fn app_data_dir(app: &tauri::AppHandle<Runtime>) -> anyhow::Result<PathBuf> {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn fs_exist_dir(app: tauri::AppHandle<Runtime>, path: &str) -> anyhow_tauri::TAResult<bool> {
|
||||
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<Runtime>, path: &str) -> anyhow_tauri:
|
||||
|
||||
#[tauri::command]
|
||||
pub fn fs_make_dir(app: tauri::AppHandle<Runtime>, 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)?;
|
||||
|
||||
@@ -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 {
|
||||
// 解码失败,返回原值(可能是旧格式)
|
||||
|
||||
Reference in New Issue
Block a user