guest@6admin.io:~$

Which identity runs the service and who can prove it

A Windows service runs as some identity and that identity is the whole security story: it is the blast radius when the service is exploited and it is the name in the logs when you need to know who did what. Most of the trouble I audit comes from one shortcut, a local account with a static password baked into the service configuration, which is both a secret sitting in cleartext and an identity nobody rotates. The fix is not a better password, it is choosing an identity that has no password a human ever holds.

The identity ladder

Pick the highest rung the service can live on, not the lowest one that happens to work.

The three built-in service identities are the floor and cover most isolated services: LocalService (very limited, no network identity), NetworkService (limited locally, authenticates to the network as the computer account) and LocalSystem (full local power, avoid for anything custom). None has a password. For a service that only touches its own files, LocalService is the right answer and the end of the discussion.

sc.exe create MyService binPath="C:\app\svc.exe" obj="NT AUTHORITY\LocalService"

A virtual service account, NT SERVICE\<ServiceName>, is the next rung and the one people overlook. The service manager creates a per-service SID with no password and that SID can be granted file and registry rights of its own, so two services under NetworkService stop sharing an identity and start being isolable from each other. For a single-box service that needs its own ACLs, this is usually the sweet spot.

A group Managed Service Account (gMSA) is the answer the moment the service needs a domain identity, to reach a share or a database as itself. Active Directory owns the password, rotates it on its own schedule and no operator ever types or stores it. That property, a domain identity with no password a human knows, is exactly what the local-account-with-a-password anti-pattern fails to provide.

# once per forest
Add-KdsRootKey -EffectiveImmediately
New-ADServiceAccount -Name gmsa_app -DNSHostName app.company.internal `
    -PrincipalsAllowedToRetrieveManagedPassword "AppServers"
# on the host, after Install-ADServiceAccount
sc.exe config MyService obj="COMPANY\gmsa_app$" password=""

A local account with a static password is the bottom rung, a stopgap while a gMSA is provisioned, never a destination. If you must, treat the password as the liability it is: unique per host, never reused, deny every interactive path and a calendar entry to delete the account when the real one lands.

Least privilege, concretely

Whatever identity runs the service, it needs the “log on as a service” right and nothing it does not use. The deny rights matter as much as the grants, because they stop a service identity from ever being usable as a way in:

# grant only service logon; deny every interactive and remote path
$acc = "NT SERVICE\MyService"
# SeServiceLogonRight granted; SeDenyInteractiveLogonRight,
# SeDenyNetworkLogonRight, SeDenyRemoteInteractiveLogonRight denied

Rights come from the matching built-in groups rather than from handing out privileges one by one: Event Log Readers for a log collector, Performance Monitor Users for a metrics agent, IIS_IUSRS for a web worker. File access is granted to the service SID on the exact directories it uses, read-and-execute on the binaries, write only where it genuinely writes:

icacls "C:\app"      /grant "NT SERVICE\MyService:(OI)(CI)RX" /T
icacls "C:\app\data" /grant "NT SERVICE\MyService:(OI)(CI)M"  /T

The principle is boring and it is the whole game: a service that is only ever allowed to read its binaries and write its data directory is a service whose compromise buys an attacker a read of its binaries and a write to its data directory and nothing else.

Accounting: proving what runs as whom

The second job of a service identity is traceability and it only works if the identity is distinct per service. A dozen services all running as NetworkService are a dozen things you cannot tell apart in a log; the same dozen under their own virtual accounts or gMSAs are twelve names you can follow.

The authoritative view of what actually runs as what comes from the service manager, not from what the installer intended:

Get-CimInstance Win32_Service |
    Select-Object Name, StartName, State, StartMode |
    Sort-Object StartName

For the audit trail, the event IDs that matter are 7045 (a service was installed, the first thing to check when an unexpected one appears), 7034/7036 (crashes and state changes) and in the Security log 4624 with logon type 5 (a service logon) tied to the account, plus 4672 when that logon was granted sensitive privileges. Enable the service-related auditing and those become a timeline:

auditpol /set /subcategory:"Security System Extension" /success:enable /failure:enable
Get-WinEvent -FilterHashtable @{LogName='System'; ID=7045}

Where to land

Prefer the identity with no password a human holds: a built-in service SID for isolated work, a virtual account when it needs its own ACLs, a gMSA the moment it needs a domain identity. Grant the service logon right, deny every interactive and network logon path, scope file and registry rights to the service’s own SID and give each service a distinct identity so the logs can tell them apart. The static-password local account is the one to migrate off, not to perfect and its only correct lifespan is “until the gMSA exists”.