fix(Prepare-KHDBStorage): reject manifest shard names outside shard root
In -UploadOnly mode, entry.name from the parsed manifest.json was used directly in Join-Path (local read) and as the remote object key with no path-traversal check. A tampered local manifest.json (requires prior local write access to the shard directory) with a name like '..\..\secrets.txt' could make the upload step read and upload an arbitrary local file under an attacker-chosen remote key. Now resolves each shard's full path via GetFullPath and rejects any entry that resolves outside the shard root, before it's added to the list actually used for reads/uploads. Verified the resolved-path containment check against normal relative names (allowed) and '..' traversal attempts (blocked). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -892,12 +892,21 @@ if ($UploadOnly) {
|
|||||||
|
|
||||||
$manifestHash = (Get-FileHash -Path $manifestPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
$manifestHash = (Get-FileHash -Path $manifestPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||||
|
|
||||||
|
$resolvedLocalShardRoot = [System.IO.Path]::GetFullPath($localShardRoot).TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) + [System.IO.Path]::DirectorySeparatorChar
|
||||||
|
|
||||||
$manifestShards = @()
|
$manifestShards = @()
|
||||||
$totalSizeBytes = 0L
|
$totalSizeBytes = 0L
|
||||||
foreach ($entry in ($manifestObject.shards | Sort-Object name)) {
|
foreach ($entry in ($manifestObject.shards | Sort-Object name)) {
|
||||||
$name = [string]$entry.name
|
$name = [string]$entry.name
|
||||||
if ([string]::IsNullOrWhiteSpace($name)) { continue }
|
if ([string]::IsNullOrWhiteSpace($name)) { continue }
|
||||||
$localPath = Join-Path -Path $localShardRoot -ChildPath $name
|
$localPath = Join-Path -Path $localShardRoot -ChildPath $name
|
||||||
|
# Manifest shard names are attacker-controllable if manifest.json was tampered with (requires
|
||||||
|
# prior local write access to the shard directory). Reject anything that resolves outside the
|
||||||
|
# shard root instead of trusting Join-Path to keep '..'/rooted paths contained.
|
||||||
|
$resolvedLocalPath = [System.IO.Path]::GetFullPath($localPath)
|
||||||
|
if (-not $resolvedLocalPath.StartsWith($resolvedLocalShardRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||||
|
throw "Manifest shard name '$name' resolves outside the shard directory '$localShardRoot'."
|
||||||
|
}
|
||||||
if (-not (Test-Path -LiteralPath $localPath)) {
|
if (-not (Test-Path -LiteralPath $localPath)) {
|
||||||
throw "Shard file '$name' listed in manifest was not found under '$localShardRoot'."
|
throw "Shard file '$name' listed in manifest was not found under '$localShardRoot'."
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user