diff --git a/TODO b/TODO index 68c4af1..8f0c968 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,6 @@ Get-GPORegSettings - implement filtering - very noisy -Get-GPORegSettings - fix output of .value, add output for *.extension.policy.dropdownlist.value.name.innertext +Get-GPORegSettings - fix output of .value Get-GPOFilePerms - parse SDDL Get-GPOAccountSettings - implement filtering +Get-GPOFolderRedirection - get permissions on target path + diff --git a/grouper.ps1 b/grouper.ps1 index 0868106..f8557d0 100644 --- a/grouper.ps1 +++ b/grouper.ps1 @@ -72,6 +72,10 @@ $Global:intRights += "SeCreateGlobalPrivilege" $Global:intRights += "SeLoadDriverPrivilege" $Global:intRights += "SeRemoteInteractiveLogonRight" +$Global:boringTrustees = @() +$Global:boringTrustees += "BUILTIN\Administrators" +$Global:boringTrustees += "NT AUTHORITY\SYSTEM" + #____________________ GPO Check functions _______________ # There's a whole pile of these functions. Each one consumes a single object from a Get-GPOReport XML report, @@ -369,21 +373,40 @@ Function Get-GPOMSIInstallation { ###### # Description: Checks for MSI installers being used to install software. # Vulnerable: TODO Only show instances where the file is writable by the current user or 'Everyone' or 'Domain Users' or 'Authenticated Users'. - # Interesting: TODO Also show instances where any user/group other than the usual default Domain/Enterprise Admins has 'Full Control'. + # Interesting: All MSI installations. # Boring: All MSI installations. ###### - $computerMSIInstallation = ($polXml.Computer.ExtensionData.Extension.MsiApplication | Sort-Object GPOSettingOrder) + $MSIInstallation = ($polXml.ExtensionData.Extension.MsiApplication | Sort-Object GPOSettingOrder) + + if ($MSIInstallation) { + foreach ($setting in $MSIInstallation) { + $output = @{} + $MSIPath = $setting.Path + $output.Add("Name", $setting.Name) + $output.Add("Path", $MSIPath) + + if ($Global:onlineChecks -eq 1) { + if ($MSIPath.StartsWith("\\")) { + $ACLData = Find-IntACL -Path $MSIPath + $output.Add("Owner",$ACLData["Owner"]) + if ($ACLData["Vulnerable"] -eq "True") { + $settingisvulnerable = 1 + $output.Add("[!]", "Source file writable by current user!") + } + $MSIPathAccess = $ACLData["Trustees"] + } + } - if ($computerMSIInstallation) { - foreach ($setting in $computerMSIInstallation) { - if ($level -eq 1) { - $output = @{} - $output.Add("Name", $setting.Name) - $output.Add("Path", $setting.Path) + if (($level -le 2) -Or (($level -le 3) -And ($settingisVulnerable -eq 1))) { Write-NoEmpties -output $output - "`r`n" + if ($MSIPathAccess) { + "" + Write-Title -Text "Permissions on source file:" -DividerChar "-" + Write-Output $MSIPathAccess + } } + "`r`n" } } } @@ -398,7 +421,7 @@ Function Get-GPOScripts { ###### # Description: Checks for startup/shutdown/logon/logoff scripts. # Vulnerable: TODO Only show instances where the file is writable by the current user or 'Everyone' or 'Domain Users' or 'Authenticated Users'. - # Interesting: TODO Also show instances where any user/group other than the usual default Domain/Enterprise Admins has 'Full Control' or where 'Parameters' is set. + # Interesting: All scripts. # Boring: All scripts. ###### @@ -406,14 +429,35 @@ Function Get-GPOScripts { if ($settingsScripts) { foreach ($setting in $settingsScripts) { - if ($level -eq 1) { - $output = @{} - $output.Add("Command", $setting.Command) - $output.Add("Type", $setting.Type) - $output.Add("Parameters", $setting.Parameters) - Write-NoEmpties -output $output - "`r`n" + $commandPath = $setting.Command + $output = @{} + $output.Add("Command", $commandPath) + $output.Add("Type", $setting.Type) + $output.Add("Parameters", $setting.Parameters) + $settingIsVulnerable = 0 + + if ($Global:onlineChecks -eq 1) { + if ($commandPath.StartsWith("\\")) { + $ACLData = Find-IntACL -Path $commandPath + $output.Add("Owner",$ACLData["Owner"]) + if ($ACLData["Vulnerable"] -eq "True") { + $settingisvulnerable = 1 + $output.Add("[!]", "Source file writable by current user!") + } + $commandPathAccess = $ACLData["Trustees"] + } } + + if (($level -le 2) -Or (($level -le 3) -And ($settingisVulnerable -eq 1))) { + Write-NoEmpties -output $output + if ($commandPathAccess) { + "" + Write-Title -Text "Permissions on source file:" -DividerChar "-" + Write-Output $commandPathAccess + } + } + "`r`n" + } } } @@ -426,9 +470,9 @@ Function Get-GPOFileUpdate { ) ###### - # Description: Checks for MSI installers being used to install software. + # Description: Checks for files being copied/updated/whatever. # Vulnerable: TODO Only show instances where the 'fromPath' file is writable by the current user or 'Everyone' or 'Domain Users' or 'Authenticated Users'. - # Interesting: TODO Also show instances where any user/group other than the usual default Domain/Enterprise Admins has 'Full Control' of the 'fromPath' file. + # Interesting: All File Updates where FromPath is a network share # Boring: All File Updates. ###### @@ -436,15 +480,36 @@ Function Get-GPOFileUpdate { if ($settingsFiles) { foreach ($setting in $settingsFiles.File) { - if ($level -eq 1) { - $output = @{} - $output.Add("Name", $setting.name) - $output.Add("Action", $setting.Properties.action) - $output.Add("fromPath", $setting.Properties.fromPath) - $output.Add("targetPath", $setting.Properties.targetPath) + $fromPath = $setting.Properties.fromPath + $targetPath = $setting.Properties.targetPath + $output = @{} + $output.Add("Name", $setting.name) + $output.Add("Action", $setting.Properties.action) + $output.Add("fromPath", $fromPath) + $output.Add("targetPath", $targetPath) + $settingIsVulnerable = 0 + + if ($Global:onlineChecks -eq 1) { + if ($fromPath.StartsWith("\\")) { + $ACLData = Find-IntACL -Path $fromPath + $output.Add("Owner",$ACLData["Owner"]) + if ($ACLData["Vulnerable"] -eq "True") { + $settingisvulnerable = 1 + $output.Add("[!]", "Source file writable by current user!") + } + $fromPathAccess = $ACLData["Trustees"] + } + } + + if (($level -le 2) -Or (($level -le 3) -And ($settingisVulnerable -eq 1))) { Write-NoEmpties -output $output - "`r`n" + if ($fromPathAccess) { + "" + Write-Title -Text "Permissions on source file:" -DividerChar "-" + Write-Output $fromPathAccess + } } + "`r`n" } } } @@ -1017,21 +1082,39 @@ Function Get-GPOShortcuts { if ($settingsShortcuts) { # Iterate over array of settings, writing out only those we care about. foreach ($setting in $settingsShortcuts) { - if ($level -eq 1) { - $output = @{} - $output.Add("Name", $setting.name) - $output.Add("Status", $setting.status) - $output.Add("targetType", $setting.properties.targetType) - $output.Add("Action", $setting.properties.Action) - $output.Add("comment", $setting.properties.comment) - $output.Add("startIn", $setting.properties.startIn) - $output.Add("arguments", $setting.properties.arguments) - $output.Add("targetPath", $setting.properties.targetPath) - $output.Add("iconPath", $setting.properties.iconPath) - $output.Add("shortcutPath", $setting.properties.shortcutPath) + $targetPath = $setting.properties.targetPath + $output = @{} + $output.Add("Name", $setting.name) + $output.Add("Status", $setting.status) + $output.Add("targetType", $setting.properties.targetType) + $output.Add("Action", $setting.properties.Action) + $output.Add("comment", $setting.properties.comment) + $output.Add("startIn", $setting.properties.startIn) + $output.Add("arguments", $setting.properties.arguments) + $output.Add("targetPath", $setting.properties.targetPath) + $output.Add("iconPath", $setting.properties.iconPath) + $output.Add("shortcutPath", $setting.properties.shortcutPath) + if ($Global:onlineChecks -eq 1) { + if ($targetPath.StartsWith("\\")) { + $ACLData = Find-IntACL -Path $targetPath + $output.Add("Owner",$ACLData["Owner"]) + if ($ACLData["Vulnerable"] -eq "True") { + $settingisvulnerable = 1 + $output.Add("[!]", "Source file writable by current user!") + } + $targetPathAccess = $ACLData["Trustees"] + } + } + + if (($level -le 2) -Or (($level -le 3) -And ($settingisVulnerable -eq 1))) { Write-NoEmpties -output $output - "`r`n" + if ($targetPathAccess) { + "" + Write-Title -Text "Permissions on source file:" -DividerChar "-" + Write-Output $targetPathAccess + } } + "`r`n" } } } @@ -1134,6 +1217,30 @@ Function Write-Banner { } } +Function Find-IntACL { + Param ( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$Path + ) + $ACLData = @{} + try { + $targetPathACL = Get-ACL $Path -ErrorAction Stop + $targetPathOwner = $targetPathACL.Owner + $targetPathAccess = $targetPathACL.Access | Where-Object {-Not ($Global:boringTrustees -Contains $_.IdentityReference)} | select FileSystemRights,AccessControlType,IdentityReference + $ACLData.Add("Owner", $targetPathOwner) + $ACLData.Add("Trustees", $targetPathAccess) + Try { + [io.file]::OpenWrite($targetPath).close() + $ACLData.Add("Vulnerable","True") + } + Catch { + $ACLData.Add("Vulnerable","False") + } + } + catch [System.Exception] { + $ACLData.Add("Vulnerable","Error") + } + return $ACLData +} #_____________________________________________________________________ Function Invoke-AuditGPO { @@ -1176,7 +1283,8 @@ Function Invoke-AuditGPO { $polchecks += {Get-GPOScripts -Level $level -polXML $computerSettings} $polchecks += {Get-GPOFileUpdate -Level $level -polXML $userSettings} $polchecks += {Get-GPOFileUpdate -Level $level -polXML $computerSettings} - $polchecks += {Get-GPOMSIInstallation -Level $level -polXML $xmlgpo} + $polchecks += {Get-GPOMSIInstallation -Level $level -polXML $userSettings} + $polchecks += {Get-GPOMSIInstallation -Level $level -polXML $computerSettings} $polchecks += {Get-GPOUserRights -Level $level -polXML $xmlgpo} $polchecks += {Get-GPOSchedTasks -Level $level -polXML $xmlgpo} $polchecks += {Get-GPOFolderRedirection -Level $level -polXML $xmlgpo} @@ -1332,7 +1440,9 @@ Function Invoke-AuditGPOReport { ParameterSetName='WithoutFile', Mandatory=$false )] [ValidateSet(1,2,3)] - [int]$level = 2 + [int]$level = 2, + + [switch]$online ) Write-Banner @@ -1353,6 +1463,20 @@ Function Invoke-AuditGPOReport { $Global:showDisabled = 1 } + # quick and dirty check to make sure that if the user said to do 'online' checks that we can actually reach the domain. + $Global:onlineChecks = 0 + if ($online) { + try { + net accounts /domain 1> $null + $Global:onlineChecks = 1 + } + catch { + Write-Output "Couldn't talk to the domain, falling back to offline mode." + $Global:onlineChecks =0 + } + + } + if ($lazyMode) { $requiredModules = @('GroupPolicy') $requiredModules | Import-Module -Verbose:$false -ErrorAction SilentlyContinue @@ -1394,4 +1518,3 @@ Function Invoke-AuditGPOReport { $stats += ('Total GPOs: {0}' -f $gpocount) Write-Output $stats } -