1. Overview
A server-side request forgery (SSRF) vulnerability exists in Microsoft Exchange Server’s Exchange Web Services (EWS) InstallApp operation. When an authenticated user submits a ManifestUrl parameter via the InstallApp SOAP request, Exchange downloads the manifest from the supplied URL. The intranet address check that prevents SSRF is gated on the isBposUser flag, which is false for all on-premises Exchange deployments. This means the check is bypassed entirely in non-cloud environments, allowing an authenticated user to force the Exchange server to make HTTP requests to arbitrary internal or external URLs. Microsoft addressed this vulnerability in the June 2026 security update (KB5094139).
2. Vulnerability Type
| Field | Value |
|---|---|
| Primary CWE | CWE-918: Server-Side Request Forgery (SSRF) |
| Related CWE | CWE-863: Incorrect Authorization |
3. Severity
CVSS 3.1 (from Microsoft Advisory)
| Field | Value |
|---|---|
| Score | 5.0 (Medium) |
| Vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N |
Our Assessment (CVSS 4.0)
| Metric Group | Metric | Value |
|---|---|---|
| Base – Exploitability | Attack Vector (AV) | Network |
| Attack Complexity (AC) | Low | |
| Attack Requirements (AT) | None | |
| Privileges Required (PR) | Low | |
| User Interaction (UI) | None | |
| Base – Vulnerable System | Confidentiality (VC) | None |
| Integrity (VI) | None | |
| Availability (VA) | None | |
| Base – Subsequent System | Confidentiality (SC) | Low |
| Integrity (SI) | None | |
| Availability (SA) | None | |
| Threat | Exploit Maturity (E) | Proof-of-Concept |
| Field | Value |
|---|---|
| CVSS 4.0 Score | 2.3 (Low) |
| CVSS 4.0 Vector | CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:N/SC:L/SI:N/SA:N/E:P |
4. Affected Products
Affected Products
| Product | CPE 2.3 |
|---|---|
| Microsoft Exchange Server 2016 Cumulative Update 23 | cpe:2.3:a:microsoft:exchange_server:2016:cumulative_update_23:*:*:*:*:*:* |
| Microsoft Exchange Server 2019 Cumulative Update 14 | cpe:2.3:a:microsoft:exchange_server:2019:cumulative_update_14:*:*:*:*:*:* |
| Microsoft Exchange Server 2019 Cumulative Update 15 | cpe:2.3:a:microsoft:exchange_server:2019:cumulative_update_15:*:*:*:*:*:* |
| Microsoft Exchange Server SE RTM | cpe:2.3:a:microsoft:exchange_server:se:rtm:*:*:*:*:*:* |
Tested Environment (Vulnerable)
| Field | Value |
|---|---|
| Product | Exchange Server SE RTM + HU6 |
| Architecture | x64 |
| Binary | Microsoft.Exchange.Data.ApplicationLogic.dll |
| File Version | 15.02.2562.040 |
| Size | 3,392,072 bytes |
| SHA256 | 27E2A17E182C98AFB8CFA625479367B08830C9BBEB7617F03ED9B21F0BF01388 |
Tested Environment (Patched)
| Field | Value |
|---|---|
| Product | Exchange Server SE RTM + SU7 |
| Patch KB | KB5094139 (June 2026) |
| Binary | Microsoft.Exchange.Data.ApplicationLogic.dll |
| File Version | 15.02.2562.042 |
| Size | 3,396,408 bytes |
| SHA256 | 1416732F14F8CA1F0F92F49D5E0E6BABDC9374CA601301E4B7A340BE45CA53AA |
5. Root Cause Analysis
5a. Detailed Description
The vulnerability exists in the SynchronousDownloadData.DownloadDataFromUri() method within Microsoft.Exchange.Data.ApplicationLogic.dll. This method is invoked when an authenticated user sends an EWS InstallApp request with a ManifestUrl element, causing Exchange to download an add-in manifest from a user-controlled URL.
The vulnerable code at the entry of DownloadDataFromUri() performs an intranet address check to prevent SSRF, but the check is gated on the isBposUser parameter:
// SynchronousDownloadData.DownloadDataFromUri() -- VULNERABLE
public static MemoryStream DownloadDataFromUri(Uri uri, long expectedMaxResponseSize,
Func<long, bool, bool> responseValidationCallback, out long downloadTime,
bool isUrlUserInput = true, bool isBposUser = true,
string correlationId = null, bool blockUrlRedirection = false)
{
int tickCount = Environment.TickCount;
string text = correlationId ?? Guid.NewGuid().ToString();
if (isBposUser && IsInternalUrlCheckEnabled() && isUrlUserInput
&& IPAddressUtil.IsIntranetAddress(uri))
{
throw new DownloadPermanentException();
}
// ... proceeds to make HTTP request to uri ...
The isBposUser flag indicates whether the user is on a Business Productivity Online Suite (cloud/O365) tenant. For all on-premises Exchange deployments, this flag is false. The condition requires isBposUser to be true for the SSRF check to execute, meaning on-premises Exchange servers skip the intranet address validation entirely.
The caller path is:
- An authenticated user sends an EWS SOAP request with
<m:InstallApp><m:ManifestUrl>http://attacker/...</m:ManifestUrl></m:InstallApp> - The EWS handler calls
SynchronousDownloadData.Execute(url, isUrlUserInput: true, isBposUser: false) Execute()callsDownloadDataFromUri()withisBposUser = false- The
if (isBposUser && ...)check short-circuits tofalse - Exchange makes an HTTP GET to the attacker-controlled URL, appending a
corr=correlation ID parameter
This allows the attacker to:
- Probe internal services reachable from the Exchange server
- Access internal HTTP endpoints (metadata services, management APIs)
- Map internal network topology through response timing and error messages
This is classified as CWE-918 (SSRF) because the server makes requests to attacker-controlled URLs. The related CWE-863 (Incorrect Authorization) applies because the security check is incorrectly gated on a deployment-type flag rather than being universally enforced.
5b. Vulnerable Code Path
Microsoft.Exchange.Data.ApplicationLogic.dll 15.02.2562.040:
// SynchronousDownloadData.DownloadDataFromUri(), line 135
// The SSRF check only fires when isBposUser==true (cloud deployments)
if (isBposUser && IsInternalUrlCheckEnabled() && isUrlUserInput
&& IPAddressUtil.IsIntranetAddress(uri))
{
throw new DownloadPermanentException();
}
// Falls through to:
string originalString = uri.OriginalString;
originalString = (!originalString.Contains("?"))
? (originalString + "?corr=" + text)
: (originalString + "&corr=" + text);
uri = new Uri(originalString);
// ...
httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
// ...
using (HttpWebResponse httpWebResponse =
(HttpWebResponse)httpWebRequest.GetResponse()) { ... }
Call Stack:
SynchronousDownloadData.DownloadDataFromUri(Uri, Int64, Func, Int64&, Boolean, Boolean, String, Boolean)
SynchronousDownloadData.Execute(String, Boolean, Boolean, Boolean)
InstallApp EWS handler (via ManifestUrl code path)
5c. Fix (Patched Version)
The June 2026 patch (KB5094139) introduces two new feature flags and restructures the SSRF protection to work independently of the isBposUser flag:
ManifestUrlValidation(master switch, enabled by default) – gates the new SSRF protection logicManifestUrlCheck(URL allowlist, enabled by default) – when enabled, only URLs matching an allowlist are permitted
The patched DownloadDataFromUri() method replaces the single isBposUser-gated check with a three-tier defense:
// SynchronousDownloadData.DownloadDataFromUri() -- PATCHED
if (isUrlUserInput)
{
if (ExtensionsConfiguration.GetSnapshot(MachineSettingsContext.Local)
.ManifestUrlValidation.Enabled)
{
bool num = IsManifestUrlAllowlistEnabled();
if (num && !IsManifestUrlAllowlisted(uri))
{
throw new DownloadPermanentException(); // blocked by allowlist
}
if (!num && IsInternalUrlCheckEnabled()
&& IPAddressUtil.IsIntranetAddress(uri))
{
throw new DownloadPermanentException(); // blocked by IP check
}
}
else if (isBposUser && IsInternalUrlCheckEnabled()
&& IPAddressUtil.IsIntranetAddress(uri))
{
throw new DownloadPermanentException(); // legacy fallback
}
}
The new IsManifestUrlAllowlisted() method only permits URLs whose authority matches https://officeclient.microsoft.com (hardcoded) or entries in the ManifestUrlCheck.AllowedUrls configuration:
private static bool IsManifestUrlAllowlisted(Uri uri)
{
string manifestUrlBase = uri.GetLeftPart(UriPartial.Authority);
if (string.Equals(manifestUrlBase, "https://officeclient.microsoft.com",
StringComparison.OrdinalIgnoreCase))
{
return true;
}
IManifestUrlCheck manifestUrlCheck =
ExtensionsConfiguration.GetSnapshot(MachineSettingsContext.Local).ManifestUrlCheck;
if (manifestUrlCheck != null && manifestUrlCheck.AllowedUrls != null)
{
return manifestUrlCheck.AllowedUrls.Any((string url) =>
string.Equals(url.TrimEnd('/'), manifestUrlBase,
StringComparison.OrdinalIgnoreCase));
}
return false;
}
The Extensions.settings.ini configuration confirms the feature flags are enabled by default:
;Allowlist check for custom add-in manifest URLs to prevent SSRF
[ManifestUrlCheck]
_meta.access=public
_meta.type=Microsoft.Exchange.Management.Extension.IManifestUrlCheck
Enabled=true
AllowedUrls=
;Master switch for manifest URL SSRF protection (allowlist + IP check fix)
;When disabled, falls back to the original isBposUser-gated check
[ManifestUrlValidation]
_meta.access=public
_meta.type=Microsoft.Exchange.Management.Extension.IFeature
Enabled=true
| Vulnerable | Patched |
|---|---|
SSRF check gated on isBposUser flag | SSRF check gated on isUrlUserInput (applies to all deployments) |
On-premises users (isBposUser=false) bypass check entirely | ManifestUrlValidation feature flag enables new protection for all users |
| Only checks for intranet addresses | Allowlist-based approach: only officeclient.microsoft.com permitted by default |
| No URL allowlist | ManifestUrlCheck with configurable AllowedUrls list |
5d. Impact
Testing confirmed that an authenticated Exchange user can force the server to make outbound HTTP GET requests to arbitrary URLs. The Exchange server connected to the attacker-controlled HTTP listener and sent a request containing a correlation ID parameter, proving the SSRF. The response from the target URL is processed by Exchange (manifest parsing), though the content is not directly returned to the attacker. This limits the vulnerability to blind or semi-blind SSRF scenarios where the attacker can observe the connection but not read arbitrary response data.
In a real-world scenario, an attacker with valid Exchange mailbox credentials could leverage this SSRF to probe internal services behind the Exchange server’s network position, access cloud metadata endpoints (e.g., IMDS at 169.254.169.254), interact with internal REST APIs, or pivot through the Exchange server to reach otherwise-firewalled services. The requirement for valid credentials (PR:L) and the blind nature of the SSRF limit the severity, but in hybrid environments where Exchange has privileged network access, the impact could be significant.
6. Proof-of-Concept
6a. PoC Code
Download poc_cve_2026_45502.py (enterprise email verification required)
poc_CVE-2026-45502.py– Sends an EWS InstallApp SOAP request with a ManifestUrl pointing to a local HTTP listener, then waits for the SSRF callback.
6b. Reproduce Instructions
Prerequisites:
- A vulnerable Exchange Server (pre-KB5094139) with EWS enabled
- Valid Exchange mailbox credentials (any regular user)
- Network connectivity from the attacker machine to Exchange (HTTPS/443) and from Exchange back to the attacker machine (HTTP on the listener port)
- Python 3 with
requests,requests_ntlm, andlxmlpackages
Steps:
Edit
poc_CVE-2026-45502.pyand set theSERVERvariable to the Exchange server’s hostname or IP. Set the credentials in theHttpNtlmAuthcall to valid mailbox credentials.Run the PoC:
python poc_CVE-2026-45502.pyThe script:
- Starts an HTTP listener on port 8888
- Sends an EWS
InstallAppSOAP request with<m:ManifestUrl>pointing to the listener - Waits up to 5 seconds for the SSRF callback
Expected result (vulnerable): The Exchange server makes an HTTP GET request to the listener. The script prints “SSRF CONFIRMED” with the source IP, path, and User-Agent of the inbound request.
Expected result (patched): The Exchange server rejects the request with a
DownloadPermanentException. No HTTP callback is received.
6c. Test Results
| Metric | Result |
|---|---|
| EWS HTTP Response | 200 OK |
| EWS ResponseClass | Error |
| EWS ResponseCode | ErrorInternalServerError |
| SSRF Callback Received | Yes |
| Callback Method | GET |
| Callback Path | /ssrf-test?marker=CVE-2026-45502-SSRF-CONFIRMED&corr=<guid> |
| Callback Source | Exchange server IP |
The Exchange server made an HTTP GET to the attacker’s listener at /ssrf-test?marker=CVE-2026-45502-SSRF-CONFIRMED&corr=<correlation-id>, confirming the server-side request forgery. The corr= parameter appended by Exchange’s DownloadDataFromUri() method provides additional confirmation that the request originated from the vulnerable code path.
6d. Patched System Verification
After applying KB5094139, the same PoC was run against the patched Exchange server. The EWS request returned an error response, and no HTTP callback was received on the attacker’s listener. The ManifestUrlValidation feature flag (enabled by default) causes the URL to be rejected by the allowlist check before any outbound HTTP request is made.
7. Detection
7A. Network-Based Detection
Signature-Based Detection
The attack sends an EWS SOAP request over HTTPS containing an <m:InstallApp> element with a <m:ManifestUrl> child. While the SOAP payload is encrypted in transit (HTTPS), detection can focus on:
- TLS-terminating proxies/WAFs: Inspect decrypted EWS traffic for InstallApp requests with ManifestUrl elements pointing to internal IP ranges or non-Microsoft domains.
- Outbound HTTP from Exchange: The SSRF callback is plain HTTP (unless the ManifestUrl uses HTTPS). Monitor for unexpected outbound HTTP connections from the Exchange server to internal or unusual external addresses.
Suricata Rules
# Detect outbound HTTP requests from Exchange servers to internal IPs
# (SSRF callback from ManifestUrl processing)
alert http $HOME_NET any -> $HOME_NET any (msg:"CVE-2026-45502 Exchange SSRF - outbound HTTP to internal IP from Exchange"; \
flow:to_server,established; \
content:"GET"; http_method; \
content:"corr="; http_uri; \
threshold:type both, track by_src, count 3, seconds 60; \
reference:cve,2026-45502; \
classtype:web-application-attack; \
sid:2026045502; rev:1;)
# Detect EWS InstallApp with ManifestUrl containing HTTP scheme
# (requires TLS inspection / decrypted traffic mirror)
alert http any any -> $HOME_NET [443,444] (msg:"CVE-2026-45502 Exchange EWS InstallApp with ManifestUrl (decrypted)"; \
flow:to_server,established; \
content:"InstallApp"; http_client_body; \
content:"ManifestUrl"; http_client_body; distance:0; \
content:"http|3a 2f 2f|"; http_client_body; distance:0; \
reference:cve,2026-45502; \
classtype:web-application-attack; \
sid:2026045503; rev:1;)
7B. Host-Based Detection
Patch Verification — Exchange Build Numbers
Check the installed Exchange build to determine vulnerability status. The fix is included in the following builds:
| Product | Vulnerable Build Range | Fixed Build | Patch KB |
|---|---|---|---|
| Exchange Server 2016 CU23 | < 15.01.2507.069 | 15.01.2507.069 | KB5094144 |
| Exchange Server 2019 CU14 | < 15.02.1544.041 | 15.02.1544.041 | KB5094142 |
| Exchange Server 2019 CU15 | < 15.02.1748.046 | 15.02.1748.046 | KB5094140 |
| Exchange Server SE RTM | < 15.02.2562.043 | 15.02.2562.043 | KB5094139 |
PowerShell — Check Exchange Build Version
Get-ExchangeServer | Format-Table Name, AdminDisplayVersion -AutoSize
Compare the AdminDisplayVersion output against the fixed build numbers above. Any build below the fixed version for the corresponding CU is vulnerable.
8. References
| Source | URL |
|---|---|
| Microsoft Advisory | https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-45502 |
| MITRE CVE | https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-45502 |
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-45502 |
| Microsoft KB | https://support.microsoft.com/help/KB5094139 |