Private/Rules/Find-IslContextIssue.ps1
|
function Find-IslContextIssue { <# .SYNOPSIS Flags per-user locations in SYSTEM scripts and privileged writes in user scripts. .DESCRIPTION As SYSTEM, HKCU: is the SYSTEM account's own hive, USERPROFILE is C:\WINDOWS\system32\config\systemprofile, APPDATA sits under it, TEMP is C:\WINDOWS\TEMP, there is no console session and no mapped drives. A script written and tested in the admin's own session works there and silently does the wrong thing under Intune. Running as the signed-in user has the opposite problem: standard users can't write HKLM or Program Files. .PARAMETER Context The IntuneScriptLab.ScriptContext from Get-IslScriptContext: AST, tokens, bytes and the effective ScriptType, Context and Architecture. .EXAMPLE Find-IslContextIssue -Context (Get-IslScriptContext -Path .\Detect.ps1) The findings this rule produces for one script, as IntuneScriptLab.Finding objects. #> [CmdletBinding()] param( [Parameter(Mandatory)] [pscustomobject]$Context ) $rule = 'IslContextIssue' $ast = $Context.Ast $literals = @(Get-IslStringLiteral -Ast $ast) $variables = @(Find-IslAstNode -Ast $ast -TypeName VariableExpressionAst) if ($Context.Context -eq 'System') { $evidence = ('SYSTEM context: User=NT AUTHORITY\SYSTEM, session 0, ' + ('USERPROFILE=C:\WINDOWS\system32\config\systemprofile, APPDATA under it, TEMP=C:\WINDOWS\TEMP ' + '(REM-PROBE-SYS64, PS-PROBE-SYS64)')) $hkcuPattern = '(?i)^(HKCU:|Registry::HKEY_CURRENT_USER|HKEY_CURRENT_USER\\)' foreach ($literal in ($literals | Where-Object { $_.Value -match $hkcuPattern })) { $findingSplat = @{ RuleName = $rule Severity = 'Error' Context = $Context Extent = $literal.Extent Message = ('HKCU: under SYSTEM is the SYSTEM account''s hive, not the signed-in ' + 'user''s. Load the user''s hive via HKU\<SID> or run the script in user context') Evidence = $evidence } New-IslFinding @findingSplat } foreach ($variable in ($variables | Where-Object { $_.VariablePath.UserPath -match ('(?i)^env:(USERPROFILE|APPDATA|' + 'LOCALAPPDATA|USERNAME|HOMEPATH|' + 'HOMEDRIVE|OneDrive|' + 'OneDriveCommercial)$|^HOME$') })) { $name = $variable.VariablePath.UserPath $findingSplat = @{ RuleName = $rule Severity = 'Error' Context = $Context Extent = $variable.Extent Message = ("`$$name resolves to the SYSTEM profile (systemprofile), not the signed-in user. " + "Look the user up (e.g. via explorer.exe's owner or HKU) or run in user context") Evidence = $evidence } New-IslFinding @findingSplat } foreach ($variable in ($variables | Where-Object { $_.VariablePath.UserPath -match '(?i)^env:(TEMP|TMP)$' })) { $findingSplat = @{ RuleName = $rule Severity = 'Information' Context = $Context Extent = $variable.Extent Message = ("`$$($variable.VariablePath.UserPath) is C:\WINDOWS\TEMP under SYSTEM, which is " + 'fine if that is what you expect') Evidence = $evidence } New-IslFinding @findingSplat } $userFolders = Find-IslAstNode -Ast $ast -TypeName InvokeMemberExpressionAst -Where { param($node) $node.Extent.Text -match ('(?i)GetFolderPath\(\s*[''"]?(ApplicationData|' + ('LocalApplicationData|UserProfile|MyDocuments|Personal|Desktop|DesktopDirectory|Favorites|' + 'StartMenu|Startup|MyPictures|MyMusic|MyVideos)')) } foreach ($node in $userFolders) { $findingSplat = @{ RuleName = $rule Severity = 'Error' Context = $Context Extent = $node.Extent Message = ('GetFolderPath for a per-user folder returns the SYSTEM profile''s folder ' + 'under SYSTEM') Evidence = $evidence } New-IslFinding @findingSplat } foreach ($literal in ($literals | Where-Object { $_.Value -match '^[D-Zd-z]:\\' })) { $findingSplat = @{ RuleName = $rule Severity = 'Warning' Context = $Context Extent = $literal.Extent Message = ("Drive $($literal.Value.Substring(0, 2)) is not mapped for SYSTEM; mapped drives " + 'belong to the user session. Use a UNC path and make sure the computer account ' + 'can reach it') Evidence = $evidence } New-IslFinding @findingSplat } } if ($Context.Context -eq 'User') { $evidence = ('User context ran as AzureAD\<user> in the console session with that user''s profile and ' + 'rights (REM-PROBE-USER64, PS-PROBE-USER)') $writers = 'Set-ItemProperty', 'New-ItemProperty', 'New-Item', 'Remove-Item', 'Remove-ItemProperty', 'Rename-Item', 'Copy-Item', 'Move-Item', 'Set-Content', 'Add-Content', 'Out-File' foreach ($command in (Find-IslCommand -Ast $ast -Name $writers)) { if ($command.Extent.Text -match ('(?i)HKLM:|HKEY_LOCAL_MACHINE|\\Program ' + 'Files|\\Windows\\|\$env:(ProgramFiles|windir|SystemRoot|ProgramData)')) { $findingSplat = @{ RuleName = $rule Severity = 'Warning' Context = $Context Extent = $command.Extent Message = ("$($command.GetCommandName()) to a machine-wide location runs as the " + 'signed-in user, who is usually not an administrator; it will fail with access denied') Evidence = $evidence } New-IslFinding @findingSplat } } $privileged = 'Start-Service', 'Stop-Service', 'Restart-Service', 'Set-Service', 'New-Service', 'Install-WindowsFeature', 'Enable-WindowsOptionalFeature', 'Add-AppxProvisionedPackage', 'Set-MpPreference', 'Add-MpPreference' foreach ($command in (Find-IslCommand -Ast $ast -Name $privileged)) { $findingSplat = @{ RuleName = $rule Severity = 'Warning' Context = $Context Extent = $command.Extent Message = ("$($command.GetCommandName()) needs administrator rights; in user context the " + 'script runs as the signed-in user') Evidence = $evidence } New-IslFinding @findingSplat } # Not something the script can fix, but the reason a user-context script never runs on part # of a fleet; Win32 requirement scripts are the one kind that does run there if ($Context.ScriptType -in 'Detection', 'Remediation', 'PlatformScript') { $findingSplat = @{ RuleName = $rule Severity = 'Information' Context = $Context Message = ('User context runs only on Entra joined or hybrid-joined devices: on an ' + 'Entra-registered device the agent downloads the policy and skips it. Deploy as SYSTEM ' + 'if registered devices must be covered') Evidence = ('IntuneManagementExtension.log on a registered device: "This is not ' + 'AADJ/HAADJ device, skip user context"; the same scripts ran as AzureAD\<user> on a ' + 'joined device (join-type experiments, REM-PROBE-USER64, PS-PROBE-USER)') } New-IslFinding @findingSplat } } } |