為什么我們需要多個SAML IDP簽名密鑰?
SAML是一種目前應用非常廣泛的單點登錄協議,如果你運行SAML服務器并與許多其他站點集成,那么幾乎可以肯定你使用的是不安全的設置。SAML安全面臨的最大威脅不是怪異的XML邊緣案例或黑客竊取你的簽名密鑰,而是低質量的第三方實現,這允許你的用戶登錄到你認為他們無法訪問的應用程序。要確保SAML斷言只適用于正確的應用程序,請為每個應用程序或服務提供者使用惟一的簽名密鑰。
這個問題并不是SAML獨有的,簽名的JWT和其他SSO的使用(比如OIDC中的使用)也可能遇到類似的問題,即缺少令牌驗證。
SAML是如何工作的?
從較高的層次上講,SAML是一種登錄用戶的方式,它使用兩個系統之間的瀏覽器內部通信,否則它們之間不能相互通信。當用戶想要登錄到他們最喜歡的SaaS時,SaaS應用程序(SP或服務提供商)會將一些關于登錄請求的數據發送到你的IDP。這包括諸如惟一請求ID和數據(如你試圖訪問的原始頁面)之類的內容。理論上,身份驗證請求可以指定IDP應該返回哪種類型的用戶名和名稱之類的字段,但實際上這會被忽略。這些請求也可以簽名,但實際上在SP旋轉其密鑰時,大多數情況都會使事情中斷。安全性幾乎沒有好處,因為在任何現代系統中,SAML交換都是通過TLS進行的。

一旦你的IDP接收到身份驗證請求,IDP將驗證你是否已登錄(可能是密碼、可能是客戶端證書),然后簽署一個斷言。斷言是SP將驗證并用于登錄你的內容。然后,你的IDP將此斷言發送回SP。SP驗證密碼簽名,驗證該斷言是否應該發送到特定的SP,并提取相關的用戶名和其他字段。現在,你可以看所有你想看的圖片了!
那個簽名秘鑰聽起來很嚇人!你的本能可能是不惜一切代價保護該密鑰。密鑰值得保護,但是對你的SAML IDP安全最大的可信威脅不是擁有你的SAML服務器的攻擊者。
攻擊SAML的方法
攻擊SAML的方法有很多!盡管獨特的簽名密鑰可以解決其中的一些問題,但這并不是萬能的。
受眾限制問題(Audience restriction issue)
這是我在這篇文章中關注的問題,稍后我將更詳細地討論它。不出意料,惟一簽名密鑰解決了這類問題。
- IDP簽名密鑰被盜:確實,能夠訪問你的IDP的人可以獲得簽名密鑰的副本,并以任何人的身份登錄到與你集成的任何網站。如果這是你所關心的威脅,則僅提供簽名預言的硬件支持的密鑰是正確的防御措施。
- XML和XML安全庫:如果可以的話,你應該使用一個內存安全的庫。也就是說,這對第三方的斷言驗證沒有實際影響,而且如果使用惟一簽名密鑰,你的安全狀態也不會改變。
XML處理問題
XML安全性是本世紀初出現的一種內聯簽名格式,當時沒有人提出要求,需要它的人也更少。但用的人多了,問題就出現了,這些問題包括,忽略用戶名中的XML注釋、簽名格式本身忽略對XML解析器有影響的注釋,以及不檢查你驗證的簽名是否實際覆蓋了你信任的所有數據。
你可以通過使用惟一的簽名密鑰來減小這些問題的影響范圍。你只需要關心單個應用程序的內部權限,而不是允許用戶登錄任何與你集成的服務(授權與否)。

解決方案

核心問題是缺乏受眾限制驗證,換句話說,SP沒有檢查斷言是否針對它。SAML的設計思想是你的IDP將只有一個簽名密鑰,你可以將它分發給與你集成的每個人。考慮到SAML的學術背景,它應該是一個合作協議,組織之間密切合作。現代企業SAML忽略了所有這些有趣的特性,因為它們是巨大的安全和配置噩夢。
當你的IDP簽署一個斷言時,它包含兩個供SP驗證的字段:SP的實體ID和斷言要發送到的URL。SP可以悄悄地忽略這些字段,而你對此無能為力。
作為負責簽名密鑰的IDP,你如何保護自己免受不可避免的弱SP攻擊?
處理一堆簽名鑰匙
相比依賴協議的某些部分,唯一可擴展的方法是強制你的斷言僅在一個SP上有效,而且是通過具有唯一簽名每個SP的密鑰。

之所以可行,是因為幾乎每個SAML SP實現都包含三個部分。他們將從請求中提取用戶名,在斷言中驗證簽名,并拒絕無效的斷言簽名,其他所有內容都應視為可選內容。
雖然SP可以忽略你的簽名,但是它的測試超級簡單,而且這種事情很容易被漏洞賞金報告人員發現。與更深奧的受眾限制測試不同,這里不涉及任何復雜性。
如何管理這么多密鑰?

過去,我通過編寫一堆Ruby自動生成相關XML來處理每個SP的唯一簽名密鑰,從而為Shibboleth管理了多個密鑰。每當我遇到另一個錯誤處理斷言的SP時,我都會感謝為減少這個我們不得不擔心的問題而付出的努力。
在理想的情況下,我們不會使用SAML。 SAML是一種繁瑣的協議,可讓你創建帶有身份驗證內聯簽名的身份提供者的網狀網絡,其中XML中的空格確定簽名是否有效。但是SAML以及OAuth 2.0和不完美的OIDC都將保留下來。鑒于SAML是事實上的企業單一登錄協議,我們將忽略它。
如果你的IDP不支持此功能(請參見下文),則應向他們打開功能請求!這是你的IDP應該支持的重要安全控制。
如果你的IDP確實支持這個功能,為你的新應用程序發出每個sp的簽名密鑰。使用舊的證書遷移應用程序需要做很多工作,但是如果你有特別敏感的應用程序,則值得這樣做。
所有SaaS IDP都應在沒有任何用戶干預的情況下生成每個應用程序的簽名密鑰,默認情況下,每個SP密鑰的使用率極高,可以悄悄地提高與這些提供商簽約的每個企業的安全性。截至2020年3月,唯一獲得此權限的提供商是Azure AD。
自托管的IDP應確保它們支持按SP的簽名密鑰,并具有啟用此功能的文檔。理想情況下,共享簽名密鑰的配置不太明顯,因此管理員默認情況下選擇每個SP的簽名密鑰。
雖然最終要由SSO管理員做出正確的SSO選擇,但是我們作為安全行業的責任是使正確的選擇變得容易。
IDP支持多個簽名密鑰
沒有實施指南,最佳做法將無濟于事。這是截至2020年3月我已測試的各種主要IDP(包括SaaS和自托管選項)的列表。如果你的首選IDP不在此列表中或條目不正確,請與我們聯系。
1. Azure AD – SaaS
Azure AD自動為每個“企業應用程序”生成一個新密鑰,并且無法在控制臺中的應用程序之間共享證書。你可以手動上傳自己的證書和私鑰,但這并不容易,我也不鼓勵這樣做,Azure AD應該是所有其他SaaS IDP的模型。
2. Shibboleth - Java(自托管)
你必須編寫大量的XML才能使它工作,如果你花了幾個小時絞盡腦汁地研究Spring XML配置,就不會出現任何問題。我已經包括了基本的需求。要點是,你需要創建單獨的簽名憑據,包括安全配置中的簽名憑據,然后從單獨的SP引用該安全配置。
另外,我確實喜歡Shibboleth是全java的狀態,即沒有內存損壞!,可以在本地自己的服務器上運行,并且采用非常符合標準的方法,從而降低了被奇怪的XML問題影響的可能性。
conf/relying-party.xml的示例配置(Shibboleth文檔):
- < util:list id="shibboleth.RelyingPartyOverrides" >
- < bean parent="RelyingPartyByName"
- c:relyingPartyIds=" >
- < property name="profileConfigurations" >
- < list >
- < bean parent="SAML2.SSO"
- p:securityConfiguration-ref="local.ExampleSecConf" / >
- < /list >
- < /property >
- < /bean >
- ...
- < /util:list> < property name="signatureSigningConfiguration" >
- < bean parent="%{idp.signing.config}"
- p:signingCredentials-ref="local.ExampleSignCred" / >
- < /property>
- ... ... ...
conf/credentials.xml的示例配置(Shibboleth文檔):
- < util:list id="shibboleth.SigningCredentials" >
- < ref bean="shibboleth.DefaultSigningCredential"/ >
- < ref bean="local.ExampleSignCred"/>
- ...
- < bean id="local.DefaultSigningCredential"
- class="net.shibboleth.idp.profile.spring.factory.BasicX509CredentialFactoryBean"
- p:privateKeyResource="%{idp.home}/credentials/example.key"
- p:certificateResource="%{idp.home}/credentials/example.pem"
- p:entityId-ref="entityID" / >
- ...
3. PingOne—SaaS
這不是默認的,因為在默認情況下,PingOne使用一個共享簽名密鑰。有一個單獨的證書頁面,你可以在其中創建新的證書。添加一些內容后,你可以將每個SAML“應用程序”配置為使用唯一的簽名密鑰。

4. OneLogin—SaaS
這不是默認的,因為在默認情況下,OneLogin使用一個共享簽名密鑰。你可以嘗試在單個SAML配置的設置中更改此秘鑰,但不能添加新的秘鑰。你必須導航到單獨的“證書”頁面以創建新證書,但是一旦完成,就可以為每個SP創建唯一的簽名密鑰。

一旦添加了“證書”(實際上是一個簽名密鑰),就可以將它分配給任意的SP。
5. Okta—SaaS
是的,但是需要在API中進行修改。Okta為每個SP使用不同的實體ID,但是默認情況下,使用完全相同的憑據對聲明進行簽名。無法在Okta控制臺中上傳自定義私鑰或旋轉簽名憑證。
但是,你可以創建一個新的簽名密鑰,并通過兩個API調用將密鑰與應用程序關聯。
6. GSuite SAML - SaaS
GSuite的SAML配置允許你在給定的時間內擁有兩個簽名證書,這樣你就可以旋轉過期的簽名證書。很明顯,GSuite可以支持其他證書,但它不支持。
7. Auth0——SaaS
盡管支持唯一的OAuth 2.0客戶端機密,但Auth0在所有SAML“應用程序”之間共享一個簽名證書!也沒有選擇旋轉你的SAML簽名憑據。鑒于你正在動態配置每個SP,因此沒有理由不生成每個SP的簽名憑據。
8. ADFS - Microsoft Windows Server(自托管)
Windows Server 2019版的ADFS不支持每個“依賴方”(我們稱之為SP)的唯一“簽名令牌”。在運行一些PowerShell以禁用自動旋轉之后,你可以手動添加一個用于旋轉的備用證書,但它對其他任何東西都沒有用處。
在每個SP上運行一個ADFS服務器并在每個服務器上使用單獨的簽名令牌在技術上是可行的。這將是痛苦的管理,更不用說Windows許可成本,所以我不認為這是一個好的建議。
9. Gluu——自托管
Gluu的用戶界面未提供任何將特定簽名身份與SAML SP相關聯的方法,也無法創建新的簽名身份。
10. Duo Access Gateway –自托管
根據通用SP配置的文檔,整個DAG服務器只有一個證書。你可以重新創建證書,但這似乎會影響到沒有唯一密鑰選項的每個SP。
11. SimpleSAMLphp—自托管
SimpleSAMLphp的IDP支持單個服務提供者的唯一密鑰,一旦你知道要查找什么,它就很簡單了。在“SP遠程元數據”參考中,通過signature.certificate和signature.privatekey可以為每個SP指定一個單獨的密鑰。
雖然它確實很好地支持惟一秘鑰,但如果可以的話,你最好不要使用這個軟件。該項目有一個經典的PHP webapp漏洞。
總結
- 有一些深奧的功能,例如動態ACS(斷言消費者服務)URL,還有可能會被誤用的功能,例如通過未加密的HTTP提供元數據,但是同樣,在現代公司SAML中,TLS至關重要。
- 我不相信libxmlsec1庫,尤其是libxml2庫。這兩個C庫都非常常用,沒有真正的替代方法。如果你認為使用Ruby,PHP或Python SAML庫是安全的,那么你就錯了,它們都依賴于libxmlsec1。
- 盡管C庫為我們服務了很多年,但到2020年,由于內存損壞問題嚴重,它將成為安全負擔。libxml2的漏洞歷史可以追溯到2004年(16年前!)。雖然libxmlsec1沒有相同的記錄歷史,但我懷疑只是由于缺少必要的報告,而不是真的沒有內存安全問題。通過對已知漏洞進行相對快速的修補來積極地維護這些庫,可以在一定程度上緩解這種危險,但是如果可以的話,我不會使用這些庫。
- 就我個人而言,我認為對于安全界的外行來說,進入的門檻是相當高的!你必須與第三方進行有效的SSO集成,并且必須能夠訪問私鑰(我們已經不太可能使用它了)或能夠更改部分斷言(如用戶名)。大多數IDP都不愿意讓你更改字段,因為它們是從你無權訪問的中央目錄中提取的。即使確實滿足所有這些條件,這些漏洞通常也只能讓你在現有組織中橫向移動,而不能完全以其他帳戶身份登錄。
- 接受不同組織的斷言實際上是一件非常可怕的事情,因為作為IDP,你幾乎無能為力。保護自己不受攻擊的最好方法是測試SP是否有這種行為。不過,我在本文中沒有深入探討這個問題,
- Gluu是基于Shibboleth的,因此你可以手動設置一個工作配置,這可能會破壞UI。