原文鏈接:https://www.anquanke.com/post/id/193149
0x00 前言
這個系列文章主要講ntlm認證相關的內容。以及着重介紹ntlm兩大安全問題–PTH和ntlm_relay。
ntlm篇分為四篇文章
第1篇文章也是本文,這篇文章主要簡單介紹一些基礎概念以及引進一些相關的漏洞,比如Pass The Hash以及ntlm_relay。
其余三篇文章的內容全部都是講ntlm_relay,這個安全問題是ntlm篇的重點內容。
第2篇文章主要講觸發windows向攻擊者發起ntlm請求的一些方式,比如大家耳熟能詳的打印機漏洞。
第3篇文章主要講的是攻擊者接收到ntlm請求之后做的事,如爆破Net-ntlm,又或者relay到SMB,HTTP,Exchange,LDAP等。
第4篇文章主要回顧一下從上世紀ntlmrelay被提出來,微軟從08年開始為ntlmrelay陸陸續續推出的一些補丁以及繞過,如ms08068,MS16-075,CVE-2015-0005,CVE-2018-8581,CVE-2019-1040,CVE2019-1384。以及ntlm relay的一些緩解措施。
0x01 LM Hash & NTLM Hash
windows內部是不保存明文密碼的,只保存密碼的hash。
其中本機用戶的密碼hash是放在 本地的SAM文件 里面,域內用戶的密碼hash是存在域控的NTDS.DIT文件 里面。那hash的格式是怎么樣的呢?
在Windows系統導出密碼的時候,經常看到這樣的密碼格式
Administrator:500:AAD3B435B51404EEAAD3B435B51404EE:31D6CFE0D16AE931B73C59D7E0C089C0:::
其中的AAD3B435B51404EEAAD3B435B51404EE是LM Hash
31D6CFE0D16AE931B73C59D7E0C089C0是NTLM Hash
下面詳細介紹下這兩種hash格式。
1. LM Hash
全稱是LAN Manager Hash, windows最早用的加密算法,由IBM設計。
LM Hash的計算:
- 用戶的密碼轉換為大寫,密碼轉換為16進制字符串,不足14字節將會用0來再后面補全。
- 密碼的16進制字符串被分成兩個7byte部分。每部分轉換成比特流,並且長度位56bit,長度不足使用0在左邊補齊長度
- 再分7bit為一組,每組末尾加0,再組成一組
- 上步驟得到的二組,分別作為key 為 “KGS!@#$%”進行DES加密。
- 將加密后的兩組拼接在一起,得到最終LM HASH值。
#coding=utf-8 import re import binascii from pyDes import * def DesEncrypt(str, Des_Key): k = des(binascii.a2b_hex(Des_Key), ECB, pad=None) EncryptStr = k.encrypt(str) return binascii.b2a_hex(EncryptStr) def group_just(length,text): # text 00110001001100100011001100110100001101010011011000000000 text_area = re.findall(r'.{%d}' % int(length), text) # ['0011000', '1001100', '1000110', '0110011', '0100001', '1010100', '1101100', '0000000'] text_area_padding = [i + '0' for i in text_area] #['00110000', '10011000', '10001100', '01100110', '01000010', '10101000', '11011000', '00000000'] hex_str = ''.join(text_area_padding) # 0011000010011000100011000110011001000010101010001101100000000000 hex_int = hex(int(hex_str, 2))[2:].rstrip("L") #30988c6642a8d800 if hex_int == '0': hex_int = '0000000000000000' return hex_int def lm_hash(password): # 1. 用戶的密碼轉換為大寫,密碼轉換為16進制字符串,不足14字節將會用0來再后面補全。 pass_hex = password.upper().encode("hex").ljust(28,'0') #3132333435360000000000000000 print(pass_hex) # 2. 密碼的16進制字符串被分成兩個7byte部分。每部分轉換成比特流,並且長度位56bit,長度不足使用0在左邊補齊長度 left_str = pass_hex[:14] #31323334353600 right_str = pass_hex[14:] #00000000000000 left_stream = bin(int(left_str, 16)).lstrip('0b').rjust(56, '0') # 00110001001100100011001100110100001101010011011000000000 right_stream = bin(int(right_str, 16)).lstrip('0b').rjust(56, '0') # 00000000000000000000000000000000000000000000000000000000 # 3. 再分7bit為一組,每組末尾加0,再組成一組 left_stream = group_just(7,left_stream) # 30988c6642a8d800 right_stream = group_just(7,right_stream) # 0000000000000000 # 4. 上步驟得到的二組,分別作為key 為 "KGS!@#$%"進行DES加密。 left_lm = DesEncrypt('KGS!@#$%',left_stream) #44efce164ab921ca right_lm = DesEncrypt('KGS!@#$%',right_stream) # aad3b435b51404ee # 5. 將加密后的兩組拼接在一起,得到最終LM HASH值。 return left_lm + right_lm if __name__ == '__main__': hash = lm_hash("123456")
LM加密算法存在一些固有的漏洞
- 首先,密碼長度最大只能為14個字符
- 密碼不區分大小寫。在生成哈希值之前,所有密碼都將轉換為大寫
- 查看我們的加密過程,就可以看到使用的是分組的DES,如果密碼強度是小於7位,那么第二個分組加密后的結果肯定是aad3b435b51404ee,如果我們看到lm hash的結尾是aad3b435b51404ee,就可以很輕易的發現密碼強度少於7位
- 一個14個字符的密碼分成7 + 7個字符,並且分別為這兩個半部分計算哈希值。這種計算哈希值的方式使破解難度成倍增加,因為攻擊者需要將7個字符(而不是14個字符)強制暴力破解。這使得14個字符的密碼的有效強度等於,或者是7個字符的密碼的兩倍,該密碼的復雜度明顯低於
14個字符的密碼的理論強度。
- Des密碼強度不高
2. NTLM Hash
為了解決LM加密和身份驗證方案中固有的安全弱點,Microsoft 於1993年在Windows NT 3.1中引入了NTLM協議。下面是各個版本對LM和NTLM的支持。
其中
也就是說從Windows Vista 和 Windows Server 2008開始,默認情況下只存儲NTLM Hash,LM Hash將不再存在。(因此后面我們介紹身份認證的時候只介紹Net-ntlm,不再介紹net-lm)如果空密碼或者不儲蓄LM Hash的話,我們抓到的LM Hash是AAD3B435B51404EEAAD3B435B51404EE。
所以在win7 中我們看到抓到LM Hash都是AAD3B435B51404EEAAD3B435B51404EE,這里的LM Hash並沒有價值。
但某些工具的參數需要填寫固定格式LM hash:NT hash,可以將LM hash填0(LM hash可以為任意值),即00000000000000000000000000000000:NT hash。
接下來講下NTLM Hash的計算
1.先將用戶密碼轉換為十六進制格式。
2.將十六進制格式的密碼進行Unicode編碼。
3.使用MD4摘要算法對Unicode編碼數據進行Hash計算
python2 -c 'import hashlib,binascii; print binascii.hexlify(hashlib.new("md4", "p@Assword!123".encode("utf-16le")).digest())'
NTLM驗證是一種Challenge/Response 驗證機制,由三種消息組成:通常稱為type 1(協商),類型type 2(質詢)和type 3(身份驗證)。
它基本上是這樣工作的:
- 用戶登錄客戶端電腦
- (type 1)客戶端向服務器發送type 1(協商)消息,它主要包含客戶端支持和服務器請求的功能列表。
- (type 2)服務器用type 2消息(質詢)進行響應,這包含服務器支持和同意的功能列表。但是,最重要的是,它包含服務器產生的Challenge。
- (type 3)客戶端用type 3消息(身份驗證)回復質詢。用戶接收到步驟3中的challenge之后,使用用戶hash與challenge進行加密運算得到response,將response,username,challeng發給服務器。消息中的response是最關鍵的部分,因為它們向服務器證明客戶端用戶已經知道帳戶密碼。
- 服務器拿到type 3之后,使用challenge和用戶hash進行加密得到response2與type 3發來的response進行比較。如果用戶hash是存儲在域控里面的話,那么沒有用戶hash,也就沒辦法計算response2。也就沒法驗證。這個時候用戶服務器就會通過netlogon協議聯系域控,建立一個安全通道,然后將type 1,type 2,type3 全部發給域控(這個過程也叫作Pass Through Authentication認證流程)
- 域控使用challenge和用戶hash進行加密得到response2,與type 3的response進行比較
下面簡單介紹下三個過程,如果對於細節不感興趣的話就可以忽略。
1. type 1 協商
這個過程是客戶端向服務器發送type 1(協商)消息,它主要包含客戶端支持和服務器請求的功能列表。
主要包含以下結構
抓包查看對應的信息如下
如果想仔細理解每個字段的值請閱讀官方文檔NEGOTIATE_MESSAGE
2. type 2 質詢
這個過程是服務器用type 2消息(質詢)進行響應,這包含服務器支持和同意的功能列表。但是,最重要的是,它包含服務器產生的Challenge。
主要 包含以下結構
其中最主要的信息是challenge。后面加密驗證依賴於challenge
抓包查看對應的信息如下
如果想仔細理解每個字段的值請閱讀官方文檔CHALLENGE_MESSAGE
3. type 3 身份驗證
這個過程客戶端接收到challenge之后,使用用戶hash與challenge進行加密運算得到response,將response,username,challenge發給服務器。消息中的response是最關鍵的部分,因為它向服務器證明客戶端用戶已經知道帳戶密碼。
主要包含以下結構
這里的Challeng不同於type2 的Challenge,這里的Challenge是一個隨機的客戶端nonce。
MIC是校驗和,設計MIC主要是為了防止這個包中途被修改
sessionkey是在要求進行簽名的時候用的,用來進行協商加密密鑰,可能有些文章會說sessionkey就是加密密鑰,需要擁有用戶hash才能計算出來,因此攻擊者算不出來,就無法加解密包。但是想想就不可能,這個session_key已經在流量里面明文傳輸,那攻擊者拿到之后不就可以直接加解密包了。當然這是后話,后面講簽名的時候會詳細講講這個問題。
抓包查看對應的信息如下
如果想仔細理解每個字段的值請閱讀官方文檔AUTHENTICATE_MESSAGE