M2: Absolute Path Bypass in File Upload/Download API
Severity: MEDIUM
Category: Path Traversal / Unauthorized File Access
File: src/Spe/sitecore modules/PowerShell/Services/RemoteScriptCall.ashx.cs
Lines: 637–692 (GetPathFromParameters), 671/681 (Path.IsPathRooted fallback)
Risk Explanation
The GetPathFromParameters method resolves file paths for upload/download operations. It supports named origin aliases (data, log, temp, etc.) that map to predefined server directories. However, the default case at line 671 has a fallback:
default:
if (Path.IsPathRooted(pathParam))
{
folder = pathParam; // attacker-controlled absolute path used directly
}
break;
The existing path traversal check at line 696 only looks for .. in pathParam:
if (pathParam.Contains(".."))
{
// block traversal
}
This check is irrelevant when the attacker supplies a fully-qualified absolute path — no .. is needed to access arbitrary files. For example:
origin=custom&path=C:\Windows\System32\config\SAM — read the SAM database
origin=custom&path=C:\inetpub\wwwroot\web.config — read database connection strings
origin=custom&path=C:\inetpub\wwwroot\App_Config\ConnectionStrings.config — read secrets
The attacker needs valid authentication to the remoting API and authorization for the fileDownload or fileUpload service.
Impact: An authenticated remoting user can read or write any file accessible to the IIS application pool identity.
Implementation Plan
Fix
-
Remove the absolute-path fallback entirely:
default:
PowerShellLog.Warn($"[Remoting] action=fileAccessDenied reason=unknownOrigin origin={originParam}");
context.Response.StatusCode = 400;
context.Response.StatusDescription = "Unknown file origin";
return null;
-
If absolute paths are a legitimate use case, add an allowlist of permitted root directories in Spe.config:
<setting name="Spe.AllowedFileRoots" value="C:\Sitecore\Data|C:\Sitecore\Logs" />
Then validate:
default:
var resolvedPath = Path.GetFullPath(pathParam);
if (!allowedRoots.Any(root => resolvedPath.StartsWith(root, StringComparison.OrdinalIgnoreCase)))
{
// reject
}
folder = resolvedPath;
break;
-
Canonicalize all paths using Path.GetFullPath() before the .. check to also catch encoded traversal variants:
var canonicalPath = Path.GetFullPath(Path.Combine(folder, fileName));
if (!canonicalPath.StartsWith(Path.GetFullPath(folder), StringComparison.OrdinalIgnoreCase))
{
// reject — path escapes the intended folder
}
Files to modify
| File |
Change |
src/Spe/sitecore modules/PowerShell/Services/RemoteScriptCall.ashx.cs |
Remove or restrict absolute-path fallback; canonicalize paths |
src/Spe/App_Config/Include/Spe/Spe.config |
(Optional) Add Spe.AllowedFileRoots setting |
Test Plan
-
Integration test — absolute path file read:
$uri = "https://spe.dev.local/-/script/file/download?origin=custom&path=C:\Windows\win.ini"
$response = Invoke-WebRequest -Uri $uri -Headers @{ Authorization = "Bearer $jwt" }
# Before fix: returns contents of win.ini
# After fix: returns 400 or 403
-
Integration test — known alias still works:
$uri = "https://spe.dev.local/-/script/file/download?origin=data&path=logs\log.txt"
# Should work both before and after fix
-
Negative test — various bypass attempts:
origin=unknown&path=C:\Windows\System32\drivers\etc\hosts → rejected
origin=&path=C:\inetpub\wwwroot\web.config → rejected
origin=data&path=..\..\Windows\win.ini → rejected (existing .. check)
origin=data&path=....\Windows\win.ini → rejected (canonicalization)
-
Positive test — all named aliases work.
-
Existing file upload/download integration tests pass.
M2: Absolute Path Bypass in File Upload/Download API
Severity: MEDIUM
Category: Path Traversal / Unauthorized File Access
File:
src/Spe/sitecore modules/PowerShell/Services/RemoteScriptCall.ashx.csLines: 637–692 (GetPathFromParameters), 671/681 (Path.IsPathRooted fallback)
Risk Explanation
The
GetPathFromParametersmethod resolves file paths for upload/download operations. It supports named origin aliases (data,log,temp, etc.) that map to predefined server directories. However, thedefaultcase at line 671 has a fallback:The existing path traversal check at line 696 only looks for
..inpathParam:This check is irrelevant when the attacker supplies a fully-qualified absolute path — no
..is needed to access arbitrary files. For example:origin=custom&path=C:\Windows\System32\config\SAM— read the SAM databaseorigin=custom&path=C:\inetpub\wwwroot\web.config— read database connection stringsorigin=custom&path=C:\inetpub\wwwroot\App_Config\ConnectionStrings.config— read secretsThe attacker needs valid authentication to the remoting API and authorization for the
fileDownloadorfileUploadservice.Impact: An authenticated remoting user can read or write any file accessible to the IIS application pool identity.
Implementation Plan
Fix
Remove the absolute-path fallback entirely:
If absolute paths are a legitimate use case, add an allowlist of permitted root directories in
Spe.config:Then validate:
Canonicalize all paths using
Path.GetFullPath()before the..check to also catch encoded traversal variants:Files to modify
src/Spe/sitecore modules/PowerShell/Services/RemoteScriptCall.ashx.cssrc/Spe/App_Config/Include/Spe/Spe.configSpe.AllowedFileRootssettingTest Plan
Integration test — absolute path file read:
Integration test — known alias still works:
Negative test — various bypass attempts:
origin=unknown&path=C:\Windows\System32\drivers\etc\hosts→ rejectedorigin=&path=C:\inetpub\wwwroot\web.config→ rejectedorigin=data&path=..\..\Windows\win.ini→ rejected (existing..check)origin=data&path=....\Windows\win.ini→ rejected (canonicalization)Positive test — all named aliases work.
Existing file upload/download integration tests pass.