MS14-066 (CVE-2014-6321)?是存在于Microsoft的schannel.dll中的TLS堆緩沖區溢出漏洞。下面對原理以及poc構造進行分析。
Https是一種基于SSL/TLS的Http,所有的http數據都是在SSL/TLS協議封裝之上傳輸的。研究Https協議原理,最終其實是研究SSL/TLS協議。SSL協議,是一種安全傳輸協議。TLS是SSL v3.0的升級版,目前市面上所有的Https都是用的是TLS,而不是SSL。
TLS的握手階段是發生在TCP三次握手之后。握手實際上是一種協商的過程,對協議所必需的一些參數進行協商。TLS握手過程分為四步,過程如下:
由于客戶端對一些加解密算法的支持程度不一樣,因此在 TLS握手階段,客戶端要告知服務端,自己支持哪些加密算法,所以客戶端需要將本地支持的加密套件的列表傳送給服務端。除此之外,客戶端還要產生一個隨機數,這個隨機數一方面需要在客戶端保存,另一方面需要傳送給服務端,客戶端的隨機數需要跟服務端產生的隨機數結合起來產生后面要講到的Master Secret。
服務端在接收到客戶端的Client Hello之后,服務端需要將自己的證書發送給客戶端。這個證書是對于服務端的一種認證。在服務端向客戶端發送的證書中沒有提供足夠的信息的時候,還可以向客戶端發送一個Server Key Exchange。
對于非常重要的保密數據,服務端還需要對客戶端進行驗證,以保證數據傳送給了安全的合法的客戶端。服務端可以向客戶端發出Cerficate Request消息,要求客戶端發送證書對客戶端的合法性進行驗證。
跟客戶端一樣,服務端也需要產生一個隨機數發送給客戶端。客戶端和服務端都需要使用這兩個隨機數來產生Master Secret。
最后服務端會發送一個Server Hello Done消息給客戶端,表示Server Hello消息結束了。
如果服務端需要對客戶端進行驗證,在客戶端收到服務端的Server Hello消息之后,首先需要向服務端發送客戶端的證書,讓服務端驗證客戶端的合法性。 在此之前的所有TLS握手信息都是明文傳送的。在收到服務端的證書等信息之后,客戶端會使用一些加密算法產生一個48個字節的Key,這個Key叫PreMaster Secret, 最終通過Master secret生成會話密鑰,用來對應用數據進行加解密的。PreMaster secret使用RSA非對稱加密的方式,使用服務端傳過來的公鑰進行加密,然后傳給服務端。
接著,客戶端對服務端的證書進行檢查,檢查證書的完整性以及證書跟服務端域名是否吻合。
ChangeCipherSpec是一個獨立的協議,用于告知服務端,客戶端已經切換到之前協商好的加密套件的狀態,準備使用之前協商好的加密套件加密數據并傳輸了。
在ChangecipherSpec傳輸完畢之后,客戶端會使用之前協商好的加密套件和會話密鑰加密一段Finish的數據傳送給服務端,此數據是為了在正式傳輸應用數據之前對剛剛握手建立起來的加解密通道進行驗證。
服務端在接收到客戶端傳過來的PreMaster加密數據之后,使用私鑰對這段加密數據進行解密,并對數據進行驗證,也會使用跟客戶端同樣的方式生成會話密鑰,一切準備好之后,會給客戶端發送一個ChangeCipherSpec,告知客戶端已經切換到協商過的加密套件狀態,準備使用加密套件和會話密鑰加密數據了。之后,服務端也會使用會話密鑰加密后一段Finish消息發送給客戶端,以驗證之前通過握手建立起來的加解密通道是否成功。
根據之前的握手信息,如果客戶端和服務端都能對Finish信息進行正常加解密且消息正確的被驗證,則說明握手通道已經建立成功,接下來,雙方可以使用上面產生的會話密鑰對數據進行加密傳輸了。
CVE-2014-6321漏洞之所以嚴重,主要是在服務端配置為不需要對客戶端進行驗證時,可以由客戶端發送Certificate Verify,最終導致服務端進行客戶端的證書的合法性進行檢查,觸發漏洞
根據之前Mike Czumak的分析報告,知道漏洞存在于unsigned int __stdcall DecodeSigAndReverse(const BYTE *pbEncoded, DWORD cbEncoded, void *Dst, int a4, LPCSTR lpszStructType)函數中。
當lpszStructType 為0x2F (X509_ECC_SIGNATURE)時,pbEncoded為指向CERT_ECC_SIGNATURE的指針,其結構如下,其中cbData為pbData指向數據的長度。
#!c
typedef struct _CERT_ECC_SIGNATURE {
CRYPT_UINT_BLOB r;
CRYPT_UINT_BLOB s;
} CERT_ECC_SIGNATURE, *PCERT_ECC_SIGNATURE;
typedef struct _CRYPTOAPI_BLOB {
DWORD cbData;
__field_bcount(cbData) BYTE *pbData;
} CRYPT_UINT_BLOB, *PCRYPT_UINT_BLOB;
第一次調用CryptDecodeObject獲取成功解碼所需要的緩沖區長度,第二次完成解碼。如果能夠控制解碼后r或者s中的cbData大小,使其超過Dst緩沖區大小,最終就能溢出。
首先看Dst緩沖區的大小,DecodeSigAndReverse的調用點在CheckClientVerifyMessage中,Dst也就是v12,v12由SPExternalAlloc根據v11的值分配得到,而v11是由BCryptGetProperty獲取的CNG對象的KeyLength屬性的值,查詢MSDN知道其表示Key的比特數,因此后面通過右移3位獲取對應的字節數。當采用256 bit橢圓曲線數字簽名算法時,v12的值為0x40。
對于待解碼的數據緩沖區指針pbSignature和緩沖區大小cbEncoded來自 NTSTATUS __stdcall CheckClientVerifyMessage(int a1, const wchar_t *pszBlobType, int a3, int a4, UCHAR *a5, DWORD cbEncoded)的參數a5和cbEncoded。通過調試分析可以看到a5指向客戶端發送的簽名數據,cbEncoded是簽名數據的長度,顯然可以控制這部分數據。如果能夠使CryptDecodeObject按照我們的意愿解碼出惡意的數據就能最終觸發漏洞。
通過IIS管理器構建服務器證書,采用自簽名證書,添加網站綁定https,設置ssl為忽略客戶端證書。
通過https協議訪問搭建好的網站。
在采用openssl-1.0.1j,在kali上編譯運行,生成EC cert和key,命令如下:
openssl ecparam -out ec_key.pem -name prime256v1 -genkey openssl req -new -key ec_key.pem -x509 -nodes -days 365 -out cert.pem
為了能夠讓服務器進行客戶端證書認證,修改openssl-1.0.1j/ssl/s3_clnt.c文件,每次都發送證書進行認證。修改函數int ssl3_connect(SSL *s),在Server Hello Done處理后,將s->s3->tmp.cert_req 置為1。
編譯修改后的代碼,切換當前目錄為openssl-1.0.1j/apps/,通過命令./openssl s_client -connect xxx.xxx.xxx.xxx:443 -cert cert.pem -key ec_key.pem -verify 5連接服務端,在服務器端的lsass.exe進程的NTSTATUS __thiscall CSsl3TlsServerContext::DigestCertVerify(int this, unsigned __int8 *a2, unsigned int a3)函數上設置斷點,可以看到觸發了斷點。
下面構造簽名部分,即CryptDecodeObject解碼的數據,為了能夠得到可控的解碼后的數據,調用CryptEncodeObject對我們想要的數據進行編碼,這樣只要編碼成功,CryptDecodeObject就總是可以解碼成功。
#!c
BYTE* GetDecodeObject()
{
CERT_ECC_SIGNATURE sig;
char pData[0x1000];
memset( pData , 0xcc , 0x1000 );
sig.r.pbData = (BYTE*)pData;
sig.r.cbData = 0x20;
sig.s.cbData = 0x1000;
sig.s.pbData = (BYTE*)pData;
DWORD cbEncoded = 0;
if ( CryptEncodeObject( X509_ASN_ENCODING , X509_ECC_SIGNATURE , &sig , NULL , &cbEncoded ) )
{
unsigned char *pEnc = new unsigned char[cbEncoded];
CryptEncodeObject( X509_ASN_ENCODING , X509_ECC_SIGNATURE , &sig , (BYTE*)pEnc , &cbEncoded );
return pEnc;
}
return NULL;
}
最終得到大小為0x200e字節的數據,數據為0x30、0x82、0x20、0x0a、0x02、0x82、0x10、0x01、0x00、0xcc(連續0x1000個)、0x82、0x10、0x01、0x00、0xcc(連續0x1000個) 修改s3_clnt.c中的int ssl3_send_client_verify(SSL *s)函數,將數據發送出去。
對修改的代碼進行編譯運行,可以看到CheckClientVerifyMessage函數的a5指向我們構造的數據,cbEncoded的值為0x0000200e。
運行到DecodeSigAndReverse函數,其參數pbEncoded指向我們構造的數據,cbEncoded的值為0x0000200e。
經過CryptDecodeObject解碼后,得到的CERT_ECC_SIGNATURE的r和s中的cbData都為0x1000,后續的memcpy將0x1000大小的緩沖區中數據拷到0x40大小的緩沖區,導致了溢出,最終觸發異常。