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

FieldValue
Primary CWECWE-918: Server-Side Request Forgery (SSRF)
Related CWECWE-863: Incorrect Authorization

3. Severity

CVSS 3.1 (from Microsoft Advisory)

FieldValue
Score5.0 (Medium)
VectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N

Our Assessment (CVSS 4.0)

Metric GroupMetricValue
Base – ExploitabilityAttack Vector (AV)Network
Attack Complexity (AC)Low
Attack Requirements (AT)None
Privileges Required (PR)Low
User Interaction (UI)None
Base – Vulnerable SystemConfidentiality (VC)None
Integrity (VI)None
Availability (VA)None
Base – Subsequent SystemConfidentiality (SC)Low
Integrity (SI)None
Availability (SA)None
ThreatExploit Maturity (E)Proof-of-Concept
FieldValue
CVSS 4.0 Score2.3 (Low)
CVSS 4.0 VectorCVSS: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

ProductCPE 2.3
Microsoft Exchange Server 2016 Cumulative Update 23cpe:2.3:a:microsoft:exchange_server:2016:cumulative_update_23:*:*:*:*:*:*
Microsoft Exchange Server 2019 Cumulative Update 14cpe:2.3:a:microsoft:exchange_server:2019:cumulative_update_14:*:*:*:*:*:*
Microsoft Exchange Server 2019 Cumulative Update 15cpe:2.3:a:microsoft:exchange_server:2019:cumulative_update_15:*:*:*:*:*:*
Microsoft Exchange Server SE RTMcpe:2.3:a:microsoft:exchange_server:se:rtm:*:*:*:*:*:*

Tested Environment (Vulnerable)

FieldValue
ProductExchange Server SE RTM + HU6
Architecturex64
BinaryMicrosoft.Exchange.Data.ApplicationLogic.dll
File Version15.02.2562.040
Size3,392,072 bytes
SHA25627E2A17E182C98AFB8CFA625479367B08830C9BBEB7617F03ED9B21F0BF01388

Tested Environment (Patched)

FieldValue
ProductExchange Server SE RTM + SU7
Patch KBKB5094139 (June 2026)
BinaryMicrosoft.Exchange.Data.ApplicationLogic.dll
File Version15.02.2562.042
Size3,396,408 bytes
SHA2561416732F14F8CA1F0F92F49D5E0E6BABDC9374CA601301E4B7A340BE45CA53AA

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:

  1. An authenticated user sends an EWS SOAP request with <m:InstallApp><m:ManifestUrl>http://attacker/...</m:ManifestUrl></m:InstallApp>
  2. The EWS handler calls SynchronousDownloadData.Execute(url, isUrlUserInput: true, isBposUser: false)
  3. Execute() calls DownloadDataFromUri() with isBposUser = false
  4. The if (isBposUser && ...) check short-circuits to false
  5. 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:

  1. ManifestUrlValidation (master switch, enabled by default) – gates the new SSRF protection logic
  2. ManifestUrlCheck (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
VulnerablePatched
SSRF check gated on isBposUser flagSSRF check gated on isUrlUserInput (applies to all deployments)
On-premises users (isBposUser=false) bypass check entirelyManifestUrlValidation feature flag enables new protection for all users
Only checks for intranet addressesAllowlist-based approach: only officeclient.microsoft.com permitted by default
No URL allowlistManifestUrlCheck 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, and lxml packages

Steps:

  1. Edit poc_CVE-2026-45502.py and set the SERVER variable to the Exchange server’s hostname or IP. Set the credentials in the HttpNtlmAuth call to valid mailbox credentials.

  2. Run the PoC:

    python poc_CVE-2026-45502.py
    
  3. The script:

    • Starts an HTTP listener on port 8888
    • Sends an EWS InstallApp SOAP request with <m:ManifestUrl> pointing to the listener
    • Waits up to 5 seconds for the SSRF callback
  4. 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.

  5. Expected result (patched): The Exchange server rejects the request with a DownloadPermanentException. No HTTP callback is received.

6c. Test Results

MetricResult
EWS HTTP Response200 OK
EWS ResponseClassError
EWS ResponseCodeErrorInternalServerError
SSRF Callback ReceivedYes
Callback MethodGET
Callback Path/ssrf-test?marker=CVE-2026-45502-SSRF-CONFIRMED&corr=<guid>
Callback SourceExchange 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:

  1. TLS-terminating proxies/WAFs: Inspect decrypted EWS traffic for InstallApp requests with ManifestUrl elements pointing to internal IP ranges or non-Microsoft domains.
  2. 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:

ProductVulnerable Build RangeFixed BuildPatch KB
Exchange Server 2016 CU23< 15.01.2507.06915.01.2507.069KB5094144
Exchange Server 2019 CU14< 15.02.1544.04115.02.1544.041KB5094142
Exchange Server 2019 CU15< 15.02.1748.04615.02.1748.046KB5094140
Exchange Server SE RTM< 15.02.2562.04315.02.2562.043KB5094139

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

SourceURL
Microsoft Advisoryhttps://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-45502
MITRE CVEhttps://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-45502
NVDhttps://nvd.nist.gov/vuln/detail/CVE-2026-45502
Microsoft KBhttps://support.microsoft.com/help/KB5094139