source/Scripts/Configuration/Apply-LatestSTIGs.ps1 (298 lines of code) (raw):
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(Mandatory = $false)]
[Hashtable] $DynParameters
)
#region Initialization
$Script:FullName = $MyInvocation.MyCommand.Path
$Script:File = $MyInvocation.MyCommand.Name
$Script:Name=[System.IO.Path]::GetFileNameWithoutExtension($Script:File)
$Script:Args = $null
If ($ENV:PROCESSOR_ARCHITEW6432 -eq "AMD64") {
Try {
foreach($k in $MyInvocation.BoundParameters.keys)
{
switch($MyInvocation.BoundParameters[$k].GetType().Name)
{
"SwitchParameter" {if($MyInvocation.BoundParameters[$k].IsPresent) { $Script:Args += "-$k " } }
"String" { $Script:Args += "-$k `"$($MyInvocation.BoundParameters[$k])`" " }
"Int32" { $Script:Args += "-$k $($MyInvocation.BoundParameters[$k]) " }
"Boolean" { $Script:Args += "-$k `$$($MyInvocation.BoundParameters[$k]) " }
}
}
If ($Script:Args) {
Start-Process -FilePath "$env:WINDIR\SysNative\WindowsPowershell\v1.0\PowerShell.exe" -ArgumentList "-File `"$($Script:FullName)`" $($Script:Args)" -Wait -NoNewWindow
} Else {
Start-Process -FilePath "$env:WINDIR\SysNative\WindowsPowershell\v1.0\PowerShell.exe" -ArgumentList "-File `"$($Script:FullName)`"" -Wait -NoNewWindow
}
}
Catch {
Throw "Failed to start 64-bit PowerShell"
}
Exit
}
[String]$Script:LogDir = "$($env:SystemRoot)\Logs\Configuration"
If (-not(Test-Path -Path $Script:LogDir)) {
New-Item -Path "$($env:SystemRoot)\Logs" -Name Configuration -ItemType Dir -Force
}
[string]$Script:LogName = "$($Script:Name).log"
If (Test-Path "$Script:LogDir\$Script:LogName") {
Remove-Item "$Script:LogDir\$Script:LogName" -Force
}
Start-Transcript -Path "$Script:LogDir\$Script:LogName"
#endregion
#region Functions
Function Set-BluetoothRadioStatus {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[ValidateSet('Off', 'On')]
[string]$BluetoothStatus
)
If ((Get-Service bthserv).Status -eq 'Stopped') { Start-Service bthserv }
Try {
Add-Type -AssemblyName System.Runtime.WindowsRuntime
$asTaskGeneric = ([System.WindowsRuntimeSystemExtensions].GetMethods() | Where-Object { $_.Name -eq 'AsTask' -and $_.GetParameters().Count -eq 1 -and $_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' })[0]
Function Await($WinRtTask, $ResultType) {
$asTask = $asTaskGeneric.MakeGenericMethod($ResultType)
$netTask = $asTask.Invoke($null, @($WinRtTask))
$netTask.Wait(-1) | Out-Null
$netTask.Result
}
[Windows.Devices.Radios.Radio,Windows.System.Devices,ContentType=WindowsRuntime] | Out-Null
[Windows.Devices.Radios.RadioAccessStatus,Windows.System.Devices,ContentType=WindowsRuntime] | Out-Null
Await ([Windows.Devices.Radios.Radio]::RequestAccessAsync()) ([Windows.Devices.Radios.RadioAccessStatus]) | Out-Null
$radios = Await ([Windows.Devices.Radios.Radio]::GetRadiosAsync()) ([System.Collections.Generic.IReadOnlyList[Windows.Devices.Radios.Radio]])
If ($radios) {
$bluetooth = $radios | Where-Object { $_.Kind -eq 'Bluetooth' }
}
If ($bluetooth) {
[Windows.Devices.Radios.RadioState,Windows.System.Devices,ContentType=WindowsRuntime] | Out-Null
Await ($bluetooth.SetStateAsync($BluetoothStatus)) ([Windows.Devices.Radios.RadioAccessStatus]) | Out-Null
}
} Catch {
Write-Warning "Set-BluetoothStatus function errored."
}
}
Function Get-InternetUrl {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, Position = 0)]
[uri]$Url,
[Parameter(Mandatory = $true, Position = 1)]
[string]$searchstring
)
Begin {
## Get the name of this function and write header
[string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
Write-Verbose "${CmdletName}: Starting ${CmdletName} with the following parameters: $PSBoundParameters"
}
Process {
Try {
Write-Verbose -message "${CmdletName}: Now extracting download URL from '$Url'."
$HTML = Invoke-WebRequest -Uri $Url -UseBasicParsing
$Links = $HTML.Links
$ahref = $null
$ahref=@()
$ahref = ($Links | Where-Object {$_.href -like "*$searchstring*"}).href
If ($ahref.count -eq 0 -or $null -eq $ahref) {
$ahref = ($Links | Where-Object {$_.OuterHTML -like "*$searchstring*"}).href
}
If ($ahref.Count -eq 1) {
Write-Verbose -Message "${CmdletName}: Download URL = '$ahref'"
$ahref
}
Elseif ($ahref.Count -gt 1) {
Write-Verbose -Message "${CmdletName}: Download URL = '$($ahref[0])'"
$ahref[0]
}
}
Catch {
Write-Error "${CmdletName}: Error Downloading HTML and determining link for download."
}
}
End {
Write-Verbose -Message "${CmdletName}: Ending ${CmdletName}"
}
}
Function Get-InternetFile {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, Position = 0)]
[uri]$Url,
[Parameter(Mandatory = $true, Position = 1)]
[string]$OutputDirectory,
[Parameter(Mandatory = $false, Position = 2)]
[string]$OutputFileName
)
Begin {
$ProgressPreference = 'SilentlyContinue'
## Get the name of this function and write header
[string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
Write-Verbose "Starting ${CmdletName} with the following parameters: $PSBoundParameters"
}
Process {
$start_time = Get-Date
If (!$OutputFileName) {
Write-Verbose "${CmdletName}: No OutputFileName specified. Trying to get file name from URL."
If ((split-path -path $Url -leaf).Contains('.')) {
$OutputFileName = split-path -path $url -leaf
Write-Verbose "${CmdletName}: Url contains file name - '$OutputFileName'."
}
Else {
Write-Verbose "${CmdletName}: Url does not contain file name. Trying 'Location' Response Header."
$request = [System.Net.WebRequest]::Create($url)
$request.AllowAutoRedirect=$false
$response=$request.GetResponse()
$Location = $response.GetResponseHeader("Location")
If ($Location) {
$OutputFileName = [System.IO.Path]::GetFileName($Location)
Write-Verbose "${CmdletName}: File Name from 'Location' Response Header is '$OutputFileName'."
}
Else {
Write-Verbose "${CmdletName}: No 'Location' Response Header returned. Trying 'Content-Disposition' Response Header."
$result = Invoke-WebRequest -Method GET -Uri $Url -UseBasicParsing
$contentDisposition = $result.Headers.'Content-Disposition'
If ($contentDisposition) {
$OutputFileName = $contentDisposition.Split("=")[1].Replace("`"","")
Write-Verbose "${CmdletName}: File Name from 'Content-Disposition' Response Header is '$OutputFileName'."
}
}
}
}
If ($OutputFileName) {
$wc = New-Object System.Net.WebClient
$OutputFile = Join-Path $OutputDirectory $OutputFileName
Write-Verbose "${CmdletName}: Downloading file at '$url' to '$OutputFile'."
Try {
$wc.DownloadFile($url, $OutputFile)
$time = (Get-Date).Subtract($start_time).Seconds
Write-Verbose "${CmdletName}: Time taken: '$time' seconds."
if (Test-Path -Path $outputfile) {
$totalSize = (Get-Item $outputfile).Length / 1MB
Write-Verbose "${CmdletName}: Download was successful. Final file size: '$totalsize' mb"
Return $OutputFile
}
}
Catch {
Write-Error "${CmdletName}: Error downloading file. Please check url."
Return $Null
}
}
Else {
Write-Error "${CmdletName}: No OutputFileName specified. Unable to download file."
Return $Null
}
}
End {
Write-Verbose "Ending ${CmdletName}"
}
}
#endregion
#region Main
#Download LGPO and copy it to System32
If (!(Test-Path -Path "$env:SystemRoot\System32\LGPO.exe")) {
$urlLGPO = 'https://download.microsoft.com/download/8/5/C/85C25433-A1B0-4FFA-9429-7E023E7DA8D8/LGPO.zip'
$outputDir = "$env:Temp\LGPO"
$fileLGPODownload = Get-InternetFile -Url $urlLGPO -OutputDirectory $env:Temp
$outputDir = "$env:Temp\LGPO"
Expand-Archive -Path $fileLGPODownload -DestinationPath $outputDir
Remove-Item $fileLGPODownload -Force
$fileLGPO = (Get-ChildItem -Path $outputDir -file -Filter 'lgpo.exe' -Recurse)[0].FullName
Write-Output "Copying `"$fileLGPO`" to System32"
Copy-Item -Path $fileLGPO -Destination "$env:SystemRoot\System32" -Force
Remove-Item -Path $outputDir -Recurse -Force
}
#Download the STIG GPOs
$uriSTIGs = 'https://public.cyber.mil/stigs/gpo'
$uriGPODownload = Get-InternetUrl -Url $uriSTIGs -searchstring 'GPOs'
Write-Output "Downloading STIG GPOs from `"$uriGPODownload`"."
If ($uriGPODownload) {
$file = Get-InternetFile -url $uriGPODownload -OutputDirectory $env:TEMP
}
$OutputDir = "$env:Temp\GPOs"
If (Test-Path -Path $OutputDir) {
Remove-Item $OutputDir -Recurse -Force
}
Expand-Archive -Path $file -DestinationPath $outputDir
Remove-Item -Path $file -Force
Write-Output "Copying ADMX and ADML files to local system."
$null = Get-ChildItem -Path "$outputDir\ADMX Templates\Microsoft" -File -Recurse -Filter '*.admx' | ForEach-Object { Copy-Item -Path $_.FullName -Destination "$env:WINDIR\PolicyDefinitions\" -Force }
$null = Get-ChildItem -Path "$outputDir\ADMX Templates\Microsoft" -Directory -Recurse | Where-Object {$_.Name -eq 'en-us'} | Get-ChildItem -File -recurse -filter '*.adml' | ForEach-Object { Copy-Item -Path $_.FullName -Destination "$env:WINDIR\PolicyDefinitions\en-us\" -Force }
Write-Output "Getting List of Applicable GPO folders."
$arrApplicableGPOs = Get-ChildItem -Path $outputDir | Where-Object {$_.Name -like 'DoD*Windows 10*' -or $_.Name -like 'DoD*Edge*' -or $_.Name -like 'DoD*Firewall*' -or $_.Name -like 'DoD*Internet Explorer*' -or $_.Name -like 'DoD*Defender Antivirus*'}
[array]$arrGPOFolders = $null
ForEach ($folder in $arrApplicableGPOs.FullName) {
$gpoFolderPath = (Get-ChildItem -Path $folder -Filter 'GPOs' -Directory).FullName
$arrGPOFolders += $gpoFolderPath
}
ForEach ($gpoFolder in $arrGPOFolders) {
Write-Output "Running 'LGPO.exe /g `"$gpoFolder`"'"
$lgpo = Start-Process -FilePath "$env:SystemRoot\System32\lgpo.exe" -ArgumentList "/g `"$gpoFolder`"" -Wait -PassThru
Write-Output "'lgpo.exe' exited with code [$($lgpo.ExitCode)]."
}
#Disable Windows PowerShell V2
Write-Output "V-220728: Disabling the PowerShell V2."
If ((Get-WindowsOptionalFeature -Online | Where-Object {$_.FeatureName -eq 'MicrosoftWindowsPowerShellV2Root'}).State -eq 'Enabled') {
Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root
}
#Disable Secondary Logon Service
Write-Output "V-220732: Disabling the Secondary Logon Service."
$Service = 'SecLogon'
$Serviceobject = Get-Service | Where-Object {$_.Name -eq $Service}
If ($Serviceobject) {
$StartType = $ServiceObject.StartType
If ($StartType -ne 'Disabled') {
start-process -FilePath "reg.exe" -ArgumentList "ADD HKLM\System\CurrentControlSet\Services\SecLogon /v Start /d 4 /T REG_DWORD /f" -PassThru -Wait
}
If ($ServiceObject.Status -ne 'Stopped') {
Try {
Stop-Service $Service -Force
}
Catch {
}
}
}
<# Enables DEP. If there are bitlocker encrypted volumes, bitlocker is temporarily suspended for this operation
Configure DEP to at least OptOut
V-220726 Windows 10
V-253283 Windows 11
#>
Write-Output "V-220726: Checking to see if DEP is enabled."
$nxOutput = BCDEdit /enum '{current}' | Select-string nx
if (-not($nxOutput -match "OptOut" -or $nxOutput -match "AlwaysOn")) {
Write-Output "DEP is not enabled. Enabling."
# Determines bitlocker encrypted volumes
$encryptedVolumes = (Get-BitLockerVolume | Where-Object {$_.ProtectionStatus -eq 'On'}).MountPoint
if ($encryptedVolumes.Count -gt 0) {
Write-Log -EventId 1 -Message "Encrypted Drive Found. Suspending encryption temporarily."
foreach ($volume in $encryptedVolumes) {
Suspend-BitLocker -MountPoint $volume -RebootCount 0
}
Start-Process -Wait -FilePath 'C:\Windows\System32\bcdedit.exe' -ArgumentList '/set "{current}" nx OptOut'
foreach ($volume in $encryptedVolumes) {
Resume-BitLocker -MountPoint $volume
Write-Output "Resumed Protection."
}
}
else {
Start-Process -Wait -FilePath 'C:\Windows\System32\bcdedit.exe' -ArgumentList '/set "{current}" nx OptOut'
}
} Else {
Write-Output "DEP is already enabled."
}
# V-220734 Bluetooth
Write-Output 'V-220734: Disabling Bluetooth Radios.'
Set-BluetoothRadioStatus -BluetoothStatus Off
Write-Output "Configuring Registry Keys that aren't policy objects."
# WN10-CC-000039
Reg.exe ADD "HKLM\SOFTWARE\Classes\batfile\shell\runasuser" -v SuppressionPolicy -d 4096 -t REG_DWORD -f
Reg.exe ADD "HKLM\SOFTWARE\Classes\cmdfile\shell\runasuser" -v SuppressionPolicy -d 4096 -t REG_DWORD -f
Reg.exe ADD "HKLM\SOFTWARE\Classes\exefile\shell\runasuser" -v SuppressionPolicy -d 4096 -t REG_DWORD -f
Reg.exe ADD "HKLM\SOFTWARE\Classes\mscfile\shell\runasuser" -v SuppressionPolicy -d 4096 -t REG_DWORD -f
# CVE-2013-3900
Write-Output "CVE-2013-3900: Mitigating PE Installation risks."
Reg.exe ADD "HKLM\SOFTWARE\Wow6432Node\Microsoft\Cryptography\Wintrust\Config" -v EnableCertPaddingCheck -d 1 -t REG_DWORD -f
Reg.exe ADD "HKLM\SOFTWARE\Microsoft\Cryptography\Wintrust\Config" -v EnableCertPaddingCheck -d 1 -t REG_DWORD -f
Remove-Item -Path $OutputDir -Recurse -Force
Stop-Transcript