ปัญหา
บริษัทหนึ่งมี web application อยู่หลายสิบตัว หลายตัวเป็น SaaS: ค่าใช้จ่าย การเดินทาง HR และ CRM ถ้าทุกตัวเก็บ password ของตัวเอง แต่ละตัวก็ต้องมีระบบ multi-factor และการ reset password ของตัวเอง คนทำ phishing ก็มีหน้า login ให้เลียนแบบหลายสิบหน้า และไม่มีใครจำได้ว่าต้องปิดบัญชีของคนที่ลาออกไปให้ครบทุกที่ บริษัทอยากมีที่เดียวให้คนเข้าสู่ระบบ ภายใต้ policy ของบริษัทเอง แล้วให้ทุกแอปเชื่อการเข้าสู่ระบบนั้นโดยไม่เคยเห็น password เลย
ตัวแอปกับ identity provider มักเป็นของคนละองค์กรและอยู่คนละ domain เลยใช้ cookie หรือ database ร่วมกันไม่ได้ สิ่งที่ต้องมีคือ message มาตรฐานที่ฝั่งหนึ่งสร้างได้ อีกฝั่งตรวจได้ และ browser ของผู้ใช้พาไปมาระหว่างสองฝั่งได้ SAML 2.0 ที่เป็น OASIS Standard ตั้งแต่มีนาคม 2005 กำหนด message นั้นกับ protocol รอบ ๆ มันไว้ และทุกวันนี้ก็ยังใช้กันกว้างขวางสำหรับ web single sign-on ในองค์กร
ทำงานยังไง
ผู้เกี่ยวข้อง identity provider (IdP) ยืนยันตัวตนผู้ใช้และรับรองให้ ส่วน service provider (SP) คือแอปที่เชื่อคำรับรองนั้น แต่ละฝั่งมี entity ID เป็น URI (มักเป็น URL) ที่ใช้เป็นชื่อเฉพาะของมัน และไปโผล่เป็น Issuer กับ Audience ใน message ก่อนจะมีใครเข้าสู่ระบบ ทั้งสองฝั่งต้องแลก metadata กันก่อน: ฝั่ง SP ให้ entity ID ของตัวเองกับ Assertion Consumer Service (ACS) URL ที่เป็น endpoint รับ response ส่วนฝั่ง IdP ให้ entity ID ของตัวเอง single sign-on (SSO) URL และ certificate ของ key ที่ใช้ sign assertion
Assertion คือคำรับรองเกี่ยวกับผู้ใช้ที่ IdP เขียนเป็น XML แล้ว sign ไว้:
Issuer: entity ID ของ IdPSignature: enveloped XML signature ที่มี reference ตัวเดียว ชี้ไปที่IDของ assertion เอง (SAML Core §5.4)Subject:NameIDที่ระบุตัวผู้ใช้ กับSubjectConfirmationแบบ bearer ที่มีข้อมูลบอกว่าส่ง assertion ไปที่ไหนได้ (Recipientคือ ACS URL) ใช้ได้ถึงเมื่อไร (NotOnOrAfter) และเป็นคำตอบของ request ไหน (InResponseTo)Conditions: ช่วงเวลาที่ใช้ได้ (NotBefore,NotOnOrAfter) และAudienceRestrictionที่ระบุ entity ID ของ SPAuthnStatement: ผู้ใช้ยืนยันตัวตนเมื่อไรและด้วยวิธีไหน กับSessionIndexที่ตอน logout จะอ้างถึงAttributeStatement: attribute อย่างอีเมล ชื่อที่แสดง และ group ที่เป็นสมาชิก
NameID format (SAML Core §8.3) บอกว่า subject เป็น identifier แบบไหน identifier แบบ persistent เป็นค่าสุ่มเทียมที่อ่านความหมายไม่ออก โดย IdP สร้างให้ service provider ตัวเดียวหรือกลุ่มเดียว (เป็น pairwise pseudonym ยาวไม่เกิน 256 ตัวอักษร) สองแอปเลยใช้มันเชื่อมโยงผู้ใช้คนเดียวกันไม่ได้ ส่วน identifier แบบ transient เป็นของชั่วคราว ไม่ใช้ซ้ำข้ามการเข้าสู่ระบบ แบบ emailAddress สะดวกดี แต่อีเมลเปลี่ยนได้และถูกเอาไปให้คนอื่นใช้ต่อได้ แล้ว unspecified ก็ให้ IdP เลือกเอง นอกจากนี้ยังมีการกำหนด X.509 subject name, Windows domain name, Kerberos principal และ entity identifier ไว้ด้วย อย่าง Microsoft Entra ID ก็ ออกค่าแบบ pairwise ให้ persistent (และให้ unspecified ด้วย) และออกค่าสุ่มใหม่ทุกครั้งที่เข้าสู่ระบบให้ transient
Binding บอกว่า SAML message เดินทางผ่าน HTTP ยังไง (SAML Bindings):
- HTTP-Redirect เอา XML ไป deflate แล้ว encode เป็น base64 และ URL-encode จากนั้นใส่ไว้ใน query parameter
SAMLRequest(หรือSAMLResponse) ตัว signature อยู่ใน XML ต่อไม่ได้ ผู้ส่งเลย sign query string แทน แล้วเพิ่ม parameterSigAlgกับSignatureเข้าไป มันเหมาะกับAuthnRequestที่มีขนาดเล็ก - HTTP-POST ใส่ XML ที่เป็น base64 ไว้ใน hidden field ของ form ที่ submit ตัวเอง ส่วน response ใช้วิธีนี้: Web Browser SSO profile ห้ามใช้ HTTP-Redirect กับ response เพราะ response มักยาวเกินกว่าจะใส่ใน URL ได้
- HTTP-Artifact ส่งแค่ reference สั้น ๆ ผ่าน browser แล้วฝั่งรับก็ไปดึง message มาเองผ่าน back channel (มักเป็น SOAP) ที่ต้องยืนยันตัวตนกันทั้งสองฝั่ง วิธีนี้ทำให้ assertion ไม่ต้องผ่าน browser แต่ต้องแลกกับการเรียกระหว่าง server กับ server
Profile เอา message กับ binding มารวมกันสำหรับ use case หนึ่ง ๆ Web Browser SSO profile (SAML Profiles §4.1) คือ flow ใน diagram ส่วน profile อื่นก็ครอบคลุม single logout, client และ proxy แบบ enhanced ที่คุยกับ IdP เอง (ECP) และ artifact resolution
การเข้าสู่ระบบแบบ SP-initiated (step 1 ถึง 3) Expense สร้าง <AuthnRequest> ที่มี ID ใหม่ ใส่ entity ID ของตัวเองเป็น Issuer ใส่ ACS URL ของตัวเองและ binding ที่อยากให้ใช้ส่งคำตอบ เก็บ ID ไว้ใน session ของ Ana แล้ว redirect browser ของเธอไปที่ SSO URL ของ IdP ฝั่ง IdP ต้องตรวจว่า ACS URL นั้นเป็นของ SP ตัวนั้นจริง (Profiles §4.1.4.1) ไม่อย่างนั้น attacker ก็สั่งให้ส่ง assertion ไปที่ URL ไหนก็ได้ตามใจ หลังยืนยันตัวตน Ana แล้ว IdP ก็ตอบกลับเป็นหน้าที่ POST SAMLResponse ไปที่ ACS URL จากนั้น Expense ก็ตรวจมัน map ผู้ใช้ แล้วตั้ง session cookie ของตัวเอง พอทีหลัง Travel ส่ง Ana ไปที่ IdP ตัว session cookie ของ IdP เองก็ทำให้เธอไม่ต้อง login รอบสอง: นี่แหละคือ single sign-on
การเข้าสู่ระบบแบบ IdP-initiated (step 4) profile นี้ยังให้ IdP ส่ง response ที่ ไม่มีใครขอ มาได้ด้วย เช่น ตอนผู้ใช้คลิก tile ของแอปบน portal ของ IdP (Profiles §4.1.5) response แบบนี้ต้องไม่มี InResponseTo และปกติจะส่งไปที่ ACS endpoint ตัว default ของ SP แล้ว IdP ก็อาจใส่ RelayState ที่บอกว่าจะเปิดหน้าไหน ตามที่ตกลงกับ SP ไว้ก่อน
การตรวจ response SAML Profiles §4.1.4.3 วางกฎไว้ และ OWASP SAML Security Cheat Sheet ก็เพิ่มเรื่อง hardening ส่วนตัว library ของคุณควรทำทั้งหมดนี้ให้ แต่คุณก็ควรรู้ว่ามันทำอะไรบ้าง:
- Parse แบบระวังตัว ปฏิเสธ DTD และการประกาศ
DOCTYPE(แนวทางป้องกัน XXE ของ OWASP) และ validate document กับสำเนา SAML schema ที่เก็บไว้ในเครื่องและเชื่อถือได้ - ตรวจ response: status เป็น
SuccessมีDestinationตรงกับ ACS URL ที่มันมาถึง (บังคับเมื่อ response ถูก sign) และมีInResponseToตรงกับ request ที่ browser session นี้ยังรอคำตอบอยู่ แล้วก็ mark ว่า request นั้นใช้ไปแล้ว - ตรวจ signature ด้วย key จาก metadata ของ IdP ห้ามใช้ certificate ที่ message แนบมาเองใน
KeyInfoรับแค่ algorithm ที่คาดไว้ (OWASP ขอให้ใช้ RSA-SHA256 หรือที่แข็งแรงกว่า และไม่ใช้ SHA-1) และรับแค่ transform แบบ enveloped-signature กับ exclusive canonicalization (Core §5.4.4) ดูให้แน่ใจด้วยว่า reference ชี้ไปที่ element ตัวเดียวกับที่คุณจะอ่านต่อ Issuerของ assertion ตรงกับ entity ID ของ IdP- Subject confirmation: มี
SubjectConfirmationDataแบบ bearer ที่Recipientเป็น ACS URL ของคุณNotOnOrAfterยังไม่ผ่านไป และInResponseToเป็น request ID ของคุณ (ไม่มีได้ก็ต่อเมื่อคุณรับ response ที่ไม่มีใครขอ) - Conditions:
NotBefore≤ ตอนนี้ <NotOnOrAfterโดยเผื่อเรื่องนาฬิกาคลาดกันไว้นิดหน่อย มีAudienceตรงกับ entity ID ของคุณ และปฏิเสธ condition ไหนก็ตามที่คุณไม่เข้าใจ - Replay: จำ
IDของทุก assertion ที่รับไปแล้วไว้จนกว่าNotOnOrAfterของมันจะผ่านไป แล้วปฏิเสธตัวที่ซ้ำ (Profiles §4.1.4.5 บังคับเรื่องนี้สำหรับ POST binding) - อ่านแค่ส่วนที่ถูก sign: เอา
NameIDกับ attribute มาจาก element ที่ signature ครอบคลุม โดยหาจากตำแหน่งของมัน ไม่ใช่ค้นหา<Assertion>หรือNameIDตัวแรกใน document - ข้อจำกัดของ session: ถ้า
AuthnStatementมีSessionNotOnOrAfterให้จบ session ของคุณภายในเวลานั้น และเช็ก authentication context ด้วยถ้าแอปต้องใช้ MFA
มันคือวินัยแบบเดียวกับ JWT validation บวกกับทางพังแบบอื่น ๆ ที่ XML แถมมาให้
Metadata และ certificate rollover SAML metadata (หนึ่ง EntityDescriptor ต่อฝั่ง) บอก endpoint และ binding ของแต่ละตัว, key ที่ mark ไว้สำหรับ signing หรือ encryption และ flag อย่าง WantAuthnRequestsSigned ของ IdP กับ AuthnRequestsSigned และ WantAssertionsSigned ของ SP ส่วน validUntil กับ cacheDuration กำหนดว่าสำเนาหนึ่งใช้ได้นานแค่ไหน signing certificate มีวันหมดอายุ อย่าง Microsoft Entra ID ก็ สร้าง self-signed certificate อายุสามปีให้ SAML application แต่ละตัว และส่งอีเมลเตือนก่อนหมดอายุ 60, 30 และ 7 วัน การ rollover ที่ไม่ทำให้ระบบล่มจะเผยแพร่ certificate ใหม่ก่อน ให้ SP แต่ละตัวเพิ่มมันจาก metadata เป็น key ที่เชื่อถือตัวที่สอง แล้วค่อยเปลี่ยนไป sign ด้วยตัวใหม่ และค่อยปลดตัวเก่าหลังจากนั้น Microsoft ขอให้ SaaS vendor อ่าน metadata URL แยกตามแอปของลูกค้าแต่ละราย เช็กอย่างน้อยทุก 24 ชั่วโมง และเก็บ signing certificate ไว้ทั้งตัวหลักและตัวสำรอง ส่วน SP ที่ก็อป certificate มาแปะเองด้วยมือจะปฏิเสธการเข้าสู่ระบบทุกครั้งตั้งแต่วินาทีที่ IdP เปลี่ยน
ใช้ตอนไหนดี
- Web application และ SaaS ในองค์กร ที่ผู้ใช้เข้าสู่ระบบผ่าน IdP ของบริษัท ส่วน IdP สำหรับพนักงานอย่าง Microsoft Entra ID และ Okta ก็มีเอกสารเรื่องการเชื่อม SAML สำหรับงานนี้
- แอปที่พูด SAML อยู่แล้ว หรือลูกค้าที่ IdP ไม่มีทางเลือกอื่นให้ แนวทางการวางแผน ของ Microsoft คือให้เลือก OpenID Connect ถ้าแอปรองรับ และใช้ SAML กับแอปเดิมที่ไม่ได้ใช้ OpenID Connect หรือ OAuth
- ไม่ใช่สำหรับ native mobile app, single-page app หรือ API Web Browser SSO profile ส่งคำรับรองที่ sign แล้วให้ web app ฝั่ง server ผ่านการ POST ของ browser และไม่ได้ให้อะไรไว้เรียก API เลย OpenID Connect ที่อยู่บน OAuth 2.0 Authorization Code + PKCE ครอบคลุมกรณีพวกนี้: ID token แบบ JSON สำหรับแอป access token สำหรับ API และ authorization code ที่เอาไปแลกผ่าน back channel ทำให้ token ไม่เคยไปอยู่ใน URL หรือ form ส่วน IdP หลายตัวก็พูดได้ทั้งสองแบบ และ federation broker ก็พูด SAML กับ IdP ของลูกค้า แล้วพูด OpenID Connect กับแอปของคุณได้ (ดู Federated Identity)
ได้อะไร เสียอะไร
- XML signature ทำผิดได้ง่าย canonicalization, ID และ reference เปิดช่องให้ parser อ่านอะไรบางอย่างที่ไม่ใช่สิ่งที่ถูก sign และประวัติการโจมตีด้านล่างก็ยาวมาก ให้ใช้ library ที่ยังมีคนดูแล และ patch ให้ทันเสมอ
- assertion เป็น bearer credential ที่วิ่งใน front channel ใครถือมันไว้ก็ใช้มันได้จนกว่าจะหมดอายุ และมันวิ่งผ่าน browser, extension ของ browser และอะไรก็ตามที่ log traffic ไว้ อายุที่สั้น การตรวจ
Recipient,Audienceและ replay และการเข้ารหัส (ถ้าเลือกใช้) ช่วยจำกัดความเสียหาย - ต้อง config แยกทุกแอป SP กับ IdP ทุกคู่ต้องแลก metadata กัน ตกลง NameID format กับชื่อ attribute และต้องรอดผ่าน certificate rollover ให้ได้ ถ้ามีแอปหลายร้อยตัวก็เป็นงาน operation จริงจัง
- Logout ไม่แข็งแรง การจบ session หนึ่งแทบไม่เคยจบ session อื่นได้อย่างเชื่อถือได้ (ดูด้านล่าง)
- attribute เป็นภาพ ณ ตอนนั้น group membership คือค่าตอนเข้าสู่ระบบ ส่วน group ที่ถูกเอาออกหรือบัญชีที่ถูกปิดจะเห็นผลก็ตอนเข้าสู่ระบบครั้งถัดไป ยกเว้นคุณจะ provision ผ่าน SCIM ด้วย หรือให้ session สั้น
ข้อควรรู้ตอนลงมือทำ
- ใช้ library ที่ยังมีคนดูแล หรือใช้ broker อย่าเขียนการตรวจ XML signature เอง library ที่ยังออก release ใหม่อยู่ก็มี SAML 2.0 support ของ Spring Security (Java), Sustainsys.Saml2 (.NET), pysaml2 (Python), node-saml (Node.js), ruby-saml (Ruby) และ SimpleSAMLphp (PHP) อีกทางคือให้ identity broker พูด SAML กับ IdP ของลูกค้า แล้วให้ OpenID Connect กับแอปของคุณ
- sign response หรือ sign assertion ถ้าใช้ POST binding ทุก assertion ต้องมี signature ปกป้อง จะ sign ที่ตัว assertion เองหรือที่ response ทั้งก้อนก็ได้ (SAML 2.0 Errata 05, E26) ตัว signature ของ response ครอบคลุม
Destination,InResponseToและ status ด้วย ส่วน signature ของ assertion ยังตรวจได้แม้ทีหลังจะดึง assertion ออกมาเก็บแยก Microsoft Entra ID sign ที่ assertion โดย default สำหรับ gallery application ส่วนใหญ่ เปลี่ยนไป sign ที่ response หรือทั้งสองอย่างได้ และ sign ด้วย SHA-256 ยกเว้น admin จะเปลี่ยนให้แอปที่รองรับแค่ SHA-1 ถ้าคุณต้องการ assertion ที่ถูก sign ให้ตั้งWantAssertionsSignedใน metadata ของคุณ และเช็กว่า signature ตัวที่คุณตรวจครอบคลุมข้อมูลที่คุณใช้จริง - เข้ารหัส assertion SAML Bindings แนะนำว่าไม่ควรใช้ Redirect และ POST binding ถ้าเนื้อหาต้องไม่ถูกเปิดเผยให้ browser เห็น ส่วน
EncryptedAssertion(XML Encryption โดยใช้ key จาก metadata ของ SP ที่ mark ไว้สำหรับencryption) ทำให้ attribute ไม่ไปถึง browser และ extension ของมัน ให้ decrypt ก่อน แล้วค่อยตรวจ signature แบบเดิมทุกอย่าง: การเข้ารหัสซ่อนเนื้อหาได้ แต่ไม่ได้พิสูจน์อะไรเลยว่าใครเป็นคนเขียน SAML token encryption ของ Entra ID ที่เป็น feature ของ P1 หรือ P2 ใช้ AES-256 กับ public key ของ certificate ที่คุณอัปโหลด นอกจากนี้ OWASP ยังแนะนำให้ตัด algorithm ของ XML Encryption ที่รู้กันว่าไม่ปลอดภัยออกด้วย - RelayState ใช้พาผู้ใช้กลับไปที่ที่เขาเริ่ม ส่วน binding ก็จำกัดมันไว้ที่ 80 byte บอกว่าควรมีการปกป้อง integrity และบังคับให้ IdP ส่งมันกลับมาแบบไม่แก้อะไร ให้เก็บ URL ปลายทางไว้ที่ server แล้วส่ง key สุ่มที่อ่านความหมายไม่ออกไปแทน ถ้า portal ส่ง URL มาใน
RelayStateสำหรับการเข้าสู่ระบบแบบ IdP-initiated ให้รับเฉพาะ URL ที่อยู่ใน allowlist ไม่อย่างนั้นมันจะกลายเป็น open redirect (OWASP) - การเข้าสู่ระบบแบบ IdP-initiated response ที่ไม่มีใครขอไม่มีอะไรให้จับคู่: ไม่มี request ID และไม่มีหลักฐานว่า browser นี้เป็นคนเริ่มการเข้าสู่ระบบ ทำให้เสียการป้องกัน assertion ที่ถูก replay หรือถูกฉีดเข้ามา และการป้องกัน login CSRF ที่ attacker พาเหยื่อเข้าสู่ระบบในบัญชีของ attacker เอง ส่วน OWASP ก็บอกว่า flow นี้ปลอดภัยน้อยกว่าโดยการออกแบบ และ Auth0 แนะนำว่าไม่ควรใช้ ถ้าใช้แบบ SP-initiated ได้ ส่วน NIST SP 800-63C-4 บังคับให้ป้องกัน assertion injection ตั้งแต่ FAL2 ขึ้นไป โดยให้ transaction เริ่มที่ relying party รูปแบบที่ปลอดภัยกว่าคือ tile บน portal ที่เปิด login URL ของแอป แล้วแอปก็เริ่มการเข้าสู่ระบบแบบ SP-initiated เอง: My Apps ของ Entra ID ทำแบบนี้ได้ถ้าแอปมี Sign on URL และ OpenID Connect ก็กำหนดไอเดียเดียวกันไว้เป็น login ที่ third party เป็นคนเริ่ม ถ้าต้องรับ response ที่ไม่มีใครขอจริง ๆ ให้รับเฉพาะจาก IdP ที่คุณระบุไว้ พร้อม replay cache และอายุที่สั้น
- request ID ต้องรอดผ่าน cross-site POST response มาถึงเป็น POST ระดับ top-level จากเว็บของ IdP และ browser ไม่ส่ง cookie ที่เป็น
SameSite=LaxหรือStrictไปกับ cross-site POST ถ้า ID ของAuthnRequestที่ยังรอคำตอบอยู่เก็บไว้ใน session ที่อิงกับ cookie ตัว cookie นั้นต้องเป็นSameSite=None; Secureไม่อย่างนั้นInResponseToจะไม่มีวันตรง - Attribute mapping และ provisioning ใช้
NameIDจาก IdP ที่ออกมันเป็น key ของผู้ใช้ (ถ้าได้ให้เป็นแบบ persistent) ไม่ใช่ใช้อีเมล และ map group ไปเป็น role ของคุณเอง IdP แต่ละตัวตั้งชื่อ attribute ตามแบบของตัวเอง: Entra ID ใช้ URI อย่างhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameและ ไม่ใส่ groups claim มาเลย ถ้าผู้ใช้อยู่ในมากกว่า 150 group ส่วน just-in-time provisioning สร้างผู้ใช้ในระบบตอนเข้าสู่ระบบครั้งแรก แล้ว SCIM (RFC 7643, RFC 7644) ก็สร้างผู้ใช้และ group ไว้ล่วงหน้า และลบคนที่ลาออกไปแล้ว Federated Identity อธิบายทั้งสองแบบ และวงจรชีวิตของบัญชีรอบ ๆ มัน - Single logout เชื่อถือไม่ได้ Single Logout profile ให้ IdP ส่ง
LogoutRequest(ที่ต้อง sign ถ้าใช้ Redirect และ POST binding) ไปหาทุก SP ที่ผู้ใช้มี session อยู่ โดยระบุด้วยSessionIndexจะส่งผ่าน browser หรือผ่าน SOAP ก็ได้ ถ้าส่งผ่าน browser ตัว SP แค่ตัวเดียวที่ล่มหรือช้าก็ทำให้ทั้งสายพังพร้อมหน้า error และ SP ที่เริ่ม logout ก็รู้น้อยมากว่าอะไรพัง บันทึกเรื่อง SLO ของ Shibboleth สรุปว่าคำแนะนำที่สมเหตุสมผลหลังความล้มเหลวส่วนใหญ่ก็คือให้ปิด browser ถ้าส่งผ่าน SOAP ตัว SP จะไม่เคยเห็น cookie ของผู้ใช้ เลยต้องหาและจบ session ได้ด้วยNameIDหรือSessionIndexให้ session ของแอปสั้นไว้ และมอง single logout ว่าเป็นแค่ best effort - ประเภทการโจมตีที่รู้จักกันดี แต่ละข้อคือเหตุผลที่ต้องใช้ library ที่คนใช้กันเยอะและยังมีคนดูแล อัปเดตให้ทัน และ test มันด้วย response ที่ถูกแก้
- Signature wrapping: attacker ย้าย assertion ที่ถูก sign ไปไว้ข้าง ๆ แล้วใส่ตัวปลอมไว้ตรงที่แอปจะมองหา ทำให้ signature ตรวจผ่าน แต่แอปไปอ่านตัวปลอม มี paper ของ USENIX Security ปี 2012 ที่เจอช่องโหว่ wrapping ระดับ critical ใน 11 จาก 14 SAML framework หลัก ๆ ในนั้นก็มี Salesforce, Shibboleth และ IBM XS40
- จุดอ่อนของ XML parser: DTD เปิดทางให้การโจมตีแบบ external entity (XXE) และ entity-expansion เลยต้องปฏิเสธมัน parser สองตัวที่อ่านไม่ตรงกันก็อันตรายพอกัน: ในเดือนมีนาคม 2025 GitHub Security Lab แสดงให้เห็นว่า ruby-saml ถึงเวอร์ชัน 1.17.0 (CVE-2025-25291 และ CVE-2025-25292) ใช้ XML parser สองตัวคือ REXML กับ Nokogiri ตอนตรวจ signature และ document ที่สองตัวนี้อ่านไม่เหมือนกันก็ทำให้ใครก็ตามที่ถือ signature ที่ถูกต้องจาก IdP อยู่หนึ่งตัว เข้าสู่ระบบเป็นผู้ใช้คนไหนก็ได้ GitLab โดนด้วย และ ruby-saml 1.18.0 ก็แก้แล้ว
- Comment ใน
NameID(2018): canonicalization มักไม่เอา comment ไปรวมในส่วนที่ถูก sign ทำให้ attacker ที่ลงทะเบียนเป็นuser@example.com.evil.exampleแทรก<!---->หลังuser@example.comได้โดยไม่ทำให้ signature เสีย แล้ว library ที่อ่านแค่ text node ตัวแรกก็จะเห็นเป็นuser@example.comส่วน CERT/CC VU#475445 (กุมภาพันธ์ 2018) ที่ Kelby Ludwig จาก Duo Security รายงาน ได้ระบุ implementation ที่โดนไว้ รวมถึง python-saml, ruby-saml, saml2-js, OmniAuth-SAML และ OpenSAML C++ ของ Shibboleth
- สิ่งที่แอปยังต้องดูแลเอง SAML จบที่การเข้าสู่ระบบ แอปต้องดูแล session ของตัวเอง (cookie flag, idle timeout และ absolute timeout ที่ไม่เกิน
SessionNotOnOrAfter) และตัดสินเรื่อง authorization เอง โดยมอง role ที่ map มาเป็น input ไม่ใช่ตัวคำตัดสิน ดู Sessions vs Tokens และ Policy-Based Authorization - Testing และ troubleshooting browser extension อย่าง SAML-tracer (Firefox และ Chrome) decode ทุก SAML message ที่วิ่งผ่าน ความล้มเหลวส่วนใหญ่มาจาก config ไม่ตรงกัน: entity ID, ACS URL, certificate, NameID format หรือชื่อ attribute ถ้ายกตัวอย่าง Microsoft Entra ID มันจะ รายงาน
AADSTS50011เมื่อ reply (ACS) URL ไม่ได้ลงทะเบียนไว้ และAADSTS700016เมื่อหา identifier (entity ID) ไม่เจอ เช็กนาฬิกาด้วย: Entra ID ตั้งNotBeforeเป็นเวลาที่ออก assertion และไม่เผื่อเวลาให้นาฬิกาที่คลาดกันเลย SP ที่นาฬิกาช้าไปนิดเดียวเลยจะเห็น assertion ที่ยังไม่ถึงเวลาใช้ได้ ให้ NTP ทำงานตลอด และเผื่อเรื่องนาฬิกาคลาดไว้น้อย ๆ เพิ่ม negative test ด้วย: signature ที่เสีย audience ที่ผิด assertion ที่หมดอายุหรือถูก replay และ response ที่ไม่มีใครขอ (ถ้าคุณไม่รับมัน) ต้องถูกปฏิเสธทั้งหมด