← All posts

How Windows Services Become a Privilege Escalation Highway (and How to Audit Yours)

An attacker who lands on a Windows box as a normal user rarely stays one for long. The interesting part of an intrusion — credential theft, lateral movement, ransomware staging — almost always needs SYSTEM. And the fastest road from a low-privilege shell to SYSTEM on most machines isn’t a kernel zero-day or a fancy exploit. It’s a service that someone installed without thinking about the permissions. Here are the four service misconfigurations that hand out SYSTEM, why they’re so common, and how to audit every service on the machine in front of you — free.

A Windows service is, in privilege terms, a loaded gun pointed at the OS. Most services run as LocalSystem — the most powerful account on the box — and the Service Control Manager (SCM) starts their binary every boot without a human in the loop. That combination is exactly what an attacker wants: if you can influence which binary a SYSTEM service runs, or replace the file it points at, you don’t need to escalate at all. The service escalates for you, automatically, at the next start. This whole class of attack is MITRE ATT&CK’s T1574, “Hijack Execution Flow,” and it is depressingly reliable because the flaws are introduced by ordinary software installers, not by anything exotic.

1. Unquoted Service Paths

The oldest trick in the book, and still everywhere. When a service’s ImagePath contains spaces and isn’t wrapped in quotes, Windows resolves it ambiguously. A path like C:\Program Files\Acme Agent\service.exe stored without quotes gets tried as C:\Program.exe first, then C:\Program Files\Acme.exe, and so on. If a low-privilege user can write to any of those earlier directories — and on a sloppy install, C:\ or a vendor folder under C:\ is sometimes user-writable — they drop a malicious Acme.exe and the SCM launches it as SYSTEM at the next service start.

# Find every service whose binary path has a space and no quotes:
Get-CimInstance Win32_Service |
  Where-Object { $_.PathName -match '^[^"].*\s.*\.exe' -and $_.PathName -notmatch '^"' } |
  Select-Object Name, PathName, StartName

The fix is trivial — quote the path — but you can’t fix what you don’t know about, and a typical workstation runs 200+ services from a dozen vendors.

2. Weak Service Permissions (the SCM ACL)

Every service object has its own access-control list governing who can query, configure, start, and stop it. If that ACL grants a non-admin principal — Authenticated Users, INTERACTIVE, a specific low-priv group — the SERVICE_CHANGE_CONFIG right, the game is over. That right lets the user rewrite the service’s binPath to point at anything, then start it:

# An attacker with SERVICE_CHANGE_CONFIG needs only two commands:
sc.exe config VulnSvc binPath= "C:\Users\Public\payload.exe"
sc.exe start VulnSvc        # ...now running as SYSTEM

# Audit the effective DACL on a service to see who can reconfigure it:
sc.exe sdshow VulnSvc

These overly-permissive ACLs are usually introduced by a third-party installer trying to make its “check for updates” feature work without prompting for admin. The convenience cost is a one-command local privilege escalation that no antivirus will flag, because sc.exe reconfiguring a service is a perfectly normal administrative action.

3. Writable Service Binaries and Folders

Even with a quoted path and a locked-down service ACL, the service is only as safe as the file it runs. If the service.exe on disk — or the directory containing it — is writable by a normal user, the attacker simply overwrites the binary with their own and waits for a restart (or triggers one). This is the same weak-folder-ACL problem that enables DLL hijacking: a service that loads a helper DLL from a user-writable directory is just as exploitable as one whose main executable is replaceable. The audit question is “can anyone who isn’t an administrator write to the path this SYSTEM service executes from?”

4. Weak Registry ACLs on the Service Key

The subtle one. Services are defined under HKLM\SYSTEM\CurrentControlSet\Services\<Name>, and that registry key has its own permissions. If a non-admin can write to a service’s key, they can edit the ImagePath value directly — same outcome as the SCM-ACL attack, but reached through the registry instead of sc.exe, and missed entirely by tools that only check service-object DACLs. A thorough audit looks at both the SCM permissions and the backing registry ACL, because a machine can be clean on one and wide open on the other.

Auditing All of It Without the Manual Slog

You can chase each of these by hand with sc.exe, icacls, and a pile of PowerShell — and for one service, you should know how. But a real machine has hundreds of services, and the flaw is never the service you’re looking at; it’s the one you didn’t. This is exactly the kind of wide, mechanical enumeration that belongs in a posture audit, not in your morning. On the machine in front of you, the full sweep is free and finishes in seconds:

# Audit service configs, paths, ACLs, and writable-binary issues — full depth:
winsentinel --audit

# Snapshot the service surface as a baseline you can diff later:
winsentinel --audit --format json --out services-baseline.json

# Then remediate what doesn't belong:
winsentinel --fix-it

None of these four flaws require a CVE, and none of them show up in a patch-level scan. They are configuration state — the registry values, file ACLs, and service descriptors that a vulnerability scanner walks right past because the software itself is “up to date.” That gap, between a fully-patched machine and a hardened one, is precisely where service-based escalation lives, and it’s the surface a configuration audit is built to read.

Where the Org Story Begins: Escalation Paths Are a Fleet Problem

On a single machine, auditing services and fixing the handful that are misconfigured is genuinely complete — every check above runs free, at full depth, on the box you own. The problem changes shape at scale. The reason service escalation is so dangerous in an organization isn’t any one laptop; it’s that the same badly-behaved vendor installer lands an identical writable-folder or weak-ACL service on every machine it touches. One unquoted-path agent rolled out to 200 endpoints is 200 local-privilege-escalation primitives, seeded by your own software deployment.

That’s the boundary where fleet orchestration earns its keep. WinSentinel Pro puts an agent on each endpoint running the same service audit into a central node — the depth is identical to the free single-machine scan; Pro doesn’t unlock extra checks. What it adds is the org-level view: a fleet rollup that says “47 of your 200 machines have a writable-binary service from the same vendor” in one place instead of 200 separate scans; drift alerts when a deployment introduces a new weak service across the fleet; and compliance evidence that your endpoints’ service configurations stayed locked down over an audit window. Single-machine service hardening is free and total. The “which of my 200 machines did that installer just weaken?” question — answered as one report — is the org problem Pro exists to solve.

The Takeaway

Attackers don’t escalate the hard way when the easy way is sitting in the service list. Unquoted paths, loose SCM ACLs, writable binaries, and weak registry permissions turn ordinary SYSTEM services into a privilege-escalation highway — and they get there through the software you installed on purpose. Treat your service inventory the way you treat your firewall: enumerated, permission-checked, and baselined, so the next vendor installer can’t quietly leave a door propped open.

# Close the service escalation paths on this machine right now:
winsentinel --audit
winsentinel --fix-it

An attacker only needs one misconfigured service. Auditing all of them is how you make sure they don’t find it first.