Created
November 23, 2023 10:19
-
-
Save felmoltor/a6e57000fc7bee8d3b0350abee105e33 to your computer and use it in GitHub Desktop.
Search writable folders and network shares without "accesschk.exe"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Author: Felipe Molina de la Torre | |
# Date: Novermber 2023 | |
# Summary: Accessckl-like script, but without using external executable files like "accesschk.exe". | |
# It shows you the folders and executables where your user have write permissions and why. | |
# This is useful for systems where AppLocker is in place and you cannot execute arbitrary exes but you can execute PowerShell. | |
# Class to store permissions | |
class Permissions { | |
[string]$GroupName | |
[string]$SID | |
[String[]]$DeniedWrites | |
[String[]]$AllowedWrites | |
} | |
function Scan-Permissions($Path,$UserGroups){ | |
# Get machine domain name | |
$computer_domain=(Get-WmiObject -Namespace root\cimv2 -Class Win32_ComputerSystem | Select Domain).Domain | |
$resultant_denied_permissions=@() | |
$resultant_allowed_permissions=@() | |
# List of "write" operations | |
# ERROR? GenericWrite is reported for group "Builtin\Users" in many files of the system, so we disable it from detection for now | |
$write_operations= @("TakeOwnership","ChangePermissions","FullControl","GenericAll","Modify","Write","WriteAttributes","WriteData","WriteExtendedAttributes","WriteKey") | |
foreach ($cacl in ($(get-acl $Path).Sddl | ConvertFrom-SddlString).DiscretionaryAcl){ | |
# Check the user or group is any of the groups this user is member of | |
$user,$acl=$cacl.split(":") | |
$domain,$username=$user.Split("\") | |
# Resolve the username to SID | |
# If it's a builtin account or local account, resolve locally, else resolve in the domain | |
$strSID="" | |
if ($username -ne "TrustedInstaller" -and $domain.Length -gt 0 -and $username.Length -gt 0){ | |
if ($domain.Trim().ToLower() -eq $computer_domain.Trim().ToLower()){ | |
# Resolve on the domain | |
#write-host "Translating $domain\$username" | |
$objUser = New-Object System.Security.Principal.NTAccount($domain, $username) | |
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]).Value | |
} | |
else { | |
# Resolve locally | |
$strSID=(new-object security.principal.ntaccount $username).translate([security.principal.securityidentifier]).Value | |
} | |
} | |
$allowed_result=@() | |
$denied_result=@() | |
# If this sid is within one of the groups | |
if ($user_groups -contains $strSID) { | |
# Create object to store the permissions details | |
$perms=[Permissions]::new() | |
$perms.SID=$strSID | |
$perms.GroupName=$user | |
# Selecting the allowed and denied permissions and parse them | |
$match_allowed= $acl.Replace("`n","").Replace("`r","").Replace(" ","") | Select-String -Pattern "AccessAllowed\((.*)\)|AccessAllowedInherited\((.*)\)" | |
$match_denied= $acl.Replace("`n","").Replace("`r","").Replace(" ","") | Select-String -Pattern "AccessDenied\((.*)\)|AccessDeniedInherited\((.*)\)" | |
# Check we have any write permissions in this item and it is not previously denied (Deny ACLs have preference over allow ACLs) | |
if ($match_denied.Matches.Length -gt 0){ | |
# Differenciate between the first regex match before the OR "|" and the second | |
$matchIdx=$null | |
if ($match_denied.Matches.groups[1].Success){$matchIdx=1} else { $matchIdx=2 } | |
$denied_permissions=$match_denied.Matches.groups[$matchIdx].value.Split(",") | |
$denied_result=($write_operations | ?{ $denied_permissions -contains $_}) | |
$perms.DeniedWrites=$denied_result | |
if ($perms.DeniedWrites.Length -gt 0){ | |
$resultant_denied_permissions+=$perms | |
} | |
} | |
# Saving this allowed results to the global resultant allowed permissions | |
if ($match_allowed.Matches.Length -gt 0){ | |
$matchIdx=$null | |
if ($match_allowed.Matches.groups[1].Success){$matchIdx=1} else { $matchIdx=2 } | |
$allowed_permissions=$match_allowed.Matches.groups[$matchIdx].value.Split(",") | |
$allowed_result=($write_operations | ?{ $allowed_permissions -contains $_}) | |
$perms.AllowedWrites=$allowed_result | |
if ($perms.AllowedWrites.Length -gt 0){ | |
$resultant_allowed_permissions+=$perms | |
} | |
} | |
} | |
} | |
# Substract the resultant set of allowed minus the denied permissions | |
return $resultant_allowed_permissions, $resultant_denied_permissions # | ? { -not $resultant_denied_permissions -contains $_ } | |
} | |
function Inspect-Child { | |
param( | |
[Parameter(Mandatory=$true)] | |
[string]$Child | |
) | |
$rp_allowed,$rp_denied=Scan-Permissions -Path $Child -UserGroups $user_groups | |
if ($rp_allowed.Length -gt 0){ | |
Write-Host "[+] Writable $($Child): " | |
foreach ($entry in $rp_allowed){ | |
if ($entry.AllowedWrites.Length -gt 0){ | |
Write-Host "* Group allowing: $($entry.GroupName)" | |
Write-Host "* Permissions write-like: $($entry.AllowedWrites)" | |
} | |
} | |
} | |
if ($rp_denied.DeniedWrites.Length -gt 0){ | |
foreach ($entry in $rp_denied){ | |
if ($entry.DeniedWrites.Length -gt 0){ | |
Write-Host "* Explicit denies: " | |
Write-Host "** Group denying: $($entry.GroupName)" | |
Write-Host "** Denied write-like permissions: $($entry.DeniedWrites)" | |
} | |
} | |
} | |
} | |
function Search-Writable{ | |
param( | |
[Parameter(Mandatory=$true,ValueFromPipeline=$true)] | |
[string]$Path, | |
[Parameter(Mandatory=$false)] | |
[switch]$Recurse, | |
[Parameter(Mandatory=$false)] | |
[switch]$UserOrGroup, | |
[Parameter(Mandatory=$false)] | |
[switch]$Files | |
) | |
# Get current user Name and SID | |
$loggedInUser = Get-CimInstance –ClassName Win32_ComputerSystem | Select-Object @{Name = 'Username'; Expression = {$_.Username}}, @{Name = 'Sid'; Expression = {([System.Security.Principal.NTAccount]$_.UserName).Translate([System.Security.Principal.SecurityIdentifier]).Value}} | |
# Get current user WindowsIdentity and list the Groups | |
$token = [System.Security.Principal.WindowsIdentity]::GetCurrent() | |
Write-Host "Current user member of groups: " | |
$token.Groups | ForEach-Object -Process { Write-Host "* $($_):`t$($_.Translate([System.Security.Principal.NTAccount]))" } | |
$user_groups=$token.Groups.Value | |
$user_groups+=$loggedInUser.Sid | |
# Iterate | |
if ($Recurse -eq $true){ | |
$childs = $null | |
if ($Files -eq $true){ | |
Write-Host "Listing files to analyse..." | |
$childs = Get-ChildItem -File -Recurse -Path $Path | |
} | |
else { | |
Write-Host "Listing files and folders to analyse..." | |
$childs = Get-ChildItem -Recurse -Path $Path | |
} | |
# Iterate over the results files and folders | |
Write-Host "Analysis in course..." | |
$childs | ForEach-Object { | |
$count += 1 | |
$p=[math]::Round(($count/($childs.Length))*100) | |
Write-Progress -Activity "Permissios analysis in progress" -Status "$p%" -PercentComplete $p | |
$child=$_ | |
Inspect-Child -Child $child.FullName | |
} | |
} | |
else { | |
Inspect-Child -Child $Path | |
} | |
} | |
## Example ## | |
Write-Host "###### Program Files ######" | |
Search-Writable -Path "C:\Program Files" -Recurse -Files |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment