The_Exchange_Team, Nino_Bilic , perhaps I didn't recycle IIS after adding the alternateHostName, however, I thought IIS detected changes in web.config files and automatically recycled.
Regardless, for testing (because the internet is full of all sorts of references to Fiddler (which won't let you set an empty "Host" header even though you may try to use HTTP/1.0 (which is allowed)), to Postman, which I'm also having difficulty with.
AI-non-slop has produced the following working Powershell script (after repeated guidance):
<#
.SYNOPSIS
Sends an HTTP/1.0 GET request with an empty Host header over an SSL/TLS connection.
Useful for testing whether the server discloses internal IP addresses via HTTPS.
.DESCRIPTION
Test-EmptyHostHeaderTLS will open a TCP connection to a specified address
on port 443 (by default), initiate a TLS/SSL handshake, construct an HTTP/1.0
request with an empty Host header, and then print the response.
.PARAMETER Server
The hostname or IP address of the target server.
.PARAMETER Port
The TCP port of the target server. Defaults to 443.
.EXAMPLE
PS C:\> .\Test-EmptyHostHeaderTLS.ps1 -Server "mail.example.com" -Port 443
Sends a GET request to mail.example.com on port 443 with an empty Host header.
#>
param(
[Parameter(Mandatory=$true)]
[string]$Server,
[int]$Port = 443
)
Write-Host "Testing empty Host header over SSL/TLS..."
Write-Host "Server: $Server, Port: $Port"
# 1) Create a TCP client
try {
$tcpClient = New-Object System.Net.Sockets.TcpClient($Server, $Port)
} catch {
Write-Host "Could not connect to $Server on port $Port. Error: $($_.Exception.Message)"
return
}
# 2) Create an SslStream
# The anonymous callback here returns $true to skip certificate validation.
# In an actual scenario, you would handle proper certificate validation.
try {
$sslStream = New-Object System.Net.Security.SslStream($tcpClient.GetStream(), $false,
{ param($sender, $cert, $chain, $errors) return $true })
# 3) Authenticate the TLS/SSL session
# If you prefer to avoid sending SNI, you can pass an empty string or skip the server name.
# However, some servers won't respond properly without SNI matching their certificate.
# For testing, we'll keep $Server to match standard usage.
$sslStream.AuthenticateAsClient($Server)
}
catch {
Write-Host "Failed to establish TLS/SSL session. Error: $($_.Exception.Message)"
$tcpClient.Close()
return
}
# 4) Write the HTTP/1.0 request with an empty Host header
# We'll do this with a StreamWriter directly on the SslStream
try {
$writer = New-Object System.IO.StreamWriter($sslStream)
$writer.AutoFlush = $true
$request = "GET / HTTP/1.0`r`n" +
"Connection: close`r`n" +
"`r`n"
Write-Host "`nSending request:"
Write-Host $request
$writer.Write($request)
$writer.Flush()
}
catch {
Write-Host "Error sending request: $($_.Exception.Message)"
$sslStream.Close()
$tcpClient.Close()
return
}
# 5) Read the response
try {
$reader = New-Object System.IO.StreamReader($sslStream)
Write-Host "`nResponse Headers & Body:"
while( -not $reader.EndOfStream ) {
$line = $reader.ReadLine()
Write-Host $line
}
} catch {
Write-Host "Error reading response: $($_.Exception.Message)"
} finally {
# Clean up
$reader.Close()
$writer.Close()
$sslStream.Close()
$tcpClient.Close()
}
Write-Host "`nCompleted SSL/TLS empty Host header test."
When run WITHOUT the alternateHostName, my response from running this script is as follows:
Testing empty Host header over SSL/TLS...
Server: redacted.redacted.tld, Port: 443
Sending request:
GET / HTTP/1.0
Connection: close
Response Headers & Body:
HTTP/1.1 302 Moved Temporarily
Cache-Control: no-cache
Pragma: no-cache
Location: https://{internal IP disclosure}/owa/
Server: Microsoft-IIS/10.0
X-FEServer: {computer name}
X-RequestId: fad6e206-af99-4b62-b6d2-9b3d1c4640ba
Date: Tue, 18 Feb 2025 17:18:30 GMT
Connection: close
Content-Length: 0
Completed SSL/TLS empty Host header test.
If I set alternateHostName (AND RECYCLE IIS... 🤦♂️) response is as follows:
Testing empty Host header over SSL/TLS...
Server: redacted.redacted.tld, Port: 443
Sending request:
GET / HTTP/1.0
Connection: close
Response Headers & Body:
HTTP/1.1 302 Moved Temporarily
Cache-Control: no-cache
Pragma: no-cache
Location: https://redacted.redacted.tld/owa/
Server: Microsoft-IIS/10.0
X-FEServer: {computer name}
X-RequestId: fd065fd5-d5ac-4188-8b3d-9037f435127b
Date: Tue, 18 Feb 2025 17:21:58 GMT
Connection: close
Content-Length: 0
Completed SSL/TLS empty Host header test.
Therefore... this seems like a ME issue and not a Microsoft/Exchange issue...except for the clobbering of the original web.config file by the CU15 install (which clears the "alternateHostName") which I would personally consider a bug...
I think I'm good for now, I'll reply here again if the external vulnerability tests continue to fail. Otherwise, if anyone has a similar issue, Microsoft Exchange Client Access Server Information Disclosure | Tenable®:
"The Microsoft Exchange Client Access Server (CAS) is affected by an information disclosure vulnerability. A remote, unauthenticated attacker can exploit this vulnerability to learn the server's internal IP address.
An attacker can send a crafted GET request to the Web Server with an empty host header that would expose internal IP Addresses of the underlying system in the header response."
feel free to use the PowerShell script (Fiddler, Postman, HTTP traffic capture program X, etc. are not necessary to test the application of alternateHostName to system.webServer/serverRuntime in your main, inetpub\wwwroot web.config file.
Thank you.