Most organizations focus on vulnerabilities, missing patches, exposed services and risky configurations. That is good, it can in all cases always be even better.

However, one of the the more practical privilege escalation risks is often overlooked because they are hard to spot:

A (Windows) service runs from a folder where normal users have write permissions.

That creates a real risk.

If a service executable or related service files are located in a folder that can be modified by broad principals such as Authenticated Users, Users, or Domain Users, a local user or attacker may be able to tamper with that service path.

If that service runs with elevated privileges, such as LocalSystem or a privileged service account, this can become a local privilege escalation or persistence path.

This is where the Microsoft Defender recommendation becomes useful:

Change service executable path to a common protected location

The idea is simple. Service binaries should preferably live in common protected locations such as C:\Program Files or C:\Windows, where normal users should not have write access.

But as always: do not just accept the recommendation blindly. Because just because this recommendation is there doesn’t mean something is inherently wrong, however during my frequent customer meetings, I always ask, if they regularly check the ACL permissions these folders have, the answer so far has always been ’no'.

So where do we start? First check what is actually exposed.

Why

A weak service executable path can matter in several scenarios:

  • Local privilege escalation
  • Service binary hijacking
  • Persistence through service tampering
  • Ransomware staging or defense evasion

This is especially relevant for devices where services run from custom folders such as:

  • C:\temp
  • D:\apps
  • D:\azuredevops
  • C:\tools
  • vendor-specific application folders (I am looking at you Oracle!)
  • old migration or installer directories

The folder location itself is not always the problem.

The real question is:

Who can write to that folder? Because Program Files or Windows have set ACL permission lists while anything outside of these standard OS Folders does not.

If a service runs from D:\SomeApp, and only Administrators and SYSTEM can modify that folder, the practical risk is much lower.

If Authenticated Users or Domain Users have Modify, Write, or FullControl, the risk becomes much more great or interesting depending on who looks at it.

Most of these folders go unnoticed because in general they slowly grow as most organisations don’t keep this in check and some applications even say in their manual; create a folder in C:\ and work from there. They do not want to bother you with ACL permissions, because than the barrier to entry might become to great and you might not use their software. Or maybe they also are not aware of the potential risk, either way, as an IT organisation you are responsible for what happens in your organisation.

This can be quite a daunting task however I wrote a KQL and a powershell script that, depending on how you want to put this in operation might need adjusting, can help with the tedious check of checking ACL permissions.

Workflow

The workflow is straightforward:

  1. Run the KQL in Microsoft Defender Advanced Hunting.
  2. Review the affected services and extracted base folders.
  3. Copy the ServiceName, ExecutableDirectory & BaseFolder values.
  4. Paste them into the PowerShell array.
  5. Run the PowerShell script on the affected device.
  6. Review ServiceExecutableFolderAclEvidence.csv.

The KQL tells you which folders Defender is concerned about.

The PowerShell collects the ACL evidence needed to determine whether those folders are writable by broad principals. You as the engineer need to make the decission if this is according to least privilege.

Find services outside common protected locations

Use the following KQL in Microsoft Defender Advanced Hunting.

let RecommendationName = "Change service executable path to a common protected location";
DeviceTvmSecureConfigurationAssessment
| join kind=inner (
    DeviceTvmSecureConfigurationAssessmentKB
    | where ConfigurationName =~ RecommendationName
    | project ConfigurationId, ConfigurationName
) on ConfigurationId
| where IsApplicable == 1
| where IsCompliant == 0
| extend ContextText = tostring(Context)
| extend Parsed = parse_json(ContextText)
| extend
    ServiceName = tostring(Parsed[0]),
    RawServicePath = tostring(Parsed[1])
| extend ServicePath = replace_string(RawServicePath, @"\""", @"""")
| extend ServicePath = trim(@'"', ServicePath)
| extend ServicePath = replace_regex(ServicePath, @"^\\\?\?\\", "")
| extend ServicePath = replace_regex(ServicePath, @"^\\\\\?\\", "")
| extend ExecutablePath = extract(@"([A-Za-z]:\\[^""]+?\.exe)", 1, ServicePath)
| extend BaseFolder = extract(@"^([A-Za-z]:\\[^\\]+)", 1, ExecutablePath)
| extend ExecutableDirectory = replace_regex(ExecutablePath, @"\\[^\\]+\.exe$", "")
| summarize arg_max(Timestamp, *) by DeviceId, ServiceName, ExecutablePath
| project
    DeviceName,
    Recommendation = ConfigurationName,
    ServiceName,
    RawServicePath,
    ServicePath,
    ExecutablePath,
    BaseFolder,
    ExecutableDirectory,
    Timestamp
| order by DeviceName asc, BaseFolder asc, ServiceName asc

This query:

  • Finds devices exposed to the Defender recommendation.
  • ServiceName
  • RawServicePath
  • ServicePath
  • ExecutablePath
  • BaseFolder
  • ExecutableDirectory
  • Provides the exact folders that should be reviewed.

KQL output

Example:

DeviceNameServiceNameExecutablePathBaseFolderExecutableDirectory
ENG-LT-024DockerDesktopServiceC:\Tools\Docker\com.docker.service.exeC:\ToolsC:\Tools\Docker
DEV-LT-118JenkinsAgentD:\BuildAgents\Jenkins\agent.exeD:\BuildAgentsD:\BuildAgents\Jenkins
IT-LT-009PDQDeployRunnerC:\Applications\PDQ\Runner\runner.exeC:\ApplicationsC:\Applications\PDQ\Runner
DEV-LT-203CustomUpdaterC:\DevTools\Updater\updater.exeC:\DevToolsC:\DevTools\Updater
ENG-LT-077AzureDevOpsAgentD:\azuredevops\a01\bin\AgentService.exeD:\azuredevopsD:\azuredevops\a01\bin
APP-LT-041OracleServiceXEC:\Oracle\product\21c\dbhomeXE\bin\oracle.exeC:\OracleC:\Oracle\product\21c\dbhomeXE\bin

Transfer from the Defender portal to Local on the device

From the KQL output, copy the ServiceName, ExecutableDirectory & BaseFolder values for the affected device. into the array below in the powershell.

Below is an example of multiple entries:

$ServicePathsToCheck = @(
    [PSCustomObject]@{
        ServiceName         = 'battlenet_helpersvc'
        BaseFolder          = 'C:\ProgramData'
        ExecutableDirectory = 'C:\ProgramData\Battle.net_components\battlenet_helpersvc'
    },

    [PSCustomObject]@{
        ServiceName         = 'EABackgroundService'
        BaseFolder          = 'D:\Program Files'
        ExecutableDirectory = 'D:\Program Files\EA\DesktopApp\EA Desktop'
    },

    [PSCustomObject]@{
        ServiceName         = 'MDCoreSvc'
        BaseFolder          = 'C:\ProgramData'
        ExecutableDirectory = 'C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.26030.3011-0'
    },

    [PSCustomObject]@{
        ServiceName         = 'TempTestService'
        BaseFolder          = 'C:\Temp'
        ExecutableDirectory = 'C:\Temp\TestService'
    },

    [PSCustomObject]@{
        ServiceName         = 'WdNisSvc'
        BaseFolder          = 'C:\ProgramData'
        ExecutableDirectory = 'C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.26030.3011-0'
    },

    [PSCustomObject]@{
        ServiceName         = 'WinDefend'
        BaseFolder          = 'C:\ProgramData'
        ExecutableDirectory = 'C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.26030.3011-0'
    }
)

Also note that below $ServicePathsToCheck is $EnableRecursiveCheck = $false.

This can be set to $true, which will scan all folders recursively.

Because of obvious resource considerations, recursive scanning is disabled by default. However, if needed, the script can also be used recursively.

$ServicePathsToCheck = @(
    [PSCustomObject]@{
        ServiceName         = ''
        BaseFolder          = ''
        ExecutableDirectory = ''
    }
)

$EnableRecursiveCheck = $false

$OutputFolder = Join-Path $env:USERPROFILE 'Desktop\ServiceAclAudit'

if (-not (Test-Path -LiteralPath $OutputFolder)) {
    New-Item -ItemType Directory -Path $OutputFolder | Out-Null
}

function Get-IdentitySid {
    param(
        [Parameter(Mandatory)]
        [System.Security.Principal.IdentityReference]$IdentityReference
    )

    try {
        return $IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value
    }
    catch {
        return $null
    }
}

function Get-ServicePathAclEvidence {
    param(
        [Parameter(Mandatory)]
        [string]$ServiceName,

        [Parameter(Mandatory)]
        [string]$BaseFolder,

        [Parameter(Mandatory)]
        [string]$ExecutableDirectory,

        [bool]$Recursive = $false
    )

    $pathsToInspect = @(
        [PSCustomObject]@{
            Path     = $BaseFolder
            PathType = 'BaseFolder'
        },
        [PSCustomObject]@{
            Path     = $ExecutableDirectory
            PathType = 'ExecutableDirectory'
        }
    ) | Where-Object {
        -not [string]::IsNullOrWhiteSpace($_.Path)
    }

    if ($Recursive -and (Test-Path -LiteralPath $BaseFolder)) {
        $childFolders = Get-ChildItem -LiteralPath $BaseFolder -Directory -Recurse -Force -ErrorAction SilentlyContinue

        foreach ($childFolder in $childFolders) {
            $pathsToInspect += [PSCustomObject]@{
                Path     = $childFolder.FullName
                PathType = 'SubFolder'
            }
        }
    }

    $pathsToInspect = $pathsToInspect | Sort-Object Path -Unique

    foreach ($pathItem in $pathsToInspect) {

        if (-not (Test-Path -LiteralPath $pathItem.Path)) {
            [PSCustomObject]@{
                ComputerName        = $env:COMPUTERNAME
                ServiceName         = $ServiceName
                BaseFolder          = $BaseFolder
                ExecutableDirectory = $ExecutableDirectory
                CheckedPath         = $pathItem.Path
                PathType            = $pathItem.PathType
                Owner               = ''
                InheritanceEnabled  = ''
                IdentityReference   = ''
                IdentitySid         = ''
                FileSystemRights    = ''
                AccessControlType   = ''
                IsInherited         = ''
                InheritanceFlags    = ''
                PropagationFlags    = ''
            }

            continue
        }

        try {
            $acl = Get-Acl -LiteralPath $pathItem.Path -ErrorAction Stop
        }
        catch {
            [PSCustomObject]@{
                ComputerName        = $env:COMPUTERNAME
                ServiceName         = $ServiceName
                BaseFolder          = $BaseFolder
                ExecutableDirectory = $ExecutableDirectory
                CheckedPath         = $pathItem.Path
                PathType            = $pathItem.PathType
                Owner               = ''
                InheritanceEnabled  = ''
                IdentityReference   = "Could not read ACL: $($_.Exception.Message)"
                IdentitySid         = ''
                FileSystemRights    = ''
                AccessControlType   = ''
                IsInherited         = ''
                InheritanceFlags    = ''
                PropagationFlags    = ''
            }

            continue
        }

        $inheritanceEnabled = -not $acl.AreAccessRulesProtected

        foreach ($ace in $acl.Access) {
            $identitySid = Get-IdentitySid -IdentityReference $ace.IdentityReference

            [PSCustomObject]@{
                ComputerName        = $env:COMPUTERNAME
                ServiceName         = $ServiceName
                BaseFolder          = $BaseFolder
                ExecutableDirectory = $ExecutableDirectory
                CheckedPath         = $pathItem.Path
                PathType            = $pathItem.PathType
                Owner               = $acl.Owner
                InheritanceEnabled  = $inheritanceEnabled
                IdentityReference   = $ace.IdentityReference.Value
                IdentitySid         = $identitySid
                FileSystemRights    = $ace.FileSystemRights.ToString()
                AccessControlType   = $ace.AccessControlType.ToString()
                IsInherited         = $ace.IsInherited
                InheritanceFlags    = $ace.InheritanceFlags.ToString()
                PropagationFlags    = $ace.PropagationFlags.ToString()
            }
        }
    }
}

$AclReport = foreach ($servicePath in $ServicePathsToCheck) {
    Get-ServicePathAclEvidence `
        -ServiceName $servicePath.ServiceName `
        -BaseFolder $servicePath.BaseFolder `
        -ExecutableDirectory $servicePath.ExecutableDirectory `
        -Recursive $EnableRecursiveCheck
}

$CsvPath = Join-Path $OutputFolder 'ServiceExecutableFolderAclEvidence.csv'

$AclReport |
    Select-Object `
        ComputerName,
        ServiceName,
        BaseFolder,
        ExecutableDirectory,
        CheckedPath,
        PathType,
        Owner,
        InheritanceEnabled,
        IdentityReference,
        IdentitySid,
        FileSystemRights,
        AccessControlType,
        IsInherited,
        InheritanceFlags,
        PropagationFlags |
    Sort-Object ServiceName, BaseFolder, CheckedPath, IdentityReference |
    Export-Csv -Path $CsvPath -NoTypeInformation -Delimiter ';' -Encoding UTF8

Write-Host ''
Write-Host 'ACL evidence exported to:'
Write-Host $CsvPath
Write-Host ''

$AclReport |
    Select-Object `
        ComputerName,
        ServiceName,
        BaseFolder,
        ExecutableDirectory,
        CheckedPath,
        PathType,
        Owner,
        InheritanceEnabled,
        IdentityReference,
        IdentitySid,
        FileSystemRights,
        AccessControlType,
        IsInherited,
        InheritanceFlags,
        PropagationFlags |
    Format-Table -AutoSize

Example of Output

ComputerNameServiceNameBaseFolderExecutableDirectoryCheckedPathPathTypeOwnerInheritanceEnabledIdentityReferenceIdentitySidFileSystemRightsAccessControlTypeIsInheritedInheritanceFlagsPropagationFlags
Host-01battlenet_helpersvcC:\ProgramDataC:\ProgramData\Battle.net_components\battlenet_helpersvcC:\ProgramDataBaseFolderNT AUTHORITY\SYSTEMFALSEBUILTIN\AdministratorsS-1-5-32-544FullControlAllowFALSEContainerInherit, ObjectInheritNone
Host-01battlenet_helpersvcC:\ProgramDataC:\ProgramData\Battle.net_components\battlenet_helpersvcC:\ProgramDataBaseFolderNT AUTHORITY\SYSTEMFALSEBUILTIN\UsersS-1-5-32-545ReadAndExecute, SynchronizeAllowFALSEContainerInherit, ObjectInheritNone
Host-01battlenet_helpersvcC:\ProgramDataC:\ProgramData\Battle.net_components\battlenet_helpersvcC:\ProgramDataBaseFolderNT AUTHORITY\SYSTEMFALSEBUILTIN\UsersS-1-5-32-545WriteAllowFALSEContainerInheritNone
Host-01battlenet_helpersvcC:\ProgramDataC:\ProgramData\Battle.net_components\battlenet_helpersvcC:\ProgramDataBaseFolderNT AUTHORITY\SYSTEMFALSECREATOR OWNERS-1-3-0268435456AllowFALSEContainerInherit, ObjectInheritInheritOnly
Host-01battlenet_helpersvcC:\ProgramDataC:\ProgramData\Battle.net_components\battlenet_helpersvcC:\ProgramDataBaseFolderNT AUTHORITY\SYSTEMFALSENT AUTHORITY\SYSTEMS-1-5-18FullControlAllowFALSEContainerInherit, ObjectInheritNone
Host-01battlenet_helpersvcC:\ProgramDataC:\ProgramData\Battle.net_components\battlenet_helpersvcC:\ProgramData\Battle.net_components\battlenet_helpersvcExecutableDirectoryBUILTIN\AdministratorsTRUEBUILTIN\AdministratorsS-1-5-32-544FullControlAllowTRUEContainerInherit, ObjectInheritNone
Host-01battlenet_helpersvcC:\ProgramDataC:\ProgramData\Battle.net_components\battlenet_helpersvcC:\ProgramData\Battle.net_components\battlenet_helpersvcExecutableDirectoryBUILTIN\AdministratorsTRUEBUILTIN\UsersS-1-5-32-545ReadAndExecute, SynchronizeAllowTRUEContainerInherit, ObjectInheritNone
Host-01battlenet_helpersvcC:\ProgramDataC:\ProgramData\Battle.net_components\battlenet_helpersvcC:\ProgramData\Battle.net_components\battlenet_helpersvcExecutableDirectoryBUILTIN\AdministratorsTRUENT AUTHORITY\INTERACTIVES-1-5-4ReadAndExecute, SynchronizeAllowTRUEContainerInherit, ObjectInheritNone
Host-01TempTestServiceC:\TempC:\TempC:\TempBaseFolderBUILTIN\AdministratorsTRUEBUILTIN\AdministratorsS-1-5-32-544FullControlAllowTRUEContainerInherit, ObjectInheritNone
Host-01TempTestServiceC:\TempC:\TempC:\TempBaseFolderBUILTIN\AdministratorsTRUEBUILTIN\UsersS-1-5-32-545ReadAndExecute, SynchronizeAllowTRUEContainerInherit, ObjectInheritNone
Host-01TempTestServiceC:\TempC:\TempC:\TempBaseFolderBUILTIN\AdministratorsTRUENT AUTHORITY\Authenticated UsersS-1-5-11-536805376AllowTRUEContainerInherit, ObjectInheritInheritOnly
Host-01TempTestServiceC:\TempC:\TempC:\TempBaseFolderBUILTIN\AdministratorsTRUENT AUTHORITY\Authenticated UsersS-1-5-11Modify, SynchronizeAllowTRUENoneNone
Host-01TempTestServiceC:\TempC:\TempC:\TempBaseFolderBUILTIN\AdministratorsTRUENT AUTHORITY\SYSTEMS-1-5-18FullControlAllowTRUEContainerInherit, ObjectInheritNone
Host-01TempTestServiceC:\TempC:\TempC:\TempExecutableDirectoryBUILTIN\AdministratorsTRUEBUILTIN\AdministratorsS-1-5-32-544FullControlAllowTRUEContainerInherit, ObjectInheritNone
Host-01TempTestServiceC:\TempC:\TempC:\TempExecutableDirectoryBUILTIN\AdministratorsTRUEBUILTIN\UsersS-1-5-32-545ReadAndExecute, SynchronizeAllowTRUEContainerInherit, ObjectInheritNone
Host-01TempTestServiceC:\TempC:\TempC:\TempExecutableDirectoryBUILTIN\AdministratorsTRUENT AUTHORITY\Authenticated UsersS-1-5-11-536805376AllowTRUEContainerInherit, ObjectInheritInheritOnly
Host-01TempTestServiceC:\TempC:\TempC:\TempExecutableDirectoryBUILTIN\AdministratorsTRUENT AUTHORITY\Authenticated UsersS-1-5-11Modify, SynchronizeAllowTRUENoneNone
Host-01TempTestServiceC:\TempC:\TempC:\TempExecutableDirectoryBUILTIN\AdministratorsTRUENT AUTHORITY\SYSTEMS-1-5-18FullControlAllowTRUEContainerInherit, ObjectInheritNone
Host-01WinDefendC:\ProgramDataC:\ProgramData\Microsoft\Windows Defender\Platform\4.18.26030.3011-0C:\ProgramDataBaseFolderNT AUTHORITY\SYSTEMFALSEBUILTIN\AdministratorsS-1-5-32-544FullControlAllowFALSEContainerInherit, ObjectInheritNone
Host-01WinDefendC:\ProgramDataC:\ProgramData\Microsoft\Windows Defender\Platform\4.18.26030.3011-0C:\ProgramDataBaseFolderNT AUTHORITY\SYSTEMFALSEBUILTIN\UsersS-1-5-32-545WriteAllowFALSEContainerInheritNone

What to look for

The most important thing to review is whether broad principals have write-capable permissions.

Examples:

Authenticated Users    Modify
BUILTIN\Users          Modify
Domain Users           Write
Everyone               FullControl

These are the entries that usually matter.

Read-only entries such as:

BUILTIN\Users    ReadAndExecute

are generally less concerning. Still use your best judgement and take into context the product we are reviewing, is it least privilege? If not, can we change it? Or can we accept the risk. Also take into account that

Deployment options

There are several operational ways to deploy this at scale.

Option 1: Manual assessment

Best for:

  • one-off reviews
  • small environments
  • focused investigations

Workflow:

  • Run the KQL.
  • Copy the folders.
  • Run the PowerShell manually.
  • Review the CSV locally.

This is the simplest approach.

Option 2: Intune deployment with local CSV output

Workflow:

  1. Use the KQL to identify affected devices.
  2. Deploy the PowerShell using:
    • Intune Platform Scripts
    • Intune Proactive Remediations
  3. The script generates the CSV locally.
  4. Retrieve results using:
    • Intune Collect Diagnostics
    • Defender Live Response
    • RMM tooling
    • remote collection methods

Option 3: Centralized ingestion into Sentinel or Log Analytics

Best for:

  • large environments
  • security engineering
  • posture management
  • ongoing monitoring

Workflow:

  1. Run the script through Intune.
  2. Convert findings to JSON.
  3. Send results to:
    • Log Analytics

This allows:

  • dashboards
  • trending
  • potential alerting
  • Centralized information for decision making

Important nuance

Do not blindly remediate this recommendation. The presence of this finding does not automatically mean something is exploitable or insecure, but it does warrant review.

Many environments intentionally run services from locations outside standard protected operating system folders. Common examples include:

  • build agents
  • deployment frameworks
  • middleware platforms
  • legacy applications
  • vendor-managed software
  • Azure VM extension paths

The folder location itself is not necessarily the problem.

The real question is:

Can broad principals modify the folder contents?

If a custom service folder is writable only by principals such as:

  • SYSTEM
  • TrustedInstaller
  • Administrators

the practical risk may be limited or acceptable depending on the environment and operational requirements.

The actual concern is when broad principals such as:

  • Authenticated Users
  • BUILTIN\Users
  • Domain Users
  • Everyone

have write-capable permissions on directories used by (privileged) services.

One thing you will likely notice quickly when running this KQL in enterprise environments is the large number of findings related to:

C:\Packages\Plugins\

This is especially common on Azure virtual machines running components such as:

  • Microsoft Defender for SQL
  • Azure Automation Hybrid Workers
  • monitoring agents
  • backup agents
  • Azure VM extensions

These directories are commonly used by Microsoft-managed extension frameworks and are therefore not automatically insecure simply because they exist outside C:\Program Files.

The same validation principle still applies however:

Do not trust the folder location alone. Validate the ACLs.

In many environments these folders are still properly protected and writable only by privileged principals. In those cases, the finding may represent an accepted or low practical risk.

This recommendation should therefore be treated as a signal for review and validation, not as proof of exploitation or immediate misconfiguration.

Why ransomware operators care

Ransomware is not only about encryption.

Before encryption, attackers often try to:

  • escalate privileges
  • maintain persistence
  • disable security tooling
  • tamper with services
  • abuse trusted execution paths

A writable service executable folder can help with that.

If an attacker already has a foothold as a normal user and can modify files inside a privileged service path, that may become a local privilege escalation or persistence mechanism.

That does not mean every finding is immediately exploitable.

But it absolutely means the finding deserves review.

Final thought

This Defender recommendation is useful, but the real value is not the recommendation itself.

The real value is validating the actual ACLs.

A service running outside C:\Program Files is not automatically dangerous.

A service running from a folder where Authenticated Users or Domain Users can modify files is a different story.

So the practical workflow becomes:

  1. Find the exposed service path with KQL.
  2. Extract the data.
  3. Validate the ACLs locally or centrally.
  4. Determine whether folders are adhered to least privilege.
  5. Then decide whether remediation is needed.