mirror of
https://github.com/2dust/v2rayN.git
synced 2026-10-11 15:54:59 +03:00
Prevent zip path traversal in app upgrade
Hardened upgrade extraction by validating each zip entry resolves under the app startup directory before writing files, and aborting with a clear failure message on suspicious paths. Added shared path utilities for directory containment checks and OS-aware path string comparison, then reused them for executable matching and `bin` prefix checks during extraction. https://github.com/2dust/v2rayN/issues/10313
This commit is contained in:
@@ -45,6 +45,8 @@ internal class UpgradeApp
|
||||
var thisAppOldFile = $"{Utils.GetExePath()}.tmp";
|
||||
File.Delete(thisAppOldFile);
|
||||
var splitKey = "/";
|
||||
var allowedBaseDir = Utils.StartupPath();
|
||||
var pathComparison = Utils.GetPathComparison();
|
||||
|
||||
using var archive = ZipFile.OpenRead(fileName);
|
||||
foreach (var entry in archive.Entries)
|
||||
@@ -65,16 +67,21 @@ internal class UpgradeApp
|
||||
}
|
||||
|
||||
var fullName = string.Join(splitKey, lst[1..lst.Length]);
|
||||
var entryOutputPath = Utils.GetPath(fullName);
|
||||
if (!Utils.IsPathUnderDirectory(allowedBaseDir, entryOutputPath))
|
||||
{
|
||||
Console.WriteLine($"{Resx.Resource.FailedUpgrade} blocked potential path traversal: {entry.FullName}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(Utils.GetExePath(), Utils.GetPath(fullName), StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(Utils.GetExePath(), entryOutputPath, pathComparison))
|
||||
{
|
||||
File.Move(Utils.GetExePath(), thisAppOldFile);
|
||||
}
|
||||
|
||||
var entryOutputPath = Utils.GetPath(fullName);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!);
|
||||
//In the bin folder, if the file already exists, it will be skipped
|
||||
if (fullName.StartsWith("bin") && File.Exists(entryOutputPath))
|
||||
if (fullName.StartsWith("bin", pathComparison) && File.Exists(entryOutputPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,28 @@ internal class Utils
|
||||
return Path.Combine(startupPath, fileName);
|
||||
}
|
||||
|
||||
public static bool IsPathUnderDirectory(string baseDir, string targetPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(baseDir) || string.IsNullOrWhiteSpace(targetPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var baseFull = Path.GetFullPath(baseDir);
|
||||
var targetFull = Path.GetFullPath(targetPath);
|
||||
|
||||
baseFull = baseFull.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
|
||||
return targetFull.StartsWith(baseFull, GetPathComparison());
|
||||
}
|
||||
|
||||
public static StringComparison GetPathComparison()
|
||||
{
|
||||
return OperatingSystem.IsWindows()
|
||||
? StringComparison.OrdinalIgnoreCase
|
||||
: StringComparison.Ordinal;
|
||||
}
|
||||
|
||||
public static string V2rayN => "v2rayN";
|
||||
|
||||
public static void StartV2RayN()
|
||||
|
||||
Reference in New Issue
Block a user