mirror of
https://github.com/k1tbyte/Wand-Enhancer.git
synced 2026-08-28 23:01:13 +00:00
fix(asar): prevent path traversal (zip-slip) during extraction
AsarExtractor's path-traversal guard called Extensions.GetRelativePath, whose fast path strips the destination prefix literally without resolving ".." segments. A crafted archive entry such as "a/../../evil" produced a relative path that did not start with "..", so the guard passed and the file was written outside the extraction directory once the OS resolved the "..". The out-of-package symlink guard shared the same weakness. Add Extensions.IsPathInside, which normalises both paths with Path.GetFullPath before the containment check, and use it for both the file/directory and symlink guards. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,9 +41,12 @@ namespace AsarSharp
|
|||||||
var destFilename = Path.Combine(dest, filename);
|
var destFilename = Path.Combine(dest, filename);
|
||||||
var file = filesystem.GetFile(filename, followLinks);
|
var file = filesystem.GetFile(filename, followLinks);
|
||||||
|
|
||||||
// Path-traversal guard.
|
// Path-traversal (zip-slip) guard. Uses the normalising
|
||||||
string relativePath = Extensions.GetRelativePath(dest, destFilename);
|
// containment check: GetRelativePath's fast path strips the
|
||||||
if (relativePath.StartsWith(".."))
|
// prefix literally without resolving "..", so a crafted entry
|
||||||
|
// such as "a/../../evil" would otherwise pass this check and be
|
||||||
|
// written outside "dest".
|
||||||
|
if (!Extensions.IsPathInside(dest, destFilename))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"{fullPath}: file \"{destFilename}\" writes out of the package");
|
$"{fullPath}: file \"{destFilename}\" writes out of the package");
|
||||||
@@ -164,7 +167,7 @@ namespace AsarSharp
|
|||||||
|
|
||||||
var linkTo = Path.Combine(relativeLinkPath, Path.GetFileName(file.Link));
|
var linkTo = Path.Combine(relativeLinkPath, Path.GetFileName(file.Link));
|
||||||
|
|
||||||
if (Extensions.GetRelativePath(dest, linkSrcPath).StartsWith(".."))
|
if (!Extensions.IsPathInside(dest, linkSrcPath))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"{fullPath}: file \"{file.Link}\" links out of the package to \"{linkSrcPath}\"");
|
$"{fullPath}: file \"{file.Link}\" links out of the package to \"{linkSrcPath}\"");
|
||||||
|
|||||||
@@ -97,6 +97,27 @@ namespace AsarSharp.Utils
|
|||||||
|
|
||||||
private static bool IsSeparator(char c) => c == '/' || c == '\\';
|
private static bool IsSeparator(char c) => c == '/' || c == '\\';
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Security check for archive extraction: returns true only when
|
||||||
|
/// <paramref name="candidate"/> resolves to a location inside
|
||||||
|
/// <paramref name="root"/>. Both paths are fully normalised first, so
|
||||||
|
/// embedded ".." segments cannot escape the root (zip-slip). The
|
||||||
|
/// <see cref="GetRelativePath"/> fast path must not be used here because
|
||||||
|
/// it strips the prefix literally without resolving "..".
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsPathInside(string root, string candidate)
|
||||||
|
{
|
||||||
|
string fullRoot = TrimTrailingSeparators(Path.GetFullPath(root));
|
||||||
|
string fullCandidate = TrimTrailingSeparators(Path.GetFullPath(candidate));
|
||||||
|
|
||||||
|
if (string.Equals(fullRoot, fullCandidate, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return fullCandidate.Length > fullRoot.Length
|
||||||
|
&& fullCandidate.StartsWith(fullRoot, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& IsSeparator(fullCandidate[fullRoot.Length]);
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetDirectoryName(string path)
|
public static string GetDirectoryName(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
|
|||||||
Reference in New Issue
Block a user