Skip to content

Instantly share code, notes, and snippets.

@felmoltor
Created November 23, 2023 10:19
Show Gist options
  • Save felmoltor/a6e57000fc7bee8d3b0350abee105e33 to your computer and use it in GitHub Desktop.
Save felmoltor/a6e57000fc7bee8d3b0350abee105e33 to your computer and use it in GitHub Desktop.
Search writable folders and network shares without "accesschk.exe"
# 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