mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
99 lines
5.9 KiB
PowerShell
99 lines
5.9 KiB
PowerShell
function Get-OpenSshAclDisposition {
|
|
param([Security.AccessControl.RawSecurityDescriptor]$Descriptor, [bool]$Directory)
|
|
|
|
$trustedOwners = @('S-1-5-18', 'S-1-5-32-544',
|
|
'S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464')
|
|
if ($null -eq $Descriptor -or $null -eq $Descriptor.Owner) { return 'unknown' }
|
|
if ($Descriptor.Owner.Value -notin $trustedOwners) { return 'unsafe' }
|
|
if (-not ($Descriptor.ControlFlags -band [Security.AccessControl.ControlFlags]::DiscretionaryAclPresent) -or
|
|
$null -eq $Descriptor.DiscretionaryAcl) { return 'unsafe' }
|
|
$write = [long][Security.AccessControl.FileSystemRights]::Write -bor
|
|
[long][Security.AccessControl.FileSystemRights]::Delete -bor
|
|
[long][Security.AccessControl.FileSystemRights]::ChangePermissions -bor
|
|
[long][Security.AccessControl.FileSystemRights]::TakeOwnership
|
|
if ($Directory) { $write = $write -bor [long][Security.AccessControl.FileSystemRights]::DeleteSubdirectoriesAndFiles }
|
|
$known = 0xf0000000L -bor [long][Security.AccessControl.FileSystemRights]::FullControl
|
|
foreach ($ace in $Descriptor.DiscretionaryAcl) {
|
|
# Inherit-only rights apply to future children, not this object. Inspect raw ACEs:
|
|
# GetAccessRules omits unsupported ACEs and leaves signed generic masks unmapped.
|
|
if ($ace.AceFlags -band [Security.AccessControl.AceFlags]::InheritOnly) { continue }
|
|
if ($ace -isnot [Security.AccessControl.CommonAce] -or $ace.IsCallback -or
|
|
$ace.AceQualifier -notin @([Security.AccessControl.AceQualifier]::AccessAllowed,
|
|
[Security.AccessControl.AceQualifier]::AccessDenied) -or
|
|
([int]$ace.AceFlags -band 0xe0)) { return 'unknown' }
|
|
$mask = [long]$ace.AccessMask -band 0xffffffffL
|
|
if ($mask -band (-bnot $known)) { return 'unknown' }
|
|
# Denies cannot cancel an unsafe allow here; this screens the installation,
|
|
# not a particular token's effective access. GENERIC_READ/EXECUTE are read-only.
|
|
if ($ace.AceQualifier -eq [Security.AccessControl.AceQualifier]::AccessAllowed -and
|
|
$ace.SecurityIdentifier.Value -notin $trustedOwners -and
|
|
($mask -band (0x50000000L -bor $write))) { return 'unsafe' }
|
|
}
|
|
return 'safe'
|
|
}
|
|
|
|
function Get-WindowsTestboxOpenSshInstallation {
|
|
$ErrorActionPreference = 'Stop'
|
|
Set-StrictMode -Version Latest
|
|
try {
|
|
$services = @(Get-CimInstance Win32_Service -Filter "Name='sshd'")
|
|
if ($services.Count -ne 1) { throw 'service' }
|
|
$service = $services[0]
|
|
if ($service.State -ne 'Running' -or $service.StartName -ne 'LocalSystem' -or
|
|
$service.ProcessId -le 0) { throw 'service' }
|
|
$directories = @("$env:WINDIR\System32\OpenSSH", "$env:ProgramFiles\OpenSSH",
|
|
"$env:ProgramFiles\OpenSSH-Win64")
|
|
$selected = @($directories | Where-Object {
|
|
$candidate = "$_\sshd.exe"
|
|
$service.PathName.Trim() -ieq "`"$candidate`"" -or
|
|
($candidate -notmatch '\s' -and $service.PathName.Trim() -ieq $candidate)
|
|
})
|
|
if ($selected.Count -ne 1 -or $selected[0] -notmatch '^[A-Za-z]:\\') { throw 'path' }
|
|
$directory = $selected[0]
|
|
$sshd = "$directory\sshd.exe"
|
|
$keygen = "$directory\ssh-keygen.exe"
|
|
$process = Get-CimInstance Win32_Process -Filter "ProcessId=$($service.ProcessId)"
|
|
if (-not $process -or $process.ExecutablePath -ine $sshd -or -not $process.CreationDate) { throw 'process' }
|
|
$owner = Invoke-CimMethod -InputObject $process -MethodName GetOwnerSid
|
|
if ($owner.ReturnValue -ne 0 -or $owner.Sid -ne 'S-1-5-18') { throw 'owner' }
|
|
|
|
# Check each component before following it. OS ancestors and provider DLLs
|
|
# remain image-owned; only the selected directory and executed pair get ACL checks.
|
|
$component = [IO.Path]::GetPathRoot($directory)
|
|
$paths = @($component)
|
|
foreach ($part in $directory.Substring($component.Length).Split('\')) {
|
|
$component = Join-Path $component $part
|
|
$paths += $component
|
|
}
|
|
foreach ($entry in @($paths) + @($sshd, $keygen)) {
|
|
$item = Get-Item -LiteralPath $entry -Force
|
|
if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
|
|
$item.PSIsContainer -ne ($entry -in $paths)) { throw 'filesystem' }
|
|
}
|
|
foreach ($entry in @($directory, $sshd, $keygen)) {
|
|
$acl = Get-Acl -LiteralPath $entry
|
|
$descriptor = [Security.AccessControl.RawSecurityDescriptor]::new($acl.GetSecurityDescriptorBinaryForm(), 0)
|
|
if ((Get-OpenSshAclDisposition $descriptor ($entry -eq $directory)) -ne 'safe') { throw 'acl' }
|
|
}
|
|
foreach ($entry in @($sshd, $keygen)) {
|
|
$signature = Get-AuthenticodeSignature -LiteralPath $entry
|
|
if ($signature.Status -ne 'Valid' -or $null -eq $signature.SignerCertificate -or
|
|
$signature.SignerCertificate.GetNameInfo([Security.Cryptography.X509Certificates.X509NameType]::SimpleName, $false) -cnotin
|
|
@('Microsoft Corporation', 'Microsoft Windows')) { throw 'signature' }
|
|
}
|
|
|
|
# Admission belongs to the same running service/process observed before the
|
|
# filesystem checks; a restart or configuration change requires a fresh attempt.
|
|
$currentServices = @(Get-CimInstance Win32_Service -Filter "Name='sshd'")
|
|
if ($currentServices.Count -ne 1) { throw 'service changed' }
|
|
$current = $currentServices[0]
|
|
if ($current.ProcessId -ne $service.ProcessId -or $current.State -ne $service.State -or
|
|
$current.StartName -ne $service.StartName -or $current.PathName -cne $service.PathName) { throw 'service changed' }
|
|
$currentProcess = Get-CimInstance Win32_Process -Filter "ProcessId=$($current.ProcessId)"
|
|
if (-not $currentProcess -or $currentProcess.ExecutablePath -ine $sshd -or
|
|
-not $currentProcess.CreationDate -or $currentProcess.CreationDate -ne $process.CreationDate) { throw 'process changed' }
|
|
return [pscustomobject]@{ Sshd = $sshd; Keygen = $keygen }
|
|
} catch {
|
|
throw 'Cannot admit the native Windows OpenSSH installation; verify the provider service, Microsoft binaries, and installation permissions.'
|
|
}
|
|
}
|