When managing Windows Services on non-domain-joined servers (particularly in internet-facing environments), administrators often need to delegate service control permissions without granting full administrative rights. The standard Windows security model doesn't provide granular service control permissions out of the box.
The most straightforward method uses Windows' native sc.exe
command-line tool to modify the security descriptor of specific services:
sc sdshow "YourServiceName" > currentSD.txt # Modify the output with appropriate permissions (more on this below) sc sdset "YourServiceName" "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWRPWPDTLOCRRC;;;SU)(A;;RPWPDTLO;;;S-1-5-32-555)"
The security descriptor string follows this pattern:
D:(ace1)(ace2)...(aceN)
Where each ACE (Access Control Entry) has the structure:
(A;;access_mask;;;sid)
- RP: SERVICE_START
- WP: SERVICE_STOP
- DT: SERVICE_PAUSE_CONTINUE
- LO: SERVICE_QUERY_STATUS
- CR: SERVICE_USER_DEFINED_CONTROL
To grant a local group "ServiceOperators" start/stop/restart permissions on "MyBackgroundService":
# First get the SID of your target group wmic group where name='ServiceOperators' get sid # Output might look like: S-1-5-21-3623811015-3361044348-30300820-1013 # Then apply the modified SDDL sc sdset "MyBackgroundService" "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWRPWPDTLOCRRC;;;SU)(A;;RPWPDTLO;;;S-1-5-21-3623811015-3361044348-30300820-1013)"
For modern servers, PowerShell provides more intuitive control:
$service = Get-Service -Name "MyBackgroundService" $acl = Get-Acl -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$($service.Name)" $rule = New-Object System.Security.AccessControl.ServiceAccessRule("DOMAIN\User", "Start,Stop", "Allow") $acl.AddAccessRule($rule) Set-Acl -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$($service.Name)" -AclObject $acl
Always verify changes with:
sc sdshow "ServiceName" # Or for PowerShell: Get-Acl "HKLM:\SYSTEM\CurrentControlSet\Services\ServiceName" | Format-List
- Never grant SERVICE_CHANGE_CONFIG or SERVICE_ALL_ACCESS to non-admin users
- Prefer group-based permissions over individual user accounts
- Document all permission changes in your change control system
- Consider creating a dedicated local group for service operators
Managing Windows Services often requires administrative privileges, which creates security concerns when you need to delegate specific service control to non-admin users or service accounts. The common workarounds like modifying registry ACLs (as mentioned in KB907460) or granting full administrator rights are either too broad or potentially unsafe.
Windows provides a robust way to modify service permissions through Service Control (SC) commands using Security Descriptor Definition Language (SDDL). Here's how to implement it:
:: Grant "Start/Stop" permissions to a specific user sc sdset "YourServiceName" "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;RPWPDTLO;;;S-1-5-21-123456789-1234567890-123456789-1001)"
The SDDL string contains these key components:
D:
- DACL (Discretionary Access Control List) prefixA;;CCLCSWRPWPDTLOCRRC;;;SY
- System account permissionsA;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA
- Built-in administrators permissionsA;;CCLCSWLOCRRC;;;IU
- Interactive users permissionsA;;RPWPDTLO;;;S-1-5-21-...
- Custom user permissions (replace with target user/group SID)
Here's a more maintainable PowerShell script to set these permissions:
$serviceName = "YourServiceName" $userSid = (New-Object System.Security.Principal.NTAccount("DOMAIN\Username")).Translate([System.Security.Principal.SecurityIdentifier]).Value $sddl = @" D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;RPWPDTLO;;;$userSid) "@ sc.exe sdset $serviceName $sddl
To check the current security descriptor of a service:
sc.exe sdshow "YourServiceName"
Microsoft's SubInACL provides another approach:
subinacl.exe /service "YourServiceName" /grant=domain\username=STO
Where STO represents:
- S - Start service
- T - Stop service
- O - Other control permissions
When implementing this solution:
- Always use the principle of least privilege
- Prefer granting permissions to groups rather than individual users
- Document all permission changes for audit purposes
- Consider implementing just-in-time access for production environments
For local accounts (non-domain), first get the SID using:
wmic useraccount where name='username' get sid
Then use the SID in your SDDL string as shown in the PowerShell example.