不需要憑證就能讓 SmartConsole 相信你是內部系統?
摘要
這份報告針對 CVE-2026-16232 進行獨立的技術研究,該漏洞是管理伺服器登入服務中的一個驗證繞過弱點,根源在於應用程式層的信任邊界違反。這個分析重建了有漏洞的程式碼路徑,將其與廠商提供的修補程式進行對比,並重現了將未經驗證的網路位置轉換為完整權限管理 Session 的多階段協定流程。報告特別關注:一個不受信任的遠端對等方所提供的身分宣告,竟被允許取代經密碼學驗證的對等身分;而舊有的授權檢查則進一步加劇了此缺陷,因為它針對特定的管理命令提供了提升的、預先的權限繞過。
1. 概述
此漏洞影響管理主控台應用程式的登入服務,該服務負責調解管理桌面用戶端與後端政策/設定儲存庫之間的存取。揭露的缺陷允許未經驗證的網路攻擊者取得應用程式範圍的登入 Token,隨後將該 Token 提升為完整的管理員 Session,從而獲得讀取和修改安全政策物件的能力 — 這種身份冒充模式,與先前驗證強制研究中記錄的其他信任轉送技術有相似之處 [1] 。攻擊僅需能夠網路存取管理服務,且用戶端信任設定未限制哪些主機可作為管理主控台用戶端;在測試環境中,此設定預設即為如此 [1] 。
此缺陷最適合歸類為欺騙導致的驗證繞過弱點 (CWE-290) [2] :伺服器接受來自網路的自我聲明身份字串,而非從已驗證的加密通道中推導出該身份。這份報告專注於此缺陷的程式碼層級與協定層級機制,不涉及任何特定的部署環境。
2. 架構背景
此管理堆疊結合了兩個世代的服務基礎架構,它們必須對「誰在與我通訊」有一致的認知。較舊的元件是原生的 FWM/CPMI 服務,它執行憑證互信交握(Certificate-based mutual-trust handshake, 以下統稱為 Secure Internal Communication (SIC) 啟動程序),之後節點之間會交換帶有長度前置詞、名稱/值編的碼物件。較新的元件是 SOAP-over-HTTPS 服務,它公開了登入、查詢和物件管理操作,並使用登入時發出的 Session 識別碼來驗證後續請求 [1] 。
這兩個世代之間設計正確的橋接,必須確保在舊有層級所聲明的任何身份,都錨定於 SIC 交握期間建立的相同加密節點身份。而此漏洞的產生,正是因為在有漏洞的程式碼路徑中,這種錨定是選擇性的而非強制性的。
3. 驗證路徑的根本原因分析
登入服務的進入點會將傳入的應用程式登入使用者名稱拆分為兩個部分:一個應用程式名稱和一個聲稱的 SIC 識別名稱 (Distinguished Name, DN)。這兩個值都來自於攻擊者可控制的網路輸入,而非任何已驗證的來源。
- // Program 1 — Login entry point splitting the untrusted identity claim
- // (application-login username parsing, main-article source)
- private AuthenticationResponse authenticateUser(AuthenticationInfoBase authenticationInfoBase,
- String string, String string2, CPUUID cPUUID, boolean bl,
- LockAdminInfoContainer lockAdminInfoContainer, ExternalLoginInfo externalLoginInfo)
- throws AuthenticationFailureLoginException, LicenseExpiredLoginException {
- // ...
- } else if (authenticationInfoBase instanceof FwmAuthenticationInfo) {
- object2 = authenticationInfoBase.getUsername();
- int n = ((String)object2).toLowerCase().lastIndexOf("cn=");
- object = (FwmAuthenticationInfo)authenticationInfoBase;
- if (FwmLoginType.APPLICATION.equals((Object)object.getFwmLoginType())) {
- // (1) attacker-supplied text after "cn=" becomes the claimed identity
- String suppliedSicDn = ((String)object2).substring(n);
- // (2) the text before it becomes the claimed application name
- String applicationName = ((String)object2).substring(0, n - 1);
- CPApplicationAuthenticationInfo cPApplicationAuthenticationInfo =
- new CPApplicationAuthenticationInfo();
- cPApplicationAuthenticationInfo.setUsername(applicationName);
- // (3) the untrusted DN is forwarded as a *separate* trust input
- this.cpApplicationAuthentication(
- (AuthenticationInfoBase)cPApplicationAuthenticationInfo, suppliedSicDn, cPUUID);
- authenticationInfoBase.setUsername(applicationName);
在標記 (1) 和 (2) 處,未受信任的使用者名稱字串被解析為一個名稱和一個身份聲明,且未經任何驗證。在 (3) 處,該聲明被傳遞給下游,就好像它是一個與信任相關的參數,而非不透明的使用者提供文字。後續被呼叫的函式則做出了決定性的錯誤:
- // Program 2 — Vulnerable identity-resolution logic
- // (remote application authentication, main-article source)
- private void authenticateRemoteApplication(String applicationName, String suppliedSicDn)
- throws AuthenticationFailureLoginException {
- // (1) the attacker-supplied claim silently overrides the authenticated
- // peer certificate DN whenever it is present
- String effectiveSicDn = suppliedSicDn == null
- ? this.j.getCertificateDnName() // only used as a fallback
- : suppliedSicDn; // <-- untrusted value wins
- CpAssert.cpassert(StringUtils.isNotEmpty(effectiveSicDn), "User DN name is not set");
- if (effectiveSicDn.equals("CN=siclocal")) {
- this.authenticateLocal(applicationName);
- } else {
- // (2) the untrusted DN is used directly to select the login domain/identity
- this.t.identifyDomainForRemoteLogin(effectiveSicDn);
- }
- }
標記 (1) 的這一行將兩個概念上截然不同的值 — 從 TLS/SIC 層經由
getCertificateDnName()
取得的已驗證節點身份,以及應用程式資料中所攜帶的未受信任聲明 — 摺疊到同一個變數中。由於三元運算子(ternary)只要提供的值非 null 就使用該值,因此當攻擊者提供任何 DN 字串時,已驗證的身份函式就永遠不會被諮詢。隨後該值在 (2) 處被用來解析管理網域,這意味著一個已經知道或被動觀察到管理伺服器自身 SIC DN 的攻擊者,可以聲稱該身份,並被當作已出示該身份憑證來處理。
4. 廠商修補程式改變了什麼
比較有漏洞的中間版本與之後的修補版本,可以發現修復方式恢復了聲稱身份與已驗證傳輸身份之間的強制綁定,並僅為本地迴路 (loopback) 流量明確保留了例外。
- // Program 3 — Patch diff: mandatory binding to the authenticated peer DN
- private void authenticateRemoteApplication(String applicationName, String suppliedSicDn)
- throws AuthenticationFailureLoginException {
- - String effectiveSicDn = suppliedSicDn == null
- - ? this.j.getCertificateDnName()
- - : suppliedSicDn;
- - CpAssert.cpassert(StringUtils.isNotEmpty(effectiveSicDn), "User DN name is not set");
- + String effectiveSicDn;
- + String certificateDn = this.j.getCertificateDnName();
- + String remoteIp = this.j.getRemoteIpAddress();
- + // (2) a claimed DN is honored ONLY for loopback local-SIC traffic
- + boolean localSic = IpUtils.isLoopback(remoteIp) && "CN=siclocal".equals(certificateDn);
- + if (localSic && suppliedSicDn != null) {
- + effectiveSicDn = suppliedSicDn;
- + } else {
- + // (3) remote peers are now bound to the authenticated certificate DN
- + effectiveSicDn = certificateDn;
- + boolean mismatch = suppliedSicDn != null
- + && StringUtils.isNotEmpty(certificateDn)
- + && !suppliedSicDn.equalsIgnoreCase(certificateDn);
- + if (mismatch) {
- + // (4) any forged claim that disagrees with the certificate is rejected
- + throw new AuthenticationFailureLoginException(
- + "Remote authentication failed for peer " + remoteIp + ".");
- + }
- + }
- + if (Strings.isNullOrEmpty(effectiveSicDn)) {
- + // (5) no authenticated identity at all -> reject, closing the
- + // "present no certificate" bypass path used by an unauthenticated PoC
- + throw new AuthenticationFailureLoginException(
- + "Remote authentication failed for peer " + remoteIp + ".");
- + }
- if (effectiveSicDn.equals("CN=siclocal")) {
- this.authenticateLocal(applicationName);
- } else {
- this.t.identifyDomainForRemoteLogin(effectiveSicDn);
- }
- }
此修復遵循標準的信任邊界強化模式:對於任何遠端節點,僅從已驗證的通道推導身份 (3);將用戶端提供的聲明視為參考資訊而非權威資訊,除非它與已驗證的身份完全一致 (4);若完全不存在經驗證的身分,則採取失敗封閉(fail closed)策略(5)。在 (2) 處狹窄的迴路例外,保留了合法的本地跨程序應用登入,同時消除了可透過網路達成的繞過。
5. 協定層級的攻擊流程
一旦理解身份替換的缺陷,攻擊序列便可直接推導出來:攻擊者完成 SIC 啟動程序中未經驗證的部分,提交一個憑證綁定請求,其 DN 欄位重複管理伺服器自身的身份字串,但未出示任何用戶端憑證,並由於第 3 節所述的缺陷而被授予應用程式 Session。然後,該 Session 被用來請求一個開啟資料庫的物件,從中提取一個不透明的應用程式 Token。該應用程式 Token 接著被用來請求主控台應用程式的 Single-sign-on ticket,並透過 SOAP 登入介面兌換該 ticket,以獲得一個具有完整權限的管理 Session 識別碼。以下總結了此順序。
6. 授權繞過加劇了此缺陷
第二個促成此弱點的因素存在於原生命令授權常式中,該常式決定用戶端是否被允許請求新的 Single-sign-on ticket。該常式並未統一應用一般的每個命令權限遮罩檢查,而是對 ticket 生成命令給予特殊處理:只要用戶端已被視為設定管理員 — 而在身份替換繞過之後,偽造的應用程式 Session 已經滿足了這個條件。
- // Program 4 — Native authorization short-circuit for ticket generation
- // (fwm_is_authorized, main-article source, decompiled)
- _BOOL4 __cdecl fwm_is_authorized(int a1, int a2, int a3)
- {
- const char *v11;
- int v9, v10, v12[7];
- v11 = *(const char **)a2; // requested command name
- v10 = CPMIGetClientPermission(a1);
- v12[0] = 0;
- v9 = CPMIGetClientAdvancedPermission(a1);
- fwobj_getint(a1, g_szCPMI_SOAP_LOCAL_BIND, v12);
- if ( v12[0] != 1 )
- {
- if ( is_fwmalert_client(a1) && strcmp(v11, "fwm-alert") )
- return 0;
- // (1) "gen-sso-token" bypasses the normal permission-mask checks below
- // whenever the caller is already recognised as a config admin
- if ( strcmp(v11, "gen-sso-token") || !fwm_isCpconfigAdmin(v11) )
- {
- // normal per-command permission checks would run here
- return 0;
- }
- }
- return 1; // authorized
- }
由於第 5 節中的偽造工作階段已經滿足了 config-administrator 檢查,(1) 的捷徑使得 ticket 請求得以成功,而無需評估通常會限制此類敏感操作的權限位圖。這第二層設計意味著,只要登入邊界出現一次身分替代錯誤,就足以直接取得完整的管理 ticket 簽發。
7. 透過 SOAP 兌換 ticket
偽造的 ticket 最終透過一般的 SOAP 登入端點來兌換成主控台 Session, ticket 作為 Session 驗證憑證而非使用者名稱/密碼對來傳送。
- <!-- Program 5 — SOAP ticket-redemption request (main-article source) -->
- <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
- xmlns:l="http://www.checkpoint.com/DleWebService/LoginSvcRemote"
- xmlns:d="http://www.checkpoint.com/management/objects/schema/DleServerCoreSvc"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
- <soap:Body>
- <l:loginNew>
- <d:loginRequest>
- <d:applicationName>SmartConsole</d:applicationName>
- <d:domain>a0eebc99-afed-4ef8-bb6d-fedfedfedfed</d:domain>
- <!-- authentication is asserted purely via the forged SSO token,
- not via any password credential -->
- <d:authenticationInfo xsi:type="d:UserSSOTokenAuthenticationInfo">
- <d:username>system_admin</d:username>
- <d:SSOToken>512d49aa4c026d57177bea06dd28669c889479bfa8ea6d3b53fabe59ec9e0a2e</d:SSOToken>
- </d:authenticationInfo>
- </d:loginRequest>
- </l:loginNew>
- </soap:Body>
- </soap:Envelope>
回應會回傳一個 Session 識別碼和用戶端 Session 識別碼配對,主控台用戶端隨後會將此配對附加到所有後續請求中,從而完成從未經驗證的網路位置到權限管理 Session 的轉換 [1] 。
8. 討論:身份轉送繞過中的重複模式
核心缺陷 — 接受自我聲明的身份來取代經密碼學驗證的身份 — 是更廣泛的驗證轉送漏洞中反覆出現的主題。針對 Windows 驗證協定的強制與轉送技術,同樣利用了服務認為自己在與誰通訊,與實際綁定到傳輸通道的身份之間的差異 ,透過強制特權程序向攻擊者控制的端點進行驗證 ,然後將捕獲的材料在其他地方重放。中間人釣魚基礎設施在 Session 層也表現出類似的失敗模式,其中被擷取或偽造的 Session Token 被依賴服務視為等同於剛完成的多因素驗證儀式 ,只要 Token 本身被視為足夠的身份證明,而不管它是如何獲得的 。CVE-2026-16232 屬於相同的廣泛類別:一個核發 Token 的服務信任關於身份的聲明,而不是驗證該聲明是否與獨立驗證的通道相符,而一旦這種信任被錯置,使用該 Token 所建立的每個下游元件都會繼承受損的信任假設。
9. 結論
此處研究的漏洞顯示,橋接兩種身分驗證方式——使用憑證的傳輸層與應用層的工作階段——需要在兩者之間建立嚴格且強制性的綁定。只要存在一個可選的後備機制,允許客戶端提供的資料取代已驗證的傳輸層身分,就足以破壞整個驗證邊界;而針對敏感 ticket 簽發命令的次級授權捷徑,則進一步將影響擴大為完整的管理層級入侵。第 4 節分析的修補程式展示了正確的修補模式:先進行驗證,並將身分無條件綁定到該驗證以應對遠端對等方;若不存在任何已驗證的身分,則系統必須採取失敗封閉策略。