using namespace Microsoft.Web.Administration using namespace Microsoft.Windows.ServerManager.Commands Import-Module IISAdministration -Force $RootPath = Split-Path $MyInvocation.MyCommand.Path -Parent $RootPath = Split-Path $RootPath -Parent . "$RootPath\Helpers\AuditGroupFunctions.ps1" $listOfWeakCipherSuites = getListOfWeakCipherSuites $listOfInsecureCipherSuites = getListOfInsecureCipherSuites #region Helper Functions $MESSAGE_ALLGOOD = "All Good" function Get-IISSiteVirtualPaths { param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Site] $Site, [switch] $AllVirtualDirectories ) process { foreach ($App in $Site.Applications) { Write-Output ($App.Path) if ($AllVirtualDirectories) { foreach ($VirtualDirectory in $App.VirtualDirectories) { if ($VirtualDirectory.Path -ne "/") { $AppPath = if ($App.Path -ne "/") { $App.Path } else { "" } Write-Output ($AppPath + $VirtualDirectory.Path) } } } } } } function Get-IISModules { (Get-IISConfigSection -SectionPath "system.webServer/modules").GetCollection() ` | Get-IISConfigAttributeValue -AttributeName "Name" } #endregion #region 1 Basic Configuration # # This section contains basic Web server-level recommendations # 1.1 function Test-IISVirtualDirPartition { <# .Synopsis Ensure web content is on non-system partition .Description Web resources published through IIS are mapped, via Virtual Directories, to physical locations on disk. It is recommended to map all Virtual Directories to a non-system disk volume. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Site] $Site ) process { $message = $MESSAGE_ALLGOOD $audit = "True" $SystemDrive = [system.environment]::getenvironmentvariable("SystemDrive") $Path = $Site.Applications["/"].VirtualDirectories["/"].PhysicalPath if ($Path.StartsWith("%SystemDrive%") -or $Path.StartsWith($SystemDrive)) { $message = "Web content is on system partition" $audit = "False" } @{ Id = "1.1" Task = "Ensure web content is on non-system partition" Status = $audit Message = $message } | Write-Output } } # 1.2 function Test-IISHostHeaders { <# .Synopsis Ensure 'host headers' are on all sites .DESCRIPTION Host headers provide the ability to host multiple websites on the same IP address and port. It is recommended that host headers be configured for all sites. Wildcard host headers are now supported. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Site] $Site ) process { $message = $MESSAGE_ALLGOOD $audit = "True" [array]$Bindings = $Site.Bindings | Where-Object { [string]::IsNullOrEmpty($_.Host) } if ($Bindings.Count -gt 0) { $message = "The following bindings do no specify a host: " + ($Bindings.bindingInformation -join ", ") $audit = "False" } @{ Id = "1.2" Task = "Ensure 'host headers' is set" Status = $audit Message = $message } | Write-Output } } # 1.3 function Test-IISDirectoryBrowsing { <# .Synopsis Ensure 'directory browsing' is set to disabled .Description Directory browsing allows the contents of a directory to be displayed upon request from a web client. If directory browsing is enabled for a directory in Internet Information Services, users receive a page that lists the contents of the directory when the following two conditions are met: 1. No specific file is requested in the URL 2. The Default Documents feature is disabled in IIS, or if it is enabled, IIS is unable to locate a file in the directory that matches a name specified in the IIS default document list It is recommended that directory browsing be disabled. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { $message = $MESSAGE_ALLGOOD $audit = "True" #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup){ # Ensure directory browsing is installed if ((Get-WindowsFeature Web-Dir-Browsing).InstallState -eq [InstallState]::Installed) { $path = "system.webServer/directoryBrowse" $section = $Configuration.GetSection($path) $Enabled = $section | Get-IISConfigAttributeValue -AttributeName "enabled" if ($Enabled -eq $true) { $message = "Directory Browsing is enabled" $audit = "False" } elseif ($null -eq $Enabled) { $message = "Directory Browsing not explicit set to false" $audit = "Warning" } } } else{ $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "1.3" Task = "Ensure 'directory browsing' is set to disabled" Status = $audit Message = $message } | Write-Output } } # 1.4 function Test-IISAppPoolIdentity { <# .Synopsis Ensure 'application pool identity' is configured for all application pools .Description Application Pool Identities are the actual users/authorities that will run the worker process - w3wp.exe. Assigning the correct user authority will help ensure that applications can function properly, while not giving overly permissive permissions on the system. These identities can further be used in ACLs to protect system content. It is recommended that each Application Pool run under a unique identity. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ApplicationPool] $AppPool ) begin { $AppPoolUsers = (Get-IISAppPool).ProcessModel.Username | Group-Object -NoElement } process { $message = $MESSAGE_ALLGOOD $audit = "True" if ($AppPool.ProcessModel.IdentityType -eq [ProcessModelIdentityType]::SpecificUser) { # Get the username of the specific application $Username = $AppPool.ProcessModel.UserName if (($AppPoolUsers | Where-Object Name -eq $Username).Count -gt 1) { $message = "ApplicationPoolIdentity $Username is used for more than one ApplicationPool" $audit = "False" } else { $message = "Unique ApplicationPoolIdentity $Username is used." $audit = "True" } } elseif ($AppPool.ProcessModel.IdentityType -ne [ProcessModelIdentityType]::ApplicationPoolIdentity) { $message = "ApplicationPoolIdentity is not set" $audit = "False" } @{ Id = "1.4" Task = "Ensure 'application pool identity' is configured" Status = $audit Message = $message } | Write-Output } } # 1.5 function Test-IISUniqueSiteAppPool { <# .Synopsis Ensure 'unique application pools' is set for sites .Description IIS introduced a new security feature called Application Pool Identities that allows Application Pools to be run under unique accounts without the need to create and manage local or domain accounts. It is recommended that all Sites run under unique, dedicated Application Pools. #> $message = $MESSAGE_ALLGOOD $audit = "True" $Apps = foreach ($Site in (Get-IISSite)) { foreach ($App in $Site.Applications) { New-Object -TypeName PSObject -Property @{ VirtualPath = $Site.name + $App.path ApplicationPoolName = $App.ApplicationPoolName } } } [array]$Findings = $Apps ` | Group-Object -Property ApplicationPoolName ` | Where-Object -Property Count -gt 1 if ($Findings.Count -gt 0) { $message = "Following sites do not have unique Application Pools: " + ($findings.Group.VirtualPath -join ", ") $audit = "False" } @{ Id = "1.5" Task = "Ensure 'unique application pools' is set for sites" Status = $audit Message = $message } | Write-Output } # 1.6 function Test-IISAnonymouseUserIdentity { <# .Synopsis Ensure 'application pool identity' is configured for anonymous user identity .Description To achieve isolation in IIS, application pools can be run as separate identities. IIS can be configured to automatically use the application pool identity if no anonymous user account is configured for a Web site. This can greatly reduce the number of accounts needed for Web sites and make management of the accounts easier. It is recommended the Application Pool Identity be set as the Anonymous User Identity. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup){ $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.webServer/security/authentication/anonymousAuthentication" $section = $Configuration.GetSection($path) $username = $section | Get-IISConfigAttributeValue -AttributeName "userName" if ($username -ne "") { $message = "Username is set to: $username" $audit = "False" } } else{ $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "1.6" Task = "Ensure 'application pool identity' is configured for anonymous user identity" Status = $audit Message = $message } | Write-Output } } #endregion #region 2 Configure Authentication and Authorization # # This section contains recommendations around the different layers of authentication in IIS. # 2.1 function Test-IISGlobalAuthorization { <# .Synopsis Ensure 'global authorization rule' is set to restrict access .Description IIS introduced URL Authorization, which allows the addition of Authorization rules to the actual URL, instead of the underlying file system resource, as a way to protect it. Authorization rules can be configured at the server, web site, folder (including Virtual Directories), or file level. The native URL Authorization module applies to all requests, whether they are .NET managed or other types of files (e.g. static files or ASP files). It is recommended that URL Authorization be configured to only grant access to the necessary security principals. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" # Ensure URL Authentication is installed if ((Get-WindowsFeature Web-Url-Auth).InstallState -eq [InstallState]::Installed) { $path = "system.webServer/security/authorization" $section = $Configuration.GetSection($path) [array]$elements = $section.GetCollection() ` | Where-Object { $accessType = $_ | Get-IISConfigAttributeValue -AttributeName "accessType" $users = $_ | Get-IISConfigAttributeValue -AttributeName "users" $roles = $_ | Get-IISConfigAttributeValue -AttributeName "roles" ($accessType -eq "Allow") -and ($users -eq "*" -or $roles -eq "?") } if ($elements.Count -ne 0) { $message = "Authorization rule to allow all or anonymous users is set" $audit = "False" } } else { $message = "URL Authorization is not installed" $audit = "Warning" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "2.1" Task = "Ensure 'global authorization rule' is set to restrict access" Status = $audit Message = $message } | Write-Output } } # 2.2 function Test-IISAuthenticatedPricipals { <# .Synopsis Ensure access to sensitive site features is restricted to authenticated principals only .Description IIS supports both challenge-based and login redirection-based authentication methods. Challenge-based authentication methods, such as Integrated Windows Authentication, require a client to respond correctly to a server-initiated challenge. A login redirection-based authentication method such as Forms Authentication relies on redirection to a login page to determine the identity of the principal. Challenge-based authentication and login redirection-based authentication methods cannot be used in conjunction with one another. It is recommended that sites containing sensitive information, confidential data, or non-public web services be configured with a credentials-based authentication mechanism. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.web/authentication" $section = $Configuration.GetSection($path) $mode = $section | Get-IISConfigAttributeValue -AttributeName "mode" if (($mode -ne "Windows") -and ($mode -ne "Forms")) { $message = "Check authentication principals" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "2.2" Task = "Ensure access to sensitive site features is restricted to authenticated principals only" Status = $audit Message = $message } | Write-Output } } # 2.3 function Test-IISFormsAuthenticationSSL { <# .Synopsis Ensure 'forms authentication' require SSL .Description Forms-based authentication can pass credentials across the network in clear text. It is therefore imperative that the traffic between client and server be encrypted using SSL, especially in cases where the site is publicly accessible. It is recommended that communications with any portion of a site using Forms Authentication be encrypted using SSL. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.web/authentication" $section = $Configuration.GetSection($path) $mode = $section | Get-IISConfigAttributeValue -AttributeName "mode" if ((Get-IISModules) -contains "FormsAuthentication") { # Ensure authentication mode is set to Forms if ($mode -eq "Forms") { $requireSSL = $section ` | Get-IISConfigElement -ChildElementName "forms" ` | Get-IISConfigAttributeValue -AttributeName "requireSSL" if (-not $requireSSL) { $message = "Forms authentication does not require SSL" $audit = "False" } } } else { $message = "Forms authentication is not installed" $audit = "Warning" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "2.3" Task = "Ensure 'forms authentication' require SSL" Status = $audit Message = $message } | Write-Output } } # 2.4 function Test-IISFormsAuthenticationCookies { <# .Synopsis Ensure 'forms authentication' is set to use cookies .Description Forms Authentication can be configured to maintain the site visitor's session identifier in either a URI or cookie. It is recommended that Forms Authentication be set to use cookies. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.web/authentication" $section = $Configuration.GetSection($path) $mode = $section | Get-IISConfigAttributeValue -AttributeName "mode" if ((Get-IISModules) -contains "FormsAuthentication") { if ($mode -eq "Forms") { $cookieless = $section | Get-IISConfigElement -ChildElementName "forms" ` | Get-IISConfigAttributeValue -AttributeName "cookieless" if ($cookieless -ne "UseCookies") { $message = "Forms authentication is not set to use cookies" $audit = "False" } } } else { $message = "Forms authentication is not installed" $audit = "Warning" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "2.4" Task = "Ensure 'forms authentication' is set to use cookies" Status = $audit Message = $message } | Write-Output } } # 2.5 function Test-IISFormsAuthenticationProtection { <# .Synopsis Ensure 'cookie protection mode' is configured for forms authentication .Description The cookie protection mode defines the protection Forms Authentication cookies will be given within a configured application. It is recommended that cookie protection mode always encrypt and validate Forms Authentication cookies. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.web/authentication" $section = $Configuration.GetSection($path) $mode = $section | Get-IISConfigAttributeValue -AttributeName "mode" if ((Get-IISModules) -contains "FormsAuthentication") { if ($mode -ieq "Forms") { $protection = $section ` | Get-IISConfigElement -ChildElementName "forms" ` | Get-IISConfigAttributeValue -AttributeName "protection" if ($protection -ne "All") { $message = "Cookie Protection Mode is not set to ALL" $audit = "False" } } } else { $message = "Forms authentication is not installed" $audit = "Warning" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "2.5" Task = "Ensure 'cookie protection mode' is configured for forms authentication" Status = $audit Message = $message } | Write-Output } } # 2.6 function Test-IISTLSForBasicAuth { <# .Synopsis Ensure transport layer security for 'basic authentication' is configured .Description Basic Authentication can pass credentials across the network in clear text. It is therefore imperative that the traffic between client and server be encrypted, especially in cases where the site is publicly accessible and is recommended that TLS be configured and required for any Site or Application using Basic Authentication. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Site] $Site ) process { $message = $MESSAGE_ALLGOOD $audit = "True" if ((Get-WindowsFeature Web-Basic-Auth).InstallState -eq [InstallState]::Installed) { [array]$httpsBindings = $Site.Bindings | Where-Object -Property Protocol -eq "https" $sslFlags = Get-IISConfigSection -Location $Site.Name ` -SectionPath "system.webServer/security/access" ` | Get-IISConfigAttributeValue -AttributeName "sslFlags" # split the flags into an array $sslValues = $sslFlags.Split("{,}") # Ensure ssl-flag is set if (-not ($sslValues -contains "ssl")) { $message = "SSL is not required in configuration" $audit = "False" } # Ensure site has https bindings elseif ($httpsBindings.Count -eq 0) { $message = "Site has no secure protocol binding" $audit = "False" } } @{ Id = "2.6" Task = "Ensure transport layer security for 'basic authentication' is configured" Status = $audit Message = $message } | Write-Output } } # 2.7 function Test-IISPasswordFormatNotClear { <# .Synopsis Ensure 'passwordFormat' is not set to clear .Description The element of the element allows optional definitions of name and password for IIS Manager User accounts within the configuration file. Forms based authentication also uses these elements to define the users. IIS Manager Users can use the administration interface to connect to sites and applications in which they've been granted authorization. Note that the element only applies when the default provider, ConfigurationAuthenticationProvider, is configured as the authentication provider. It is recommended that passwordFormat be set to a value other than Clear, such as SHA1. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.web/authentication" $section = $Configuration.GetSection($path) $passwordFormat = $section ` | Get-IISConfigElement -ChildElementName "forms" ` | Get-IISConfigElement -ChildElementName "credentials" ` | Get-IISConfigAttributeValue -AttributeName "passwordFormat" if ($passwordFormat -eq "Clear" ) { $message = "Credentials passwordFormat set to 'Clear'" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "2.7" Task = "Ensure 'passwordFormat' is not set to clear" Status = $audit Message = $message } | Write-Output } } # 2.7 function Test-IISPasswordFormatNotClearMachineLevel { <# .Synopsis Ensure 'passwordFormat' is not set to clear .Description The element of the element allows optional definitions of name and password for IIS Manager User accounts within the configuration file. Forms based authentication also uses these elements to define the users. IIS Manager Users can use the administration interface to connect to sites and applications in which they've been granted authorization. Note that the element only applies when the default provider, ConfigurationAuthenticationProvider, is configured as the authentication provider. It is recommended that passwordFormat be set to a value other than Clear, such as SHA1. #> $message = $MESSAGE_ALLGOOD $audit = "True" $machineConfig = [System.Configuration.ConfigurationManager]::OpenMachineConfiguration() $passwordFormat = $machineConfig.GetSection("system.web/authentication").forms.credentials.passwordFormat if ($passwordFormat -eq "Clear" ) { $message = "Credentials passwordFormat set to 'Clear'" $audit = "False" } @{ Id = "2.7" Task = "Ensure 'passwordFormat' is not set to clear" Status = $audit Message = $message } | Write-Output } # 2.8 function Test-IISCredentialsNotStored { <# .Synopsis Ensure 'credentials' are not stored in configuration files .Description The element of the element allows optional definitions of name and password for IIS Manager User accounts within the configuration file. Forms based authentication also uses these elements to define the users. IIS Manager Users can use the administration interface to connect to sites and applications in which they've been granted authorization. Note that the element only applies when the default provider, ConfigurationAuthenticationProvider, is configured as the authentication provider. It is recommended to avoid storing passwords in the configuration file even in form of hash. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.web/authentication" $section = $Configuration.GetSection($path) $credentials = $section ` | Get-IISConfigElement -ChildElementName "forms" ` | Get-IISConfigElement -ChildElementName "credentials" if ($credentials.IsLocallyStored) { $message = "'credentials' is stored in configuration" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "2.8" Task = "Ensure 'credentials' are not stored in configuration files" Status = $audit Message = $message } | Write-Output } } # 2.8 function Test-IISCredentialsNotStoredMachineLevel { <# .Synopsis Ensure 'credentials' are not stored in configuration files .Description The element of the element allows optional definitions of name and password for IIS Manager User accounts within the configuration file. Forms based authentication also uses these elements to define the users. IIS Manager Users can use the administration interface to connect to sites and applications in which they've been granted authorization. Note that the element only applies when the default provider, ConfigurationAuthenticationProvider, is configured as the authentication provider. It is recommended to avoid storing passwords in the configuration file even in form of hash. #> $message = $MESSAGE_ALLGOOD $audit = "True" $machineConfig = [System.Configuration.ConfigurationManager]::OpenMachineConfiguration() $credentials = $machineConfig.GetSection("system.web/authentication").forms.credentials if ($credentials.ElementInformation.IsPresent) { $message = "'credentials' is stored in configuration" $audit = "False" } @{ Id = "2.8" Task = "Ensure 'credentials' are not stored in configuration files" Status = $audit Message = $message } | Write-Output } #endregion #region 3 ASP.NET Configuration Recommendation # # This section contains recommendations specific to ASP.NET. # 3.1 function Test-IISDeploymentMethodRetail { <# .Synopsis Ensure 'deployment method retail' is set .Description The switch is intended for use by production IIS servers. This switch is used to help applications run with the best possible performance and least possible security information leakages by disabling the application's ability to generate trace output on a page, disabling the ability to display detailed error messages to end users, and disabling the debug switch. Often times, switches and options that are developer-focused, such as failed request tracing and debugging, are enabled during active development. It is recommended that the deployment method on any production server be set to retail. #> $message = $MESSAGE_ALLGOOD $audit = "True" $machineConfig = [System.Configuration.ConfigurationManager]::OpenMachineConfiguration() $deployment = $machineConfig.GetSection("system.web/deployment") if (-not $deployment.retail) { $message = "retail is not enabled in machine.config" $audit = "False" } @{ Id = "3.1" Task = "Ensure 'deployment method retail' is set" Status = $audit Message = $message } | Write-Output } # 3.2 function Test-IISDebugOff { <# .Synopsis Ensure 'debug' is turned off .Description Developers often enable the debug mode during active ASP.NET development so that they do not have to continually clear their browsers cache every time they make a change to a resource handler. The problem would arise from this being left "on" or set to "true". Compilation debug output is displayed to the end user, allowing malicious persons to obtain detailed information about applications. is recommended that debugging still be turned off. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.web/compilation" $section = $Configuration.GetSection($path) $debug = $section | Get-IISConfigAttributeValue -AttributeName "debug" if ($debug) { $message = "Debug is ON" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "3.2" Task = "Ensure 'debug' is turned off" Status = $audit Message = $message } | Write-Output } } # 3.3 function Test-IISCustomErrorsNotOff { <# .Synopsis Ensure custom error messages are not off .Description When an ASP.NET application fails and causes an HTTP/1.x 500 Internal Server Error, or a feature configuration (such as Request Filtering) prevents a page from being displayed, an error message will be generated. Administrators can choose whether or not the application should display a friendly message to the client, detailed error message to the client, or detailed error message to localhost only. It is recommended that customErrors still be turned to On or RemoteOnly. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.web/customErrors" $section = $Configuration.GetSection($path) $mode = $section | Get-IISConfigAttributeValue -AttributeName "mode" if ($mode -eq "Off") { $message = "Custom errors are 'OFF'" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "3.3" Task = "Ensure custom error messages are not off" Status = $audit Message = $message } | Write-Output } } # 3.4 function Test-IISHttpErrorsHidden { <# .Synopsis Ensure IIS HTTP detailed errors are hidden from displaying remotely .Description A Web site's error pages are often set to show detailed error information for troubleshooting purposes during testing or initial deployment. To prevent unauthorized users from viewing this privileged information, detailed error pages must not be seen by remote users. This setting can be modified in the errorMode attribute setting for a Web site's error pages. By default, the errorMode attribute is set in the Web.config file for the Web site or application and is located in the element of the section. It is recommended that custom errors be prevented from displaying remotely. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.webServer/httpErrors" $section = $Configuration.GetSection($path) $errorMode = $section | Get-IISConfigAttributeValue -AttributeName "errorMode" if (($errorMode -ne "Custom") -and ($errorMode -ne "DetailedLocalOnly")) { $message = "HTTP detailed errors are set to 'Detailed'" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "3.4" Task = "Ensure IIS HTTP detailed errors are hidden from displaying remotely" Status = $audit Message = $message } | Write-Output } } # 3.5 function Test-IISAspNetTracingDisabled { <# .Synopsis Ensure ASP.NET stack tracing is not enabled .Description A Web site's error pages are often set to show detailed error information for troubleshooting purposes during testing or initial deployment. To prevent unauthorized users from viewing this privileged information, detailed error pages must not be seen by remote users. This setting can be modified in the errorMode attribute setting for a Web site's error pages. By default, the errorMode attribute is set in the Web.config file for the Web site or application and is located in the element of the section. It is recommended that custom errors be prevented from displaying remotely. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.web/trace" $section = $Configuration.GetSection($path) $traceEnabled = $section | Get-IISConfigAttributeValue -AttributeName "enabled" if ($traceEnabled) { $message = "trace is enabled" $audit = "FALSE" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "3.5" Task = "Ensure ASP.NET stack tracing is not enabled" Status = $audit Message = $message } | Write-Output } } # 3.5 function Test-IISAspNetTracingDisabledMachineLevel { <# .Synopsis Ensure ASP.NET stack tracing is not enabled .Description A Web site's error pages are often set to show detailed error information for troubleshooting purposes during testing or initial deployment. To prevent unauthorized users from viewing this privileged information, detailed error pages must not be seen by remote users. This setting can be modified in the errorMode attribute setting for a Web site's error pages. By default, the errorMode attribute is set in the Web.config file for the Web site or application and is located in the element of the section. It is recommended that custom errors be prevented from displaying remotely. #> $message = $MESSAGE_ALLGOOD $audit = "True" $machineConfig = [System.Configuration.ConfigurationManager]::OpenMachineConfiguration() $trace = $machineConfig.GetSection("system.web/trace") if ($trace.enabled) { $message = "trace is enabled in machine.config" $audit = "FALSE" } @{ Id = "3.5" Task = "Ensure ASP.NET stack tracing is not enabled" Status = $audit Message = $message } | Write-Output } # 3.6 function Test-IISCookielessSessionState { <# .Synopsis Ensure 'httpcookie' mode is configured for session state .Description A session cookie associates session information with client information for that session, which can be the duration of a user's connection to a site. The cookie is passed in a HTTP header together with all requests between the client and server. It is recommended that session state be configured to UseCookies. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.web/sessionState" $section = $Configuration.GetSection($path) $cookieless = $section | Get-IISConfigAttributeValue -AttributeName "cookieless" if (($cookieless -ne "UseCookies") -and ($cookieless -ne "False")) { $message = "sessionState set to $cookieless" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "3.6" Task = "Ensure 'httpcookie' mode is configured for session state" Status = $audit Message = $message } | Write-Output } } # 3.7 function Test-IISCookiesHttpOnly { <# .Synopsis Ensure 'cookies' are set with HttpOnly attribute .Description The httpOnlyCookies attribute of the httpCookies node determines if IIS will set the HttpOnly flag on HTTP cookies it sets. The HttpOnly flag indicates to the user agent that the cookie must not be accessible by client-side script (i.e document.cookie). It is recommended that the httpOnlyCookies attribute be set to true. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.web/httpCookies" $section = $Configuration.GetSection($path) $httpOnlyCookies = $section | Get-IISConfigAttributeValue -AttributeName "httpOnlyCookies" if (-not $httpOnlyCookies) { $message = "httpOnlyCookies set to $httpOnlyCookies" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "3.7" Task = "Ensure 'cookies' are set with HttpOnly attribute" Status = $audit Message = $message } | Write-Output } } # 3.8 function Test-IISMachineKeyValidation { <# .Synopsis Ensure 'MachineKey validation method - .Net 3.5' is configured .Description The machineKey element of the ASP.NET web.config specifies the algorithm and keys that ASP.NET will use for encryption. The Machine Key feature can be managed to specify hashing and encryption settings for application services such as view state, Forms authentication, membership and roles, and anonymous identification. It is recommended that AES or SHA1 methods be configured for use at the global level. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Site] $Site ) process { $message = $MESSAGE_ALLGOOD $audit = "True" $siteAppPool = $Site.Applications["/"].ApplicationPoolName $appPoolVersion = (Get-IISAppPool -Name $siteAppPool).managedRuntimeVersion # Ensure ApplicationPool running is .NET 3.5 (which is an extension of 2.0 so we look for 2.*) if ($appPoolVersion -like "v2.*") { $validation = Get-IISConfigSection -CommitPath $Site.Name ` -SectionPath "system.web/machineKey" ` | Get-IISConfigAttributeValue -AttributeName "Validation" if ($validation -ne "SHA1") { $message = "Validation set to $validation" $audit = "False" } } @{ Id = "3.8" Task = "Ensure 'MachineKey validation method - .Net 3.5' is configured" Status = $audit Message = $message } | Write-Output } } # 3.9 function Test-IISMachineKeyValidationV45 { <# .Synopsis Ensure 'MachineKey validation method - .Net 4.5' is configured .Description The machineKey element of the ASP.NET web.config specifies the algorithm and keys that ASP.NET will use for encryption. The Machine Key feature can be managed to specify hashing and encryption settings for application services such as view state, Forms authentication, membership and roles, and anonymous identification. It is recommended that SHA-2 methods be configured for use at the global level. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Site] $Site ) process { $message = $MESSAGE_ALLGOOD $audit = "True" $siteAppPool = $site.Applications["/"].ApplicationPoolName $appPoolVersion = (Get-IISAppPool -Name $siteAppPool).managedRuntimeVersion # Ensure an ApplicationPool is running .NET 4.5 if ($appPoolVersion -like "v4.*") { $validation = Get-IISConfigSection -CommitPath $Site.name ` -SectionPath "system.web/machineKey" ` | Get-IISConfigAttributeValue -AttributeName "Validation" if (($validation -ne "HMACSHA256") -and ($validation -ne "HMACSHA512")) { $message = "Validation set to $validation" $audit = "False" } } @{ Id = "3.9" Task = "Ensure 'MachineKey validation method - .Net 4.5' is configured" Status = $audit Message = $message } | Write-Output } } # 3.10 function Test-IISDotNetTrustLevel { <# .Synopsis Ensure global .NET trust level is configured .Description An application's trust level determines the permissions that are granted by the ASP.NET code access security (CAS) policy. CAS defines two trust categories: full trust and partial trust. An application that has full trust permissions may access all resource types on a server and perform privileged operations, while applications that run with partial trust have varying levels of operating permissions and access to resources. It is recommended that the global .NET Trust Level be set to Medium or lower. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Site] $Site ) process { $message = $MESSAGE_ALLGOOD $audit = "True" $siteAppPool = $site.Applications["/"].ApplicationPoolName $appPoolVersion = (Get-IISAppPool -Name $siteAppPool).managedRuntimeVersion if ($appPoolVersion -like "v4.*") { $message = "This only applies to .Net 2.0. Future versions have stopped supporting this feature." $audit = "None" } else { $level = Get-IISConfigSection -CommitPath $Site.name ` -SectionPath "system.web/trust" ` | Get-IISConfigAttributeValue -AttributeName "level" # medium trust level should be set in .NET 2.*, but not in later versions if (($appPoolVersion -like "v2.*" -and $level -ne "medium" -or $level -ne "low" -or $level -ne "minimal") ` -or ($appPoolVersion -notlike "v4.*" -and -not [string]::IsNullOrEmpty($appPoolVersion))) { $message = "TrustLevel set to $level" $audit = "False" } } @{ Id = "3.10" Task = "Ensure global .NET trust level is configured" Status = $audit Message = $message } | Write-Output } } #endregion #region 4 Request Filtering and Other Restriction Modules # # Request Filtering is a powerful module that provides a configurable set of rules that enables administrators to allow or reject the types of requests that they determine should be allowed or rejected at the server, web site, or web application levels. # 4.1 function Test-IISMaxAllowedContentLength { <# .Synopsis Ensure 'maxAllowedContentLength' is configured .Description The maxAllowedContentLength Request Filter is the maximum size of the http request, measured in bytes, which can be sent from a client to the server. Configuring this value enables the total request size to be restricted to a configured value. It is recommended that the overall size of requests be restricted to a maximum value appropriate for the server, site, or application. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" # Ensure request filering is installed if ((Get-WindowsFeature Web-Filtering).InstallState -eq [InstallState]::Installed) { $path = "system.webServer/security/requestFiltering" $section = $Configuration.GetSection($path) $maxContentLength = $section ` | Get-IISConfigElement -ChildElementName "requestLimits" ` | Get-IISConfigAttributeValue -AttributeName "maxAllowedContentLength" if ($maxContentLength -ge 0) { $message += "`n maxContentLength: $maxContentLength" } else { $message = "maxContentLength not configured" $audit = "False" } } else { $message = "Request Filering is not installed" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "4.1" Task = "Ensure 'maxAllowedContentLength' is configured" Status = $audit Message = $message } | Write-Output } } # 4.2 function Test-IISMaxURLRequestFilter { <# .Synopsis Ensure 'maxURL request filter' is configured .Description The maxURL attribute of the property is the maximum length (in Bytes) in which a requested URL can be (excluding query string) in order for IIS to accept. Configuring this Request Filter enables administrators to restrict the length of the requests that the server will accept. It is recommended that a limit be put on the length of URL. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" # Ensure request filering is installed if ((Get-WindowsFeature Web-Filtering).InstallState -eq [InstallState]::Installed) { $path = "system.webServer/security/requestFiltering" $section = $Configuration.GetSection($path) $maxURLRequestFilter = $section ` | Get-IISConfigElement -ChildElementName "requestLimits" ` | Get-IISConfigAttributeValue -AttributeName "maxURL" if ($maxURLRequestFilter -ge 1) { $message += "`n maxURLRequestFilter: $maxURLRequestFilter" } else { $message = "maxURLRequestFilter not configured" $audit = "False" } } else { $message = "Request Filering is not installed" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "4.2" Task = "Ensure 'maxURL request filter' is configured" Status = $audit Message = $message } | Write-Output } } # 4.3 function Test-IISMaxQueryStringRequestFilter { <# .Synopsis Ensure 'MaxQueryString request filter' is configured .Description The MaxQueryString Request Filter describes the upper limit on the length of the query string that the configured IIS server will allow for websites or applications. It is recommended that values always be established to limit the amount of data will can be accepted in the query string. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" # Ensure request filering is installed if ((Get-WindowsFeature Web-Filtering).InstallState -eq [InstallState]::Installed) { $path = "system.webServer/security/requestFiltering" $section = $Configuration.GetSection($path) $maxQueryStringRequestFilter = $section ` | Get-IISConfigElement -ChildElementName "requestLimits" ` | Get-IISConfigAttributeValue -AttributeName "maxQueryString" if ($maxQueryStringRequestFilter -ge 1) { $message += "`n maxQueryStringRequestFilter: $maxQueryStringRequestFilter" } else { $message = "maxQueryStringRequestFilter not configured" $audit = "False" } } else { $message = "Request Filering is not installed" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "4.3" Task = "Ensure 'MaxQueryString request filter' is configured" Status = $audit Message = $message } | Write-Output } } # 4.4 function Test-IISNonASCIICharURLForbidden { <# .Synopsis Ensure non-ASCII characters in URLs are not allowed .Description This feature is used to allow or reject all requests to IIS that contain non-ASCII characters. When using this feature, Request Filtering will deny the request if high-bit characters are present in the URL. The UrlScan equivalent is AllowHighBitCharacters. It is recommended that requests containing non-ASCII characters be rejected, where possible. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" # Ensure request filering is installed if ((Get-WindowsFeature Web-Filtering).InstallState -eq [InstallState]::Installed) { $path = "system.webServer/security/requestFiltering" $section = $Configuration.GetSection($path) $allowHighBitCharacters = $section ` | Get-IISConfigAttributeValue -AttributeName "allowHighBitCharacters" if ($allowHighBitCharacters) { $message = "non-ASCII characters in URLs are allowed" $audit = "False" } } else { $message = "Request Filering is not installed" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "4.4" Task = "Ensure non-ASCII characters in URLs are not allowed" Status = $audit Message = $message } | Write-Output } } # 4.5 function Test-IISRejectDoubleEncodedRequests { <# .Synopsis Ensure Double-Encoded requests will be rejected .Description This Request Filter feature prevents attacks that rely on double-encoded requests and applies if an attacker submits a double-encoded request to IIS. When the double-encoded requests filter is enabled, IIS will go through a two iteration process of normalizing the request. If the first normalization differs from the second, the request is rejected and the error code is logged as a 404.11. The double-encoded requests filter was the VerifyNormalization option in UrlScan. It is recommended that double-encoded requests be rejected. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" # Ensure request filering is installed if ((Get-WindowsFeature Web-Filtering).InstallState -eq [InstallState]::Installed) { $path = "system.webServer/security/requestFiltering" $section = $Configuration.GetSection($path) $allowDoubleEscaping = $section` | Get-IISConfigAttributeValue -AttributeName "allowDoubleEscaping" if ($allowDoubleEscaping) { $message = "Rejecting Double-Encoded requests not set" $audit = "False" } } else { $message = "Request Filering is not installed" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "4.5" Task = "Ensure Double-Encoded requests will be rejected" Status = $audit Message = $message } | Write-Output } } # 4.6 function Test-IISHTTPTraceMethodeDisabled { <# .Synopsis Ensure 'HTTP Trace Method' is disabled .Description The HTTP TRACE method returns the contents of client HTTP requests in the entity-body of the TRACE response. Attackers could leverage this behavior to access sensitive information, such as authentication data or cookies, contained in the HTTP headers of the request. One such way to mitigate this is by using the element of the collection. The element replaces the [AllowVerbs] and [DenyVerbs] features in UrlScan. It is recommended the HTTP TRACE method be denied. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = "HTTP Trace Method is not filtered" $audit = "False" # Ensure request filering is installed if ((Get-WindowsFeature Web-Filtering).InstallState -eq [InstallState]::Installed) { $path = "system.webServer/security/requestFiltering" $section = $Configuration.GetSection($path) [array]$httpTraceMethod = $section.GetCollection("verbs") ` | Where-Object { $trace = $_ | Get-IISConfigAttributeValue -AttributeName "verb" $allowed = $_ | Get-IISConfigAttributeValue -AttributeName "allowed" ($trace -eq "trace") -and (-not $allowed) } if ($httpTraceMethod.Count -eq 1) { $message = $MESSAGE_ALLGOOD $audit = "True" } } else { $message = "Request Filering is not installed" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "4.6" Task = "Ensure 'HTTP Trace Method' is disabled" Status = $audit Message = $message } | Write-Output } } # 4.7 function Test-IISBlockUnlistedFileExtensions { <# .Synopsis Ensure Unlisted File Extensions are not allowed .Description The FileExtensions Request Filter allows administrators to define specific extensions their web server(s) will allow and disallow. The property allowUnlisted will cover all other file extensions not explicitly allowed or denied. Often times, extensions such as .config, .bat, .exe, to name a few, should never be served. The AllowExtensions and DenyExtensions options are the UrlScan equivalents. It is recommended that all extensions be unallowed at the most global level possible, with only those necessary being allowed. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" if ((Get-WindowsFeature Web-Filtering).InstallState -eq [InstallState]::Installed) { $path = "system.webServer/security/requestFiltering" $section = $Configuration.GetSection($path) $allowUnlisted = $section ` | Get-IISConfigElement -ChildElementName "fileExtensions" ` | Get-IISConfigAttributeValue -AttributeName "allowUnlisted" if ($allowUnlisted) { $message = "Unlisted file extensions allowed" $audit = "False" } } else { $message = "Request Filering is not installed" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "4.7" Task = "Ensure Unlisted File Extensions are not allowed" Status = $audit Message = $message } | Write-Output } } # 4.8 function Test-IISHandlerDenyWrite { <# .Synopsis Ensure Handler is not granted Write and Script/Execute .Description Handler mappings can be configured to give permissions to Read, Write, Script, or Execute depending on what the use is for - reading static content, uploading files, executing scripts, etc. It is recommended to grant a handler either Execute/``Script or Write permissions, but not both. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = $MESSAGE_ALLGOOD $audit = "True" $path = "system.webServer/handlers" $section = $Configuration.GetSection($path) $accessPolicy = ($section | Get-IISConfigAttributeValue -AttributeName "accessPolicy").Split(",") if ((($accessPolicy -contains "Script") -or ($accessPolicy -contains "Execute")) ` -and ($accessPolicy -contains "Write")) { $message = "Handler is granted write and script/execute" $audit = "False" } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "4.8" Task = "Ensure Handler is not granted Write and Script/Execute" Status = $audit Message = $message } | Write-Output } } # 4.9 function Test-IISIsapisNotAllowed { <# .Synopsis Ensure 'notListedIsapisAllowed' is set to false .Description The notListedIsapisAllowed attribute is a server-level setting that is located in the ApplicationHost.config file in the element of the section under . This element ensures that malicious users cannot copy unauthorized ISAPI binaries to the Web server and then run them. It is recommended that notListedIsapisAllowed be set to false. #> $message = $MESSAGE_ALLGOOD $audit = "True" try { $isapiCgiRestriction = Get-IISConfigSection ` -SectionPath "system.webServer/security/isapiCgiRestriction" ` | Get-IISConfigAttributeValue -AttributeName "notListedIsapisAllowed" # Verify that the notListedIsapisAllowed attribute in the element is set to false if ($isapiCgiRestriction) { $message = "IsapiCgiRestriction 'notListedIsapisAllowed' not set to false" $audit = "False" } } catch { $message = "Cannot get setting 'notListedIsapisAllowed' for IsapiCgiRestriction" $audit = "False" } @{ Id = "4.9" Task = "Ensure 'notListedIsapisAllowed' is set to false" Status = $audit Message = $message } | Write-Output } # 4.10 function Test-IISCgisNotAllowed { <# .Synopsis Ensure 'notListedCgisAllowed' is set to false .Description The notListedCgisAllowed attribute is a server-level setting that is located in the ApplicationHost.config file in the element of the section under . This element ensures that malicious users cannot copy unauthorized CGI binaries to the Web server and then run them. It is recommended that notListedCgisAllowed be set to false. #> $message = $MESSAGE_ALLGOOD $audit = "True" try { $isapiCgiRestriction = Get-IISConfigSection ` -SectionPath "system.webServer/security/isapiCgiRestriction" ` | Get-IISConfigAttributeValue -AttributeName "notListedCgisAllowed" # Verify that the notListedCgisAllowed attribute in the element is set to false if ($isapiCgiRestriction) { $message = "IsapiCgiRestriction 'notListedCgisAllowed' not set to false" $audit = "False" } } catch { $message = "Cannot get setting 'notListedCgisAllowed' for IsapiCgiRestriction" $audit = "False" } @{ Id = "4.10" Task = "Ensure 'notListedCgisAllowed' is set to false" Status = $audit Message = $message } | Write-Output } # 4.11 function Test-IISDynamicIPRestrictionEnabled { <# .Synopsis Ensure 'Dynamic IP Address Restrictions' is enabled .Description IIS Dynamic IP Address Restrictions capability can be used to thwart DDos attacks. This is complimentary to the IP Addresses and Domain names Restrictions lists that can be manually maintained within IIS. In contrast, Dynamic IP address filtering allows administrators to configure the server to block access for IPs that exceed the specified request threshold. The default action Deny action for restrictions is to return a Forbidden response to the client. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Site] $Site ) process { $message = $MESSAGE_ALLGOOD $audit = "True" # Ensure the windows feature is installed if ((Get-WindowsFeature Web-Ip-Security).InstallState -ne [InstallState]::Installed) { $message = "`"IP and Domain Restrictions`" must be installed to enabled `"Dynamic IP Address Restrictions`"" $audit = "False" } else { $dynamicIpSecurity = Get-IISConfigSection -Location $Site.Name ` -SectionPath "system.webServer/security/dynamicIpSecurity" $denyByConcurrentRequests = $dynamicIpSecurity ` | Get-IISConfigElement -ChildElementName "denyByConcurrentRequests" ` | Get-IISConfigAttributeValue -AttributeName "enabled" $denyByRequestRate = $dynamicIpSecurity ` | Get-IISConfigElement -ChildElementName "denyByRequestRate" ` | Get-IISConfigAttributeValue -AttributeName "enabled" if ($denyByConcurrentRequests -and -not $denyByRequestRate) { $message = "Deny IP Address based on the number of requests over a period of time disabled" $audit = "False" } elseif (-not $denyByConcurrentRequests -and $denyByRequestRate) { $message = "Deny IP Address based on the number of concurrent requests disabled" $audit = "False" } elseif (-not $denyByConcurrentRequests -and -not $denyByRequestRate) { $message = "Dynamic IP Restriction disabled" $audit = "False" } } @{ Id = "4.11" Task = "Ensure 'Dynamic IP Address Restrictions' is enabled" Status = $audit Message = $message } | Write-Output } } #endregion #region 5 IIS Logging Recommendations # # This section contains recommendations regarding IIS logging that have not been covered in the Basic Configurations section. # 5.1 function Test-IISLogFileLocation { <# .Synopsis Ensure Default IIS web log location is moved .Description IIS will log relatively detailed information on every request. These logs are usually the first item looked at in a security response, and can be the most valuable. Malicious users are aware of this, and will often try to remove evidence of their activities. It is therefore recommended that the default location for IIS log files be changed to a restricted, non-system drive. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Site] $Site ) process { $logFileLocation = ($Site.logFile.Directory).replace("%SystemDrive%", $env:SystemDrive) $message = $MESSAGE_ALLGOOD $audit = "True" if ($logFileLocation.StartsWith($env:SystemDrive)) { $message = "Logfile location is on system drive: $logFileLocation" $audit = "False" } @{ Id = "5.1" Task = "Ensure Default IIS web log location is moved" Status = $audit Message = $message } | Write-Output } } # 5.2 function Test-IISAdvancedLoggingEnabled { <# .Synopsis Ensure Advanced IIS logging is enabled .Description IIS Advanced Logging is a module which provides flexibility in logging requests and client data. It provides controls that allow businesses to specify what fields are important, easily add additional fields, and provide policies pertaining to log file rollover and Request Filtering. HTTP request/response headers, server variables, and client-side fields can be easily logged with minor configuration in the IIS management console. It is recommended that Advanced Logging be enabled, and the fields which could be of value to the type of business or application in the event of a security incident, be identified and logged. #> # check site defaults @{ Id = "5.2" Task = "Ensure Advanced IIS logging is enabled" Status = "None" Message = "Advanced Logging is not available for IIS 10. See enhanced logging instead." } | Write-Output } # 5.3 function Test-IISETWLoggingEnabled { <# .Synopsis Ensure 'ETW Logging' is enabled .Description IIS introduces a new logging method. Administrators can now send logging information to Event Tracing for Windows (ETW) #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Site] $Site ) process { $message = $MESSAGE_ALLGOOD $audit = "True" if (-not ($Site.logFile.logTargetW3C -like "*ETW*")) { $message = "ETW Logging disabled" $audit = "False" } @{ Id = "5.3" Task = "Ensure 'ETW Logging' is enabled" Status = $audit Message = $message } | Write-Output } } #endregion #region 6 FTP Requests # # This section contains a crucial configuration setting for running file transfer protocol (FTP). # 6.1 function Test-IISFtpRequestsEncrypted { <# .Synopsis Ensure FTP requests are encrypted .Description The new FTP Publishing Service for IIS supports adding an SSL certificate to an FTP site. Using an SSL certificate with an FTP site is also known as FTP-S or FTP over Secure Socket Layers (SSL). FTP-S is an RFC standard (RFC 4217) where an SSL certificate is added to an FTP site and thereby making it possible to perform secure file transfers. #> $message = $MESSAGE_ALLGOOD $audit = "True" if ((Get-WindowsFeature Web-Ftp-Server).InstallState -eq [InstallState]::Installed) { try { $sslConfigElement = Get-IISConfigSection ` -SectionPath "system.applicationHost/sites" ` | Get-IISConfigElement -ChildElementName "siteDefaults" ` | Get-IISConfigElement -ChildElementName "ftpServer" ` | Get-IISConfigElement -ChildElementName "security" ` | Get-IISConfigElement -ChildElementName "ssl" $controlChannelPolicy = $sslConfigElement ` | Get-IISConfigAttributeValue -AttributeName "controlChannelPolicy" $dataChannelPolicy = $sslConfigElement ` | Get-IISConfigAttributeValue -AttributeName "dataChannelPolicy" if (($controlChannelPolicy -ne "SslRequire") -or ($dataChannelPolicy -ne "SslRequire")) { $message = "Found following settings: `n controlChannelPolicy: $controlChannelPolicy `n dataChannelPolicy: $dataChannelPolicy" $audit = "False" } } catch { $message = "Cannot get FTP security setting" $audit = "False" } } else { $message = "Skipped this benchmark - right now Web-Ftp-Server is not installed" $audit = "None" } @{ Id = "6.1" Task = "Ensure FTP requests are encrypted" Status = $audit Message = $message } | Write-Output } # 6.2 function Test-IISFtpLogonAttemptRestriction { <# .Synopsis Ensure FTP Logon attempt restrictions is enabled .Description IIS introduced a built-in network security feature to automatically block brute force FTP attacks. This can be used to mitigate a malicious client from attempting a brute-force attack on a discovered account, such as the local administrator account. #> $message = $MESSAGE_ALLGOOD $audit = "True" if ((Get-WindowsFeature Web-Ftp-Server).InstallState -eq [InstallState]::Installed) { try { $denyByFailure = Get-IISConfigSection ` -SectionPath "system.ftpServer/security/authentication" ` | Get-IISConfigElement -ChildElementName "denyByFailure" $enabled = $denyByFailure ` | Get-IISConfigAttributeValue -AttributeName "enabled" $maxFailure = $denyByFailure ` | Get-IISConfigAttributeValue -AttributeName "maxFailure" $entryExpiration = $denyByFailure ` | Get-IISConfigAttributeValue -AttributeName "entryExpiration" $loggingOnlyMode = $denyByFailure ` | Get-IISConfigAttributeValue -AttributeName "loggingOnlyMode" if (($enabled) -and ($maxFailure -gt 0) -and ($entryExpiration -gt 0) -and (-not $loggingOnlyMode)) { # All good } elseif (-not $enabled ) { $message = "Feature disabled" $audit = "False" } else { $message = "Feature enabled, but check settings. Found: `n maxFailure: " ` + $maxFailure + "`n entryExpiration: " ` + $entryExpiration + "`n Only logging mode: " ` + $loggingOnlyMode $audit = "False" } } catch { $audit = "False" $message = "Cannot get FTP Logon attempt settings" } } else { $message = "Skipped this benchmark - right now Web-Ftp-Server is not installed" $audit = "None" } @{ Id = "6.2" Task = "Ensure FTP Logon attempt restrictions is enabled" Status = $audit Message = $message } | Write-Output } #endregion #region 7 Transport Encryption # # This section contains recommendations for configuring IIS protocols and cipher suites. # 7.1 function Test-IISHSTSHeaderSet { <# .Synopsis Ensure HSTS Header is set .Description HTTP Strict Transport Security (HSTS) allows a site to inform the user agent to communicate with the site only over HTTPS. This header takes two parameters: max-age, "specifies the number of seconds, after the reception of the STS header field, during which the user agent regards the host (from whom the message was received) as a Known HSTS Host [speaks only HTTPS]"; and includeSubDomains. includeSubDomains is an optional directive that defines how this policy is applied to subdomains. If includeSubDomains is included in the header, it provides the following definition: this HSTS Policy also applies to any hosts whose domain names are subdomains of the Known HSTS Host's domain name. #> param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { #Ensure $Configuration is not empty if ($Configuration.RootSectionGroup) { $message = "HSTS Header not set" $audit = "False" $path = "system.webServer/httpProtocol" $section = $Configuration.GetSection($path) [array]$customHeaders = $section.GetCollection("customHeaders") ` | Where-Object { $name = $_ | Get-IISConfigAttributeValue -AttributeName "name" $name -eq "Strict-Transport-Security" } if ($customHeaders.Count -eq 1) { $value = $customHeaders[0] | Get-IISConfigAttributeValue -AttributeName "value" $pattern = [regex]::new("max-age=(?[0-9]*)") $match = $pattern.Match($value) if ($match.Success) { [int]$maxAge = $match.Groups["maxage"].Value if ($maxAge -eq 0) { $message = "Max-age should be at least be higher than 0. It is recommended to set max-age to at least 480 seconds. Max-age is set at $maxAge" $audit = "False" } elseif ($maxAge -lt 480) { $message = "It is recommended to set max-age to at least 480 seconds. Max-age is set at $maxAge" $audit = "Warning" } else { $message = $MESSAGE_ALLGOOD + ". Max-age is set at $maxAge" $audit = "True" } } } } else { $message = "Cannot read configuration file, the reference to the directory may not be correct or present" $audit = "Warning" } @{ Id = "7.1" Task = "Ensure HSTS Header is set" Status = $audit Message = $message } | Write-Output } } # 7.2 function Test-IISSSL2Disabled { <# .Synopsis Ensure SSLv2 is disabled .Description This protocol is not considered cryptographically secure. Disabling it is recommended. This protocol is disabled by default if the registry key is not present. A reboot is required for these changes to be reflected. #> $message = $MESSAGE_ALLGOOD $audit = "True" $path = "HKLM:\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0" # SSL is disabled by default # if $path exists, $path/server should also exist if ((Test-Path $path) -and (Test-Path "$path\Server")) { # Ensure the following key exists $Key = Get-Item "$path\Server" if ($null -ne $Key.GetValue("Enabled", $null)) { $value = Get-ItemProperty "$path\Server" | Select-Object -ExpandProperty "Enabled" # Ensure it is set to 0 if ($value -ne 0) { $message = "SSL 2.0 is enabled" $audit = "False" } } } @{ Id = "7.2" Task = "Ensure SSLv2 is disabled" Status = $audit Message = $message } | Write-Output } # 7.3 function Test-IISSSL3Disabled { <# .Synopsis Ensure SSLv3 is disabled .Description This protocol is not considered cryptographically secure. Disabling it is recommended. #> $message = $MESSAGE_ALLGOOD $audit = "True" $path = "HKLM:\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0" # SSL is disabled by default # if $path exists, $path/server should also exist if ((Test-Path $path) -and (Test-Path "$path\Server")) { # Ensure the following key exists $Key = Get-Item "$path\Server" if ($null -ne $Key.GetValue("Enabled", $null)) { $value = Get-ItemProperty "$path\Server" | Select-Object -ExpandProperty "Enabled" # Ensure it is set to 0 if ($value -ne 0) { $message = "SSL 3.0 is enabled" $audit = "False" } } } @{ Id = "7.3" Task = "Ensure SSLv3 is disabled" Status = $audit Message = $message } | Write-Output } # 7.4 function Test-IISTLSDisabled { <# .Synopsis Ensure TLS 1.0 is disabled .Description The PCI Data Security Standard 3.1 recommends disabling "early TLS" along with SSL: SSL and early TLS are not considered strong cryptography and cannot be used as a security control after June 30, 2016. #> $message = "TLS 1.0 is enabled" $audit = "False" $path = "HKLM:\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server" # TLS 1.0 is enabled by default if (Test-Path $path) { # Ensure the following key exists $Key = Get-Item $path if ($null -ne $Key.GetValue("Enabled", $null)) { $value = Get-ItemProperty $path | Select-Object -ExpandProperty "Enabled" # Ensure it is set to 0 if ($value -eq 0) { $message = $MESSAGE_ALLGOOD $audit = "True" } } elseif ($null -ne $Key.GetValue("DisabledByDefault", $null)) { $value = Get-ItemProperty $path | Select-Object -ExpandProperty "DisabledByDefault" # Ensure it is set to 1 if ($value -eq 1) { $message = $MESSAGE_ALLGOOD $audit = "True" } } } @{ Id = "7.4" Task = "Ensure TLS 1.0 is disabled" Status = $audit Message = $message } | Write-Output } # 7.5 function Test-IISTLS1_1Disabled { <# .Synopsis Ensure TLS 1.1 is disabled .Description TLS 1.1 is required for backward compatibility. Ensure you fully test your application to ensure that backwards compatibility is not needed. If it is, build in exceptions as necessary for backwards compatibility. #> $message = "TLS 1.1 is enabled" $audit = "False" $path = "HKLM:\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server" # TLS is enabled by default if (Test-Path $path) { # Ensure the following key exists $Key = Get-Item $path if ($null -ne $Key.GetValue("Enabled", $null)) { $value = Get-ItemProperty $path | Select-Object -ExpandProperty "Enabled" # Ensure it is set to 0 if ($value -eq 0) { $message = $MESSAGE_ALLGOOD $audit = "True" } } elseif ($null -ne $Key.GetValue("DisabledByDefault", $null)) { $value = Get-ItemProperty $path | Select-Object -ExpandProperty "DisabledByDefault" # Ensure it is set to 1 if ($value -eq 1) { $message = $MESSAGE_ALLGOOD $audit = "True" } } } @{ Id = "7.5" Task = "Ensure TLS 1.1 is disabled" Status = $audit Message = $message } | Write-Output } # 7.6 function Test-IISTLS1_2Enabled { <# .Synopsis Ensure TLS 1.2 is enabled .Description TLS 1.2 is the most recent and mature protocol for protecting the confidentiality and integrity of HTTP traffic. Enabling TLS 1.2 is recommended. This protocol is enabled by default if the registry key is not present. As with any registry changes, a reboot is required for changes to take effect. #> $message = $MESSAGE_ALLGOOD $audit = "True" $path = "HKLM:\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2" # if $path exists, $path/server should also exist # TLS 1.2 is enabled by default if ((Test-Path $path) -and (Test-Path "$path\Server")) { # Ensure the following key exists $Key = Get-Item "$path\Server" if ($null -ne $Key.GetValue("Enabled", $null)) { $value = Get-ItemProperty "$path\Server" | Select-Object -ExpandProperty "Enabled" if ($value -ne 1) { $message = "TLS 1.2 is disabled" $audit = "False" } } else { $message = "TLS 1.2 is disabled" $audit = "False" } if ($null -ne $Key.GetValue("DisabledByDefault", $null)) { # Get-ItemProperty returns a [UInt32] $value = Get-ItemProperty "$path\Server" | Select-Object -ExpandProperty "DisabledByDefault" if ($value -ne 0) { $message = "TLS 1.2 is disabled by default" $audit = "False" } } else { $message = "TLS 1.2 is disabled" $audit = "False" } } @{ Id = "7.6" Task = "Ensure TLS 1.2 is enabled" Status = $audit Message = $message } | Write-Output } # 7.7 function Test-IISNullCipherDisabled { <# .Synopsis Ensure NULL Cipher Suites is disabled .Description The NULL cipher does not provide data confidentiality or integrity. It is recommended that the NULL cipher be disabled. #> $message = "NULL cipher is enabled" $audit = "False" $path = "HKLM:\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\NULL\" if (Test-Path $path) { $Key = Get-Item $path if ($null -ne $Key.GetValue("Enabled", $null)) { $value = Get-ItemProperty $path | Select-Object -ExpandProperty "Enabled" if ($value -eq 0) { $message = $MESSAGE_ALLGOOD $audit = "True" } } } else { $message = $MESSAGE_ALLGOOD $audit = "True" } @{ Id = "7.7" Task = "Ensure NULL Cipher Suites is disabled" Status = $audit Message = $message } | Write-Output } # 7.8 function Test-IISDESCipherDisabled { <# .Synopsis Ensure DES Cipher Suites is disabled .Description DES is a weak symmetric-key cipher. It is recommended that it be disabled. #> $message = "DES cipher is enabled" $audit = "False" $path = "HKLM:\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\DES 56/56\" if (Test-Path $path) { $Key = Get-Item $path if ($null -ne $Key.GetValue("Enabled", $null)) { $value = Get-ItemProperty $path | Select-Object -ExpandProperty "Enabled" if ($value -eq 0) { $message = $MESSAGE_ALLGOOD $audit = "True" } } } else { $message = $MESSAGE_ALLGOOD $audit = "True" } @{ Id = "7.8" Task = "Ensure DES Cipher Suites is disabled" Status = $audit Message = $message } | Write-Output } # 7.9 function Test-IISRC4CipherDisabled { <# .Synopsis Ensure RC4 Cipher Suites is disabled .Description RC4 is a stream cipher that has known practical attacks. It is recommended that RC4 be disabled. The only RC4 cipher enabled by default on Server 2012 and 2012 R2 is RC4 128/128. #> $rc4Ciphers = @("RC4 40/128", "RC4 56/128", "RC4 64/128", "RC4 128/128") $index = 1 foreach ($rc4Cipher in $rc4Ciphers) { $message = "$rc4Cipher cipher is enabled" $audit = "False" $path = "HKLM:\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\$rc4Cipher\" if (Test-Path $path) { $Key = Get-Item $path if ($null -ne $Key.GetValue("Enabled", $null)) { $value = Get-ItemProperty $path | Select-Object -ExpandProperty "Enabled" if ($value -eq 0) { $message = $MESSAGE_ALLGOOD $audit = "True" } } } else { $message = $MESSAGE_ALLGOOD $audit = "True" } @{ Id = "7.9.$index" Task = "Ensure RC4 Cipher Suites is disabled" Status = $audit Message = $message } | Write-Output $index++ } } # 7.10 function Test-IISAES128Disabled { <# .Synopsis Ensure AES 128/128 Cipher Suite is configured .Description Enabling AES 128/128 may be required for client compatibility. Enable or disable this cipher suite accordingly. #> $message = "AES 128/128 Cipher Suite is still enabled" $audit = "False" try { # Get-ItemProperty returns a [UInt32] $enabled = Get-ItemProperty "HKLM:\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\AES 128/128\" ` -ErrorAction Stop ` | Select-Object ` -ExpandProperty Enabled if ($enabled -eq 0) { $message = $MESSAGE_ALLGOOD $audit = "True" } } catch { # do anything here } # If the key/value is not present,Triple AES 128/128 Cipher is disabled @{ Id = "7.10" Task = "Ensure AES 128/128 Cipher Suite is disabled" Status = $audit Message = $message } | Write-Output } # 7.11 function Test-IISAES256Enabled { <# .Synopsis Ensure AES 256/256 Cipher Suite is enabled .Description AES 256/256 is the most recent and mature cipher suite for protecting the confidentiality and integrity of HTTP traffic. Enabling AES 256/256 is recommended. This is enabled by default on Server 2012 and 2012 R2. #> $message = "AES 256/256 Cipher is disabled" $audit = "False" $path = "HKLM:\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\AES 256/256\" if (Test-Path $path) { $Key = Get-Item $path if ($null -ne $Key.GetValue("Enabled", $null)) { $value = Get-ItemProperty $path | Select-Object -ExpandProperty "Enabled" if ($value -eq 1) { $message = $MESSAGE_ALLGOOD $audit = "True" } } } else { $message = $MESSAGE_ALLGOOD $audit = "True" } @{ Id = "7.11" Task = "Ensure AES 256/256 Cipher Suite is enabled" Status = $audit Message = $message } | Write-Output } # 7.12 function Test-IISTLSCipherOrder { <# .Synopsis Ensure TLS Cipher Suite ordering is configured .Description Cipher suites are a named combination of authentication, encryption, message authentication code, and key exchange algorithms used for the security settings of a network connection using TLS protocol. Clients send a cipher list and a list of ciphers that it supports in order of preference to a server. The server then replies with the cipher suite that it selects from the client cipher suite list. #> $task = "Ensure TLS Cipher Suite ordering is correctly configured" $id = "7.12" try { $regValue = Get-ItemProperty -ErrorAction Stop ` -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Cryptography\Configuration\SSL\00010002" ` -Name "Functions" $reference = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" $res = $regValue.Functions.GetType().Name #check if correct type $typeTable = @{ "String" = "String Value" "Byte" = "Byte Value" "Int32" = "DWORD (32-bit) Value" "Int64" = "QWORD (64-bit) Value" "String[]" = "Multi-String Value" } $currentType = $typeTable[$res] if ($res -ne [String]) { @{ Id = $id Task = $task Status = "False" Message = "Wrong Registry type! Registry type is '$currentType'. Expected: 'String Value'" } | Write-Output } #check if insecure or weak cipher is inside value $regValues = $regValue.Split(',') $regValues = $regValues -replace ' ', '' $weakRulesFound = @() $insecureRulesFound = @() foreach($element in $regValues){ if($listOfWeakCipherSuites.Contains($element)){ $weakRulesFound += $element } if($listOfInsecureCipherSuites.Contains($element)){ $insecureRulesFound += $element } } #Default status $status = "Error" #Output $verbInsecure = "rules have" $verbWeak = "rules have" if($insecureRulesFound.Count -eq 1){$verbInsecure = "rule has"} if($weakRulesFound.Count -eq 1){$verbWeak = "rule has"} $insecureMessage = "$($insecureRulesFound.Count) insecure $($verbInsecure) been found! List of insecure rules:
" $weakMessage = "$($weakRulesFound.Count) weak $($verbWeak) been found! List of weak rules:
" #Preparing message foreach($member in $weakRulesFound){ $status = "Warning" $weakMessage += "$($member)
" } foreach($member in $insecureRulesFound){ $status = "False" $insecureMessage += "$($member)
" } #Combine or shorten message if($insecureRulesFound.Count -gt 0 -or $weakRulesFound.Count -gt 0){ $message = "" if($weakRulesFound.Count -eq 0){ $weakMessage = "" } if($insecureRulesFound.Count -eq 0){ $insecureMessage = "" } $message = $insecureMessage + $weakMessage @{ Id = $id Task = $task Status = $status Message = $message } | Write-Output } if ($regValue -ne $reference) { @{ Id = $id Task = $task Status = "True" Message = "Compliant" } | Write-Output } } catch { $regValue = Get-ItemProperty -ErrorAction Stop ` -Path "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010002" ` -Name "Functions" $reference = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" $res = $regValue.Functions.GetType().Name #check if correct type $typeTable = @{ "String" = "String Value" "Byte" = "Byte Value" "Int32" = "DWORD (32-bit) Value" "Int64" = "QWORD (64-bit) Value" "String[]" = "Multi-String Value" } $currentType = $typeTable[$res] if ($res -ne [String[]]) { @{ Id = $id Task = $task Status = "False" Message = "Wrong Registry type! Registry type is '$currentType'. Expected: 'Multi-String Value'" } | Write-Output } #check if insecure or weak cipher is inside value $regValues = $regValue -replace ' ', '' $weakRulesFound = @() $insecureRulesFound = @() foreach($element in $regValues){ if($listOfWeakCipherSuites.Contains($element)){ $weakRulesFound += $element } if($listOfInsecureCipherSuites.Contains($element)){ $insecureRulesFound += $element } } #Default status $status = "Error" #Output $verbInsecure = "rules have" $verbWeak = "rules have" if($insecureRulesFound.Count -eq 1){$verbInsecure = "rule has"} if($weakRulesFound.Count -eq 1){$verbWeak = "rule has"} $insecureMessage = "$($insecureRulesFound.Count) insecure $($verbInsecure) been found! List of insecure rules:
" $weakMessage = "$($weakRulesFound.Count) weak $($verbWeak) been found! List of weak rules:
" #Preparing message foreach($member in $weakRulesFound){ $status = "Warning" $weakMessage += "$($member)
" } foreach($member in $insecureRulesFound){ $status = "False" $insecureMessage += "$($member)
" } #Combine or shorten message if($insecureRulesFound.Count -gt 0 -or $weakRulesFound.Count -gt 0){ $message = "" if($weakRulesFound.Count -eq 0){ $weakMessage = "" } if($insecureRulesFound.Count -eq 0){ $insecureMessage = "" } $message = $insecureMessage + $weakMessage @{ Id = $id Task = $task Status = $status Message = $message } | Write-Output } if ($regValue -ne $reference) { @{ Id = $id Task = $task Status = "True" Message = "Compliant" } | Write-Output } } @{ Id = $id Task = $task Status = "True" Message = "Compliant" } | Write-Output } #endregion #region Report Generation function Get-IIS10SystemReport { # Section 1 Test-IISUniqueSiteAppPool # Section 2 Test-IISPasswordFormatNotClearMachineLevel Test-IISCredentialsNotStoredMachineLevel # Section 3 Test-IISDeploymentMethodRetail Test-IISAspNetTracingDisabledMachineLevel # Section 4 Test-IISIsapisNotAllowed Test-IISCgisNotAllowed # Section 5 Test-IISAdvancedLoggingEnabled # Section 6 Test-IISFtpRequestsEncrypted Test-IISFtpLogonAttemptRestriction # Section 7 Test-IISSSL2Disabled Test-IISSSL3Disabled Test-IISTLSDisabled Test-IISTLS1_1Disabled Test-IISTLS1_2Enabled Test-IISNullCipherDisabled Test-IISDESCipherDisabled Test-IISRC4CipherDisabled Test-IISAES128Disabled Test-IISAES256Enabled Test-IISTLSCipherOrder } function Get-IIS10ApplicationHostReport { $Configuration = (Get-IISServerManager).GetApplicationHostConfiguration() # Section 1 $Configuration | Test-IISDirectoryBrowsing $Configuration | Test-IISAnonymouseUserIdentity # Section 2 $Configuration | Test-IISGlobalAuthorization $Configuration | Test-IISAuthenticatedPricipals $Configuration | Test-IISFormsAuthenticationSSL $Configuration | Test-IISFormsAuthenticationCookies $Configuration | Test-IISFormsAuthenticationProtection $Configuration | Test-IISPasswordFormatNotClear $Configuration | Test-IISCredentialsNotStored # Section 3 $Configuration | Test-IISDebugOff $Configuration | Test-IISCustomErrorsNotOff $Configuration | Test-IISHttpErrorsHidden $Configuration | Test-IISAspNetTracingDisabled $Configuration | Test-IISCookielessSessionState # Section 4 $Configuration | Test-IISMaxAllowedContentLength $Configuration | Test-IISMaxURLRequestFilter $Configuration | Test-IISMaxQueryStringRequestFilter $Configuration | Test-IISNonASCIICharURLForbidden $Configuration | Test-IISRejectDoubleEncodedRequests $Configuration | Test-IISHTTPTraceMethodeDisabled $Configuration | Test-IISBlockUnlistedFileExtensions $Configuration | Test-IISHandlerDenyWrite # Section 5 # Section 6 # Section 7 $Configuration | Test-IISHSTSHeaderSet } function Get-VirtualPathAudit { param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Configuration] $Configuration ) process { # Section 1 $Configuration | Test-IISDirectoryBrowsing $Configuration | Test-IISAnonymouseUserIdentity # Section 2 $Configuration | Test-IISGlobalAuthorization $Configuration | Test-IISAuthenticatedPricipals $Configuration | Test-IISFormsAuthenticationSSL $Configuration | Test-IISFormsAuthenticationCookies $Configuration | Test-IISFormsAuthenticationProtection $Configuration | Test-IISPasswordFormatNotClear $Configuration | Test-IISCredentialsNotStored # Section 3 $Configuration | Test-IISDebugOff $Configuration | Test-IISCustomErrorsNotOff $Configuration | Test-IISHttpErrorsHidden $Configuration | Test-IISAspNetTracingDisabled $Configuration | Test-IISCookielessSessionState $Configuration | Test-IISCookiesHttpOnly # Section 4 $Configuration | Test-IISMaxAllowedContentLength $Configuration | Test-IISMaxURLRequestFilter $Configuration | Test-IISMaxQueryStringRequestFilter $Configuration | Test-IISNonASCIICharURLForbidden $Configuration | Test-IISRejectDoubleEncodedRequests $Configuration | Test-IISHTTPTraceMethodeDisabled $Configuration | Test-IISBlockUnlistedFileExtensions $Configuration | Test-IISHandlerDenyWrite # Section 5 # Section 6 # Section 7 $Configuration | Test-IISHSTSHeaderSet } } function Get-SiteAudit { param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Site] $Site ) process { $AppPools = $Site.Applications.ApplicationPoolName | Sort-Object | Get-Unique | Get-IISAppPool # Section 1 $Site | Test-IISVirtualDirPartition $Site | Test-IISHostHeaders $AppPools | Test-IISAppPoolIdentity # Section 2 $Site | Test-IISTLSForBasicAuth # Section 3 $Site | Test-IISMachineKeyValidation $Site | Test-IISMachineKeyValidationV45 $Site | Test-IISDotNetTrustLevel # Section 4 $Site | Test-IISDynamicIPRestrictionEnabled # Section 5 $Site | Test-IISLogFileLocation $Site | Test-IISETWLoggingEnabled # Section 6 # Section 7 } } function Get-IISHostInformation { $infos = Get-CimInstance Win32_OperatingSystem $disk = Get-CimInstance Win32_LogicalDisk | Where-Object -Property DeviceID -eq "C:" $IISinstallPath = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\InetStp").Installpath return [ordered]@{ "Hostname" = [System.Net.Dns]::GetHostByName(($env:computerName)).HostName "Operating System" = $infos.Caption "Build Number" = $infos.BuildNumber "IIS Version" = (Get-ItemProperty -Path ("$IISinstallPath\w3wp.exe")).VersionInfo.ProductVersion "Free physical memory (GB)" = "{0:N3}" -f ($infos.FreePhysicalMemory / 1MB) "Free disk space (GB)" = "{0:N1}" -f ($disk.FreeSpace / 1GB) } } [Report] @{ Title = "IIS 10 Benchmarks" ModuleName = "ATAPAuditor" BasedOn = "CIS Microsoft IIS 10 Benchmark, Version: 1.1.0, Date: 12-11-2018" HostInformation = Get-IISHostInformation Sections = @( [ReportSection] @{ Title = "System Report" AuditInfos = Get-IIS10SystemReport } [ReportSection] @{ Title = "ApplicationHost" AuditInfos = Get-IIS10ApplicationHostReport } foreach ($Site in Get-IISSite) { $VirtualPaths = $Site | Get-IISSiteVirtualPaths -AllVirtualDirectories [ReportSection] @{ Title = "Full site report for: $($Site.Name)" AuditInfos = $Site | Get-SiteAudit SubSections = @( foreach ($VirtualPath in $VirtualPaths) { $Configuration = (Get-IISServerManager).GetWebConfiguration($Site.Name, $VirtualPath) [ReportSection]@{ Title = "Report for: $VirtualPath" AuditInfos = $Configuration | Get-VirtualPathAudit } } ) } } ) } #endregion