Windows Security: Best practices for securing Windows services

Hey there, fellow threat hunters! 👋 Today we're diving into Windows Service hardening. Sure, everyone knows you should "secure your services," but let's get into the nitty-gritty of what that actually means and how to do it properly.

Windows Security: Best practices for securing Windows services

Important Disclaimer

  • This blog post is intended for educational purposes only
  • In production environments, always use established and well-tested security tools such as:
    • Microsoft Security Configuration Manager (SCM)
    • Microsoft Defender for Endpoint
    • Enterprise service management platforms
    • Commercial security compliance tools
  • Always test service modifications in a non-production environment first
  • Follow your organization's change management procedures
  • Document all changes and maintain proper system backups

Understanding the Basics: Why Services Are a Target

Before we jump into hardening, let's understand why attackers love targeting Windows services:
  • Often run with SYSTEM privileges
  • Start automatically with Windows
  • Can be used for persistence
  • Many organizations don't properly audit them
  • Provide access to system resources

1. Service Account Principle of Least Privilege

First, let's check for services running with excessive privileges:
# Get services running as SYSTEM
Get-WmiObject win32_service | 
    Where-Object {$_.StartName -eq "LocalSystem"} |
    Select-Object Name, DisplayName, StartName, PathName |
    Format-Table -AutoSize
Best practices for service accounts:
  • Use managed service accounts (MSAs) where possible
  • Create dedicated service accounts with minimal permissions
  • Never use domain admin accounts for services
  • Regularly audit service account permissions
  • Document service account dependencies and permissions

2. Understanding Service Accounts: MSA vs gMSA

Let's clear up some confusion about service accounts. I've seen too many environments where admins either don't know about or don't understand the difference between Managed Service Accounts (MSAs) and Group Managed Service Accounts (gMSAs).

Traditional Service Accounts - The Old Way

First, let's remember why we're moving away from traditional service accounts:
  • Manual password management
  • Password rotation headaches
  • Service interruptions during password changes
  • Passwords stored in scripts/documentation
  • No centralized management

Managed Service Accounts (MSAs)

MSAs are like your entry-level managed accounts:
  • Features:
    • Automatic password management
    • Simplified SPN management
    • Can only be used on one computer
    • Built-in password rotation
  • Best used for:
    • Single-server applications
    • Standalone services
    • Windows Server 2008 R2 environments
    • Simple service deployments
Here's how to create an MSA:
# Create a regular MSA
New-ADServiceAccount -Name "MSA_AppService" -RestrictToSingleComputer

# Install the MSA on the server
Add-ADComputerServiceAccount -Identity "ServerName" -ServiceAccount "MSA_AppService"

# Install the service account locally
Install-ADServiceAccount -Identity "MSA_AppService"

# Test the installation
Test-ADServiceAccount -Identity "MSA_AppService"

Group Managed Service Accounts (gMSAs) - The Modern Approach

gMSAs are like MSAs with superpowers:
  • Features:
    • Can be used across multiple servers
    • Automatic password management
    • Complex 240-character passwords
    • Centralized management
    • Supports Failover Clusters
    • Built-in SPN management
  • Best used for:
    • Farm deployments
    • Load-balanced services
    • Clustered services
    • Modern Windows deployments (2012 R2 and newer)
    • Enterprise applications
Here's how to set up a gMSA:
# First, ensure you have a KDS Root Key (only needed once in the domain)
Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))

# Create a security group for servers that will use the gMSA
New-ADGroup -Name "gMSA_Servers" `
   -GroupScope Global `
   -Description "Servers that can use the gMSA account"

# Add servers to the group
Add-ADGroupMember -Identity "gMSA_Servers" -Members "Server1$","Server2$"

# Create the gMSA
New-ADServiceAccount -Name "gMSA_WebApp" `
   -DNSHostName "gMSA_WebApp.domain.com" `
   -PrincipalsAllowedToRetrieveManagedPassword "gMSA_Servers" `
   -ServicePrincipalNames "HTTP/webapp.domain.com"

# Install on each server
Invoke-Command -ComputerName "Server1", "Server2" -ScriptBlock {
   Install-ADServiceAccount -Identity "gMSA_WebApp"
   Test-ADServiceAccount -Identity "gMSA_WebApp"
}

When to Use What - Decision Matrix

ScenarioRecommended Account TypeReason
Single server application MSA Simpler to manage, sufficient for single-server deployments
Web farm gMSA Supports multiple servers, works with load balancing
Failover cluster gMSA Supports resource migration between nodes
Legacy application Traditional Service Account If application doesn't support managed accounts
Modern enterprise app gMSA Best security features and management capabilities

Common Pitfalls and Troubleshooting

Let's look at some common issues and how to resolve them:
# Issue 1: Service won't start with gMSA
# Check if the server can retrieve the password
Test-ADServiceAccount -Identity "gMSA_WebApp"

# Issue 2: SPNs not registering
# View current SPNs
Get-ADServiceAccount -Identity "gMSA_WebApp" -Properties ServicePrincipalNames

# Add missing SPN
Set-ADServiceAccount -Identity "gMSA_WebApp" `
   -ServicePrincipalNames @{Add="HTTP/newapp.domain.com"}

# Issue 3: Permission problems
# Check gMSA permissions
Get-ADServiceAccount -Identity "gMSA_WebApp" -Properties * |
   Select-Object Name, PrincipalsAllowedToRetrieveManagedPassword

3. Service Executable Security

Now that we have our service accounts sorted, let's lock down the service executables themselves:
# Comprehensive service executable audit script
function Audit-ServiceExecutables {
   Get-WmiObject win32_service | ForEach-Object {
       $service = $_
       $execPath = $service.PathName -replace '^"([^"]+)".*', '$1'
       
       try {
           $acl = Get-Acl $execPath -ErrorAction Stop
           $sig = Get-AuthenticodeSignature $execPath -ErrorAction Stop
           
           [PSCustomObject]@{
               ServiceName = $service.Name
               ExecutablePath = $execPath
               Permissions = $acl.Access | Where-Object {
                   $_.IdentityReference -notmatch 'NT SERVICE|NT AUTHORITY|BUILTIN'
               }
               IsSigned = $sig.Status -eq 'Valid'
               SignatureStatus = $sig.Status
               SignedBy = $sig.SignerCertificate.Subject
               LastWriteTime = (Get-Item $execPath).LastWriteTime
               FileHash = (Get-FileHash $execPath).Hash
           }
       } catch {
           Write-Warning "Could not check $($service.Name): $($_.Exception.Message)"
       }
   }
}
Best practices for service executables:
  • File System Security:
    • Place service executables in secure locations
    • Restrict permissions to SYSTEM and Administrators
    • Enable auditing on executable directories
    • Use file integrity monitoring
  • Signature Verification:
    • Only allow signed executables
    • Verify signature chain
    • Maintain an allowlist of trusted publishers
  • Path Security:
    • Use fully qualified paths
    • Avoid paths with spaces unless properly quoted
    • Restrict write access to service directories
Here's a script to fix common service path issues:
# Fix unquoted service paths
Get-WmiObject win32_service | 
   Where-Object {$_.PathName -notmatch '^".*"$' -and $_.PathName -match '\s+'} |
   ForEach-Object {
       $newPath = '"' + $_.PathName + '"'
       Write-Host "Fixing path for $($_.Name)"
       $_ | Set-WmiInstance -Arguments @{PathName=$newPath}
   }

4. Service Dependencies and Attack Surface

Understanding service dependencies is crucial for security. One vulnerable dependency can compromise your entire service chain:
# Comprehensive dependency mapping function
function Get-ServiceDependencyMap {
   param(
       [string]$ServiceName,
       [int]$MaxDepth = 10
   )
   
   $dependencyMap = @{}
   $seenServices = @{}
   
   function Map-Dependencies {
       param(
           $ServiceName,
           $CurrentDepth = 0
       )
       
       if ($CurrentDepth -gt $MaxDepth -or $seenServices.ContainsKey($ServiceName)) {
           return
       }
       
       $seenServices[$ServiceName] = $true
       $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
       
       if ($service) {
           $serviceInfo = @{
               Status = $service.Status
               StartType = $service.StartType
               Dependencies = @()
               DependentServices = @()
           }
           
           # Get dependencies
           $service.ServicesDependedOn | ForEach-Object {
               $serviceInfo.Dependencies += $_.Name
               Map-Dependencies -ServiceName $_.Name -CurrentDepth ($CurrentDepth + 1)
           }
           
           # Get dependent services
           $service.DependentServices | ForEach-Object {
               $serviceInfo.DependentServices += $_.Name
           }
           
           $dependencyMap[$ServiceName] = $serviceInfo
       }
   }
   
   Map-Dependencies -ServiceName $ServiceName
   return $dependencyMap
}

5. Advanced Hardening Techniques

Beyond basic configuration, let's implement advanced hardening:

5.1 Service Isolation

# Configure service isolation
$serviceName = "YourService"
$sddl = "D:(A;;CCLCSWRPWPDTLOCRRC;;;SU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)"

# Apply restricted security descriptor
sc.exe sdset $serviceName $sddl

# Verify the change
$service = Get-WmiObject win32_service -Filter "Name='$serviceName'"
$service.GetSecurityDescriptor().Descriptor

5.2 Resource Access Restrictions

# Create a restricted token for service
$policy = @'
{
   "Windows": {
       "SecurityFilters": [
           {
               "Services": {
                   "IsolationType": "Limited",
                   "RestrictedServices": ["YourService"],
                   "Capabilities": ["EnumerateUsers"]
               }
           }
       ]
   }
}
'@

Set-Content -Path "C:\ServicePolicy.json" -Value $policy

# Apply policy using Windows Security
New-ServiceSecurityPolicy -Path "C:\ServicePolicy.json"

6. Monitoring and Incident Response

Set up proper monitoring for your hardened services:
# Create an event monitor for critical service changes
$query = @"
    <QueryList>
        <Query Id="0" Path="System">
            <Select Path="System">
                *[System[(EventID=7030 or EventID=7031 or EventID=7032 or 
                  EventID=7034 or EventID=7035 or EventID=7036 or EventID=7040)]]
            </Select>
        </Query>
    </QueryList>
"@

# Create the event subscription
$params = @{
    Query = $query
    SourceIdentifier = "ServiceMonitor"
    Action = {
        $event = $Event.SourceEventArgs.NewEvent
        $message = "Service Event Detected: $($event.Message)"
        Send-MailMessage -To "admin@domain.com" -Subject "Service Alert" -Body $message
    }
}

Register-CimIndicationEvent @params

7. Common Attack Scenarios and Mitigations

Let's look at real-world attacks and how to prevent them:

7.1 DLL Hijacking Prevention

# Audit service DLL load paths
function Audit-ServiceDLLPaths {
   Get-WmiObject win32_service | ForEach-Object {
       $service = $_
       if ($service.PathName -like "*svchost.exe*") {
           $regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$($service.Name)\Parameters"
           $dllPath = Get-ItemProperty -Path $regPath -Name "ServiceDll" -ErrorAction SilentlyContinue
           
           if ($dllPath) {
               [PSCustomObject]@{
                   ServiceName = $service.Name
                   DLLPath = $dllPath.ServiceDll
                   Exists = Test-Path $dllPath.ServiceDll
                   IsSecurePath = $dllPath.ServiceDll -like "$env:SystemRoot\System32\*"
               }
           }
       }
   }
}

7.2 Privilege Escalation Prevention

  • Service Configuration:
    • Restrict service account permissions
    • Remove unnecessary privileges
    • Implement service isolation
    • Monitor for privilege changes
  • File System Security:
    • Lock down service directories
    • Implement access controls
    • Monitor for unauthorized changes

8. Quick Reference - Hardening Checklist

CategoryActionPriority
Account Security Implement gMSA High
File System Secure service paths High
Dependencies Map and secure dependencies Medium
Monitoring Implement change detection High
Isolation Configure service isolation Medium

References

Wrapping Up

Remember, service hardening is not a one-time task but an ongoing process. Keep your configurations updated, monitor for changes, and regularly audit your service security posture. Stay tuned for our next post where we'll dive into Windows Scheduled Task security! Until then, keep hunting! 🕵️‍♂️

0 comments:

Post a Comment