u-chan-secrets 3.0.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ # Internal SYSTEM fixture. The caller supplies auditProcessId and auditServiceHome.
2
+ $ErrorActionPreference='Stop'
3
+ Import-Module "$PSHOME\Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1"
4
+ $all=@(Get-CimInstance Win32_Process)
5
+ $ids=@($auditProcessId)
6
+ for($i=0;$i -lt $ids.Count;$i++) {
7
+ $parent=$ids[$i]
8
+ $ids+=@($all|Where-Object{$_.ParentProcessId -eq $parent}|ForEach-Object{[int]$_.ProcessId})
9
+ }
10
+ $paths=@();$exited=0;$unreadableLive=@()
11
+ foreach($id in $ids) {
12
+ try {$paths+=@((Get-Process -Id $id -ErrorAction Stop).Modules|ForEach-Object{$_.FileName})}
13
+ catch {if(Get-Process -Id $id -ErrorAction SilentlyContinue){$unreadableLive+=$id}else{$exited++}}
14
+ }
15
+ $paths=@($paths|Sort-Object -Unique)
16
+ $servicePrefix=[IO.Path]::GetFullPath($auditServiceHome).TrimEnd('\')+'\'
17
+ $windowsPrefix=[IO.Path]::GetFullPath($env:windir).TrimEnd('\')+'\'
18
+ $outside=@($paths|Where-Object{-not $_.StartsWith($servicePrefix,[StringComparison]::OrdinalIgnoreCase)-and -not $_.StartsWith($windowsPrefix,[StringComparison]::OrdinalIgnoreCase)})
19
+ $defender=0;$unsafe=@()
20
+ foreach($file in $outside) {
21
+ $item=Get-Item -LiteralPath $file -Force
22
+ $signature=Get-AuthenticodeSignature -LiteralPath $file
23
+ $acl=$item.GetAccessControl()
24
+ $owner=$acl.GetOwner([Security.Principal.SecurityIdentifier]).Value
25
+ $trustedOwner=$owner -in @('S-1-5-18','S-1-5-32-544') -or $owner.StartsWith('S-1-5-80-')
26
+ $bad=@($acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])|Where-Object{
27
+ $_.AccessControlType -eq 'Allow' -and ($_.FileSystemRights -band 0xD0156) -and $_.IdentityReference.Value -notin @('S-1-5-18','S-1-5-32-544') -and -not $_.IdentityReference.Value.StartsWith('S-1-5-80-')
28
+ })
29
+ if($file.StartsWith('C:\ProgramData\Microsoft\Windows Defender\Platform\',[StringComparison]::OrdinalIgnoreCase)-and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)-and $trustedOwner -and $signature.Status -eq 'Valid' -and $signature.SignerCertificate.Subject -match 'O=Microsoft Corporation' -and $bad.Count -eq 0){$defender++}else{$unsafe+=$file}
30
+ }
31
+ @{
32
+ processes=$ids.Count;modules=$paths.Count;outside=$unsafe;verifiedDefenderModules=$defender
33
+ unreadableLive=$unreadableLive.Count;exitedBeforeInspection=$exited
34
+ bitwardenObserved=($paths -contains (Join-Path $auditServiceHome 'bin\bw.exe'))
35
+ browserObserved=(@($paths|Where-Object{$_.StartsWith((Join-Path $servicePrefix 'bin\browser\'),[StringComparison]::OrdinalIgnoreCase)}).Count -gt 0)
36
+ }|ConvertTo-Json -Compress
@@ -0,0 +1,31 @@
1
+ # Internal admin fixture. Inputs are a fixed ProgramData root and a validated run UUID directory.
2
+ $ErrorActionPreference='Stop'
3
+ $admins=New-Object Security.Principal.SecurityIdentifier('S-1-5-32-544')
4
+ $acl=New-Object Security.AccessControl.DirectorySecurity
5
+ $acl.SetOwner($admins)
6
+ $acl.SetAccessRuleProtection($true,$false)
7
+ foreach($sid in @('S-1-5-18','S-1-5-32-544','S-1-5-32-545')) {
8
+ $rights=if($sid -eq 'S-1-5-32-545'){'ReadAndExecute'}else{'FullControl'}
9
+ $id=New-Object Security.Principal.SecurityIdentifier($sid)
10
+ $acl.AddAccessRule((New-Object Security.AccessControl.FileSystemAccessRule($id,$rights,'ContainerInherit,ObjectInherit','None','Allow')))
11
+ }
12
+ function Assert-ProbeChannel([string]$target) {
13
+ $item=Get-Item -LiteralPath $target -Force
14
+ if(-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)){throw 'Public channel is redirected'}
15
+ $current=$item.GetAccessControl()
16
+ if($current.GetOwner([Security.Principal.SecurityIdentifier]).Value -notin @('S-1-5-18','S-1-5-32-544')){throw 'Public channel has untrusted owner'}
17
+ foreach($rule in $current.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])) {
18
+ if($rule.AccessControlType -eq 'Allow' -and ($rule.FileSystemRights -band 0xD0156) -and $rule.IdentityReference.Value -notin @('S-1-5-18','S-1-5-32-544')){throw 'Public channel is writable by an untrusted identity'}
19
+ }
20
+ }
21
+ # Parent delete-child access could otherwise override the protected root's DACL.
22
+ $parent=(Get-Item -LiteralPath ([IO.Path]::GetDirectoryName($probePublicRoot)) -Force).GetAccessControl()
23
+ foreach($rule in $parent.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])) {
24
+ if($rule.AccessControlType -eq 'Allow' -and ($rule.FileSystemRights -band 0x40) -and -not ($rule.PropagationFlags -band [Security.AccessControl.PropagationFlags]::InheritOnly) -and $rule.IdentityReference.Value -notin @('S-1-5-18','S-1-5-32-544') -and -not $rule.IdentityReference.Value.StartsWith('S-1-5-80-')){throw 'Public channel parent permits untrusted deletion'}
25
+ }
26
+ if(-not [IO.Directory]::Exists($probePublicRoot)){[IO.Directory]::CreateDirectory($probePublicRoot,$acl)|Out-Null}
27
+ # Validate after creation as CreateDirectory may return an existing directory after a race.
28
+ Assert-ProbeChannel $probePublicRoot
29
+ if([IO.Directory]::Exists($probeRunDirectory)-or [IO.File]::Exists($probeRunDirectory)){throw 'Run UUID has already been used'}
30
+ [IO.Directory]::CreateDirectory($probeRunDirectory,$acl)|Out-Null
31
+ Assert-ProbeChannel $probeRunDirectory
@@ -0,0 +1,2 @@
1
+ export function accepted(native: unknown, attacker: unknown, runId?: string): boolean
2
+ export function moduleAuditSucceeded(audit: unknown): boolean
@@ -0,0 +1,9 @@
1
+ const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
2
+ export function accepted(native, attacker, runId) {
3
+ if (!uuid.test(runId ?? '') || !native || !attacker) return false
4
+ const expected = ['nonElevated', 'hookPositiveControl', 'capturePositiveControl', 'desktopReadDenied', 'desktopHookDenied', 'desktopSwitchDenied', 'replyPipeConnectDenied', 'replyPipeSpoofDenied']
5
+ return native.ok === true && native.runId === runId && attacker.runId === runId && expected.every(k => attacker[k] === 'True') && attacker.postClickAccepted === 'False' && attacker.replyPipeConnected === 'False' && attacker.printWindowSawMarker === 'False' && attacker.screenCaptureSawMarker === 'False' && attacker.protectedCanaryEvents === '0'
6
+ }
7
+ export function moduleAuditSucceeded(audit) {
8
+ return !!audit && Number.isInteger(audit.modules) && audit.modules > 0 && audit.unreadableLive === 0 && Array.isArray(audit.outside) && audit.outside.length === 0 && audit.bitwardenObserved === true && audit.browserObserved === true
9
+ }