HawkTrace Logo
Research
June 24, 2026

CVE-2026-45504 Microsoft Exchange SSRF via File Read

As the HawkTrace team, we have analyzed a server-side request forgery vulnerability affecting Microsoft Exchange that we discovered some time ago. This vulnerability allows an authenticated user to read arbitrary files from the Exchange server

Batuhan Er• Security Researcher
6 mins read

CVE-2026-45504 — SSRF via File Read

Any low-privileged user on Microsoft Exchange can read arbitrary files from the system without authorization. All vulnerabilities of this type can be found on the ZeroVault Platform (https://zerovault.io).

CVE-2026-45504 / 8.8

Microsoft Exchange Overview


What is Microsoft Exchange?

Microsoft Exchange, is Microsoft's server-based email and messaging platform that enables organizations to manage email, calendars, contacts, and tasks. Available either as an on-premises deployment or in the cloud through Microsoft 365, it provides a centralized and secure mail infrastructure accessible through clients such as Outlook


TryTwice

private static WebResponse TryTwice(ICallContext callContext, string url, ICredentials credentials, WebHeaderCollection headers)
		{
			WebResponse webResponse = null;
			Exception ex = null;
			for (int i = 0; i < 2; i++)
			{
				HttpWebRequest httpWebRequest = WebRequest.CreateHttp(url);
				httpWebRequest.Method = "GET";
				httpWebRequest.Credentials = credentials;
				httpWebRequest.UserAgent = OneDriveProUtilities.UserAgentString;
				httpWebRequest.ContentType = "application/json;odata=verbose";
				httpWebRequest.PreAuthenticate = true;
				httpWebRequest.Headers.Add(headers);
				try
				{
					OneDriveProUtilities.EndBudget(callContext);
					IAsyncResult asyncResult = httpWebRequest.BeginGetResponse(null, null);
					asyncResult.AsyncWaitHandle.WaitOne();
					webResponse = httpWebRequest.EndGetResponse(asyncResult);
				}
				catch (WebException ex2)
				{
					ex = ex2;
					webResponse = ex2.Response;
				}
				finally
				{
					OneDriveProUtilities.StartBudget(callContext, null, "GetWacUrl");
				}
				HttpWebResponse httpWebResponse = webResponse as HttpWebResponse;
				if (ex == null && httpWebResponse != null && httpWebResponse.StatusCode < HttpStatusCode.BadRequest)
				{
					return httpWebResponse;
				}
				StringBuilder stringBuilder = new StringBuilder();
				OneDriveProUtilities.GetRequestAndResponseDetails(stringBuilder, ex, httpWebRequest, httpWebResponse, null, null);
				if (callContext != null && callContext.ProtocolLog != null)
				{
					callContext.ProtocolLog.Set(GetWacAttachmentInfoMetadata.RequestDetails, stringBuilder.ToString());
				}
				else
				{
					OwaServerTraceLogger.AppendToLog(OwaFeature.OneDrivePro, "OneDrivePro.SendRestRequest", null, "HttpFailure", stringBuilder.ToString());
				}
			}
			if (ex != null)
			{
				throw ex;
			}
			return null;
		}

TryTwice function appears from the outside to be a simple helper that takes certain parameters and sends an HTTP request. However, the real story begins with what URL it is called with the URL parameter is passed directly into WebRequest.CreateHttp(url), which makes a request to the given address and returns the response

GetTokenRequestWebResponse

private static WebResponse GetTokenRequestWebResponse(ICallContext callContext, OwaIdentity identity, string getWacTokenUrlFormat, string endPointUrl, string documentUrl, string actionOrAppId, string callingMethod = "GetWacToken", string eventId = "SP.GWT")
		{
			if (documentUrl.EndsWith("?web=1", StringComparison.OrdinalIgnoreCase))
			{
				documentUrl = documentUrl.Substring(0, documentUrl.Length - 6);
			}
			string text = string.Format(getWacTokenUrlFormat, endPointUrl, HttpUtility.UrlEncode(documentUrl.Replace("'", "''")), actionOrAppId);
			ICredentials oauthCredential = OauthUtils.GetOauthCredential(identity.GetOWAMiniRecipient());
			WebHeaderCollection oauthRequestHeaders = OneDriveProUtilities.GetOAuthRequestHeaders();
			oauthRequestHeaders[HttpRequestHeader.AcceptLanguage] = Thread.CurrentThread.CurrentCulture.Name;
			WebResponse webResponse;
			...
			ExTraceGlobals.AttachmentHandlingTracer.TraceWarning<string, string>(0L, "OneDriveProUtilities.{0} Exception while trying to get wac token using durable url. : {1}", callingMethod, ex.StackTrace);
			documentUrl = documentUrl.Substring(0, documentUrl.LastIndexOf("?", StringComparison.InvariantCulture));
			text = string.Format(getWacTokenUrlFormat, endPointUrl, HttpUtility.UrlEncode(documentUrl.Replace("'", "''")), actionOrAppId);				ExTraceGlobals.AttachmentHandlingTracer.TraceWarning<string, string>(0L, "OneDriveProUtilities.{0} Fallback to canonical url format: {1}", callingMethod, text);
			webResponse = OneDriveProUtilities.TryTwice(callContext, text, oauthCredential, oauthRequestHeaders);
			}
			return webResponse;
		}

GetTokenRequestWebResponse transforms the given URL into a SharePoint REST API request.

GetWacUrl

internal static WacUrlInfo GetWacUrl(ICallContext callContext, OwaIdentity identity, string endPointUrl, string documentUrl, bool isEdit, bool isSPGetWacTokenEnabled)
		{
			string text = (isEdit ? "2" : "4");
			if (isSPGetWacTokenEnabled)
			{
				text = (isEdit ? "1" : "0");
			}
			string text2 = (isSPGetWacTokenEnabled ? "{0}/_api/SP.Utilities.WOPIHostUtility.GetWopiTargetPropertiesByUrl(fileUrl=@p, requestedAction={2})?@p='{1}'" : "{0}/_api/Microsoft.SharePoint.Yammer.WACAPI.GetWacToken(fileUrl=@p, wopiAction={2})?@p='{1}'");
			WebResponse tokenRequestWebResponse = OneDriveProUtilities.GetTokenRequestWebResponse(callContext, identity, text2, endPointUrl, documentUrl, text, "GetWacToken", "SP.GWT");
			XmlDocument xmlDocument = new XmlDocument();
			OneDriveProUtilities.EndBudget(callContext);
			XmlReaderSettings xmlReaderSettings = new XmlReaderSettings();
			xmlReaderSettings.XmlResolver = null;
			XmlReader xmlReader = XmlReader.Create(tokenRequestWebResponse.GetResponseStream(), xmlReaderSettings);
			xmlDocument.Load(xmlReader);
			OneDriveProUtilities.StartBudget(callContext, null, "GetWacUrl");
			string text3 = "http://schemas.microsoft.com/ado/2007/08/dataservices";
			string text4 = null;
			string text5 = null;
			string text6 = null;
			foreach (object obj in xmlDocument.GetElementsByTagName("*", text3))
			{
				XmlNode xmlNode = (XmlNode)obj;
				if (xmlNode is XmlElement)
				{
					if (text4 != null && text5 != null && text6 != null)
					{
						break;
					}
					if (!isSPGetWacTokenEnabled && string.CompareOrdinal(xmlNode.LocalName, "AppUrl") == 0)
					{
						text4 = xmlNode.InnerText;
					}
					else if (isSPGetWacTokenEnabled && string.CompareOrdinal(xmlNode.LocalName, "WebApplicationUrl") == 0)
					{
						text4 = xmlNode.InnerText;
					}
					else if (string.CompareOrdinal(xmlNode.LocalName, "AccessToken") == 0)
					{
						text5 = xmlNode.InnerText;
					}
					else if (string.CompareOrdinal(xmlNode.LocalName, "AccessTokenTtl") == 0)
					{
						text6 = xmlNode.InnerText;
					}
				}
			}
			if (text4 == null || text5 == null || text6 == null)
			{
				StringBuilder stringBuilder = new StringBuilder();
				OneDriveProUtilities.GetRequestAndResponseDetails(stringBuilder, null, null, tokenRequestWebResponse, null, null);
				stringBuilder.AppendFormat("baseUrl: {0}, accessToken: {1}, accessTokenTtl: {2}\n", text4, text5, text6);
				if (callContext != null && callContext.ProtocolLog != null)
				{
					callContext.ProtocolLog.Set(GetWacAttachmentInfoMetadata.RequestDetails, stringBuilder.ToString());
				}
				else
				{
					OwaServerTraceLogger.AppendToLog(OwaFeature.OneDrivePro, "OneDrivePro.SendRestRequest", null, "HttpFailure", stringBuilder.ToString());
				}
				throw new OwaException("SharePoint's GetWacToken response is not usable.");
			}
			text4 = OneDriveProUtilities.ReplaceLanguageParametersOnWacUrl(text4, Thread.CurrentThread.CurrentCulture.Name);
			return new WacUrlInfo
			{
				BaseUrl = text4,
				Token = text5,
				TokenTtl = text6
			};
		}

GetWacUrl function works to obtain a WOPI token through SharePoint it makes a request to the given URL and parses the WebApplicationUrl and AccessToken values from the returned OData response. This is actually where the vulnerability begins. Up to this point we can supply any URL and trigger a request, but that alone is not enough :D.

GetWacUrl (wrapper)

internal static string GetWacUrl(ICallContext callContext, OwaIdentity identity, string endPointUrl, string documentUrl, bool isEdit, FeaturesManager featuresManager)
		{
			bool flag = featuresManager != null && featuresManager.ServerSettings.SPGetWacToken.Enabled;
			WacUrlInfo wacUrl = OneDriveProUtilities.GetWacUrl(callContext, identity, endPointUrl, documentUrl, isEdit, flag);
			string text = (isEdit ? "OwaEdit" : "OwaView");
			return string.Format("{0}&access_token={1}&access_token_ttl={2}&sc={3}", new object[] { wacUrl.BaseUrl, wacUrl.Token, wacUrl.TokenTtl, text });
		}

GetWacUrl wrapper wrapper function determines the format of the final URL constructing it in the form of http://attacker.com/...&access_token=...&sc=....

Escape Charset

Just like in SQL Injection and many other vulnerabilities, we will work some magic here with the # character. If the attacker returns file:///c:/windows/win.ini as the WebApplicationUrl, Exchange will construct the request as file:///c:/windows/win.ini?...&access_token=... which breaks the file path. However, by appending a # character, the system will treat everything after it as a fragment and ignore it, resulting in file:///c:/windows/win.ini#?...&access_token=..., which gives us exactly the file we want. To trigger this, Exchange will send the following request to our server /_api/SP.Utilities.WOPIHostUtility.GetWopiTargetPropertiesByUrl(fileUrl=@p,requestedAction=0)?@p='http://hawktrace.local:80/' And we respond with file:///C:/windows/win.ini# as the WebApplicationUrl, allowing the chain to continue exactly as intended.

Trace

Microsoft.Exchange.Clients.Owa2.Server.Core.OneDriveProUtilities.TryTwice(ICallContext, string, ICredentials, WebHeaderCollection) : WebResponse @06000B4F
		Microsoft.Exchange.Clients.Owa2.Server.Core.OneDriveProUtilities.GetTokenRequestWebResponse(ICallContext, OwaIdentity, string, string, string, string, string, string) : WebResponse @06000B43
				Microsoft.Exchange.Clients.Owa2.Server.Core.OneDriveProUtilities.GetFileHandlerUrl(ICallContext, OwaIdentity, string, string, string, bool) : string @06000B48
				Microsoft.Exchange.Clients.Owa2.Server.Core.OneDriveProUtilities.GetWacUrl(ICallContext, OwaIdentity, string, string, bool, bool) : WacUrlInfo @06000B46
						Microsoft.Exchange.Clients.Owa2.Server.Core.CreateMeetingNotes.GetWacUrlInfo(string) : WacUrlInfo @06002501
						Microsoft.Exchange.Clients.Owa2.Server.Core.OneDriveProUtilities.GetWacUrl(ICallContext, OwaIdentity, string, string, bool, FeaturesManager) : string @06000B45
								Microsoft.Exchange.Clients.Owa2.Server.Core.GetWacInfoBase.CreateWacAttachmentTypeForReferenceAttachmentAsync(UserContext, ICallContext, AttachmentIdType, string, string, WacAction, bool, string, string, AttachmentPermissionLevel, bool) : Task<WacAttachmentType> @06002CDF
										Microsoft.Exchange.Clients.Owa2.Server.Core.GetWacAttachmentInfo.GetResultForReferenceAttachmentAsync(ICallContext, UserContext, GetWacAttachmentInfo.Implementation, AttachmentDataProvider, string, BaseItemId, string, WacAction, string, string, RequestDetailsLogger, string, AttachmentPermissionLevel) : Task<WacAttachmentType> @06002CCE
												Microsoft.Exchange.Clients.Owa2.Server.Core.GetWacAttachmentInfo.ExecuteAsync(UserContext, ICallContext, IStoreSession, IItem, IAttachment, string, string, WacAction, string) : Task<WacAttachmentType> @06002CC9

...

Since everything after this chain simply comes down to creating an attachment through the exchange.asmx web service and triggering the requests, I won't go into further detail the PoC speaks for itself :D

PoC

CVE-2026-45504 Microsoft Exchange File Read


Conclusion

CVE-2026-45504 is a critical arbitrary file read vulnerability in Microsoft Exchange Server 2019, caused by a missing scheme validation on the WebApplicationUrl field returned from attacker-controlled WOPI endpoints. The vulnerability allows an authenticated low-privileged user to read arbitrary local files from the Exchange server by creating an EWS ReferenceAttachment with a crafted ProviderEndpointUrl pointing to an attacker-controlled server. When the victim opens the attachment preview, Exchange makes a SSRF request to the attacker's server, which responds with file:///C:/path/to/file# as the WebApplicationUrl. The # fragment trick causes Exchange to append OAuth parameters after the fragment marker, which are then ignored by the URI parser, resulting in Exchange reading the target file via FileWebRequest and returning its contents to the attacker. Permanent mitigation requires validating the scheme of WebApplicationUrl values returned by WOPI providers to block file:// and other non-HTTP schemes before passing them to WebClient.OpenRead().


Stay Protected

Security vulnerabilities are discovered constantly. HawkTrace helps organizations stay ahead of threats through comprehensive security assessments, APT simulations, and continuous threat hunting.

Need expert security consultation? Contact us or explore more research.