- CVE-2025-25291 và CVE-2025-25292 được phát hiện trong ruby-saml 1.17.0 trở xuống làm tăng nguy cơ chiếm đoạt tài khoản trong môi trường SAML SSO, vì chỉ với một chữ ký hợp lệ là có thể đăng nhập dưới danh nghĩa người dùng tùy ý
- Lỗ hổng bắt nguồn từ khác biệt giữa các parser, nơi REXML và Nokogiri có thể diễn giải các phần tử
Signaturekhác nhau trong cùng một tài liệu XML trên đường xác minh chữ ký - Khi
SignedInfo,SignatureValue, hash assertion vàDigestValueđược ghép từ kết quả của các parser khác nhau, từng bước kiểm tra riêng lẻ vẫn có thể vượt qua nhưng liên kết giữa hash và chữ ký có thể bị đứt gãy - Trong quá trình xem xét tái đưa ruby-saml vào sử dụng, GitHub đã tiến hành bug bounty và đánh giá bởi Security Lab, phát hiện một instance có thể khai thác trên GitLab và thông báo cho đội bảo mật của họ; hiện GitHub không dùng ruby-saml cho xác thực
- Người dùng cần cập nhật lên ruby-saml 1.18.0, đồng thời nâng cả các thư viện tham chiếu tới ruby-saml như
omniauth-samllên bản phát hành trỏ tới phiên bản đã được vá
Tác động của việc vượt qua xác thực ruby-saml
- Đã xác nhận 2 lỗ hổng vượt qua xác thực mức độ nghiêm trọng cao trong ruby-saml 1.17.0 trở xuống
- CVE-2025-25291
- CVE-2025-25292
- Kẻ tấn công chỉ cần một chữ ký hợp lệ được tạo bằng khóa dùng để xác minh SAML response hoặc assertion của tổ chức mục tiêu là có thể tự tạo SAML assertion và đăng nhập với tư cách người dùng bất kỳ
- Nguồn chữ ký khả dĩ gồm:
- assertion hoặc response đã ký của một người dùng khác có quyền thấp hơn
- trong một số trường hợp, metadata đã ký của SAML IdP được truy cập công khai
- Rốt cuộc, lỗ hổng này có thể bị dùng cho tấn công chiếm đoạt tài khoản
- Hiện GitHub không dùng ruby-saml cho xác thực, nhưng trong lúc xem xét lại phương án dùng thư viện mã nguồn mở cho xác thực SAML, họ đã đánh giá ruby-saml
- ruby-saml cũng được dùng trong nhiều dự án và sản phẩm phổ biến khác; GitHub đã phát hiện một instance có thể khai thác tại GitLab và thông báo cho đội bảo mật GitLab
Vì sao GitHub xem xét lại ruby-saml
- GitHub từng dùng ruby-saml đến năm 2014, nhưng do thiếu các tính năng cần thiết vào thời điểm đó nên đã chuyển sang triển khai SAML riêng
- Sau đó, ngay cả trong triển khai riêng cũng xuất hiện các báo cáo bug bounty như CVE-2024-9487, một lỗ hổng liên quan đến assertion được mã hóa, nên GitHub bắt đầu cân nhắc tái sử dụng ruby-saml
- Tháng 10/2024, lỗ hổng vượt qua xác thực của ruby-saml do ahacker1 phát hiện là CVE-2024-45409 đã được công bố
- Để đánh giá kỹ hơn khả năng chuyển sang ruby-saml, GitHub đã khởi động một chương trình bug bounty riêng tư
- Cung cấp cho các nhà nghiên cứu được chọn quyền truy cập vào môi trường thử nghiệm GitHub thực hiện xác thực SAML bằng ruby-saml
- GitHub Security Lab cũng đồng thời rà soát bề mặt tấn công của ruby-saml
Sự không nhất quán khi xác minh do hai parser XML tạo ra
- Trong quá trình rà soát mã, ahacker1 và GitHub Security Lab xác nhận rằng trên đường xác minh chữ ký của ruby-saml có sử dụng đồng thời hai parser XML
- REXML: parser XML được triển khai thuần Ruby
- Nokogiri: cung cấp API bao quanh libxml2, libgumbo, Xerces cho JRuby và hỗ trợ phân tích XML lẫn HTML
- Đường mã gây vấn đề là phương thức
validate_signaturetrongxml_security.rb - Phương thức này đọc phần tử
Signatuređầu tiên vàSignatureValuethực tế bằng REXMLREXML::XPath.first(@working_copy, "//ds:Signature", {"ds"=>DSIG})
- Trong khi đó, ngay trong cùng luồng xác minh,
SignaturevàSignedInfolại được truy vấn lại và chuẩn hóa bằng Nokogiridocument.at_xpath('//ds:Signature', 'ds' => DSIG)noko_signed_info_element.canonicalize(canon_algorithm)
- assertion được trích xuất, chuẩn hóa và hash bằng Nokogiri, nhưng
DigestValuedùng để so sánh lại đến từ REXML - Kết quả là các thành phần cuối cùng phục vụ xác minh bị tách ra như sau
- assertion được trích xuất, chuẩn hóa rồi hash bằng Nokogiri
- hash đối chiếu đến từ
DigestValuemà REXML đọc được SignedInfođược trích xuất và chuẩn hóa bằng NokogiriSignatureValueđược trích xuất bằng REXML
Liên kết bảo mật bị đứt trong xác minh chữ ký SAML
- SAML response truyền thông tin người dùng đăng nhập từ IdP sang SP dưới dạng XML
- Khi dùng HTTP POST binding, SAML response đi qua trình duyệt người dùng để tới SP, nên cần xác minh chữ ký để ngăn người dùng sửa đổi thông điệp
- Trong một SAML response đã đơn giản hóa, thông tin quan trọng thường nằm trong
SubjectvàNameIDbên trongAssertion - Thông thường, assertion hoặc toàn bộ SAML response đều có thể được ký
- Nếu assertion được ký, việc xác minh diễn ra theo hai bước
- Loại bỏ
Signaturekhỏi assertion, chuẩn hóa rồi hash để so sánh vớiDigestValue - Chuẩn hóa
SignedInforồi xác minh chữ ký bằngSignatureValue
- Loại bỏ
- Với lỗ hổng lần này, dù từng bước đều vượt qua thì vẫn không đảm bảo chúng cùng trỏ tới một dữ liệu
- hash có thể là hash của assertion thực tế
- chữ ký có thể là chữ ký cho một phần tử
SignedInfokhác
- Thuộc tính bảo mật cần có là nội dung được hash, giá trị hash và chữ ký phải được liên kết trực tiếp với nhau; sau khi xác minh, chỉ nên đọc thông tin từ đúng phần đã được xác minh
Cách xây dựng exploit thực tế
- Điều kiện cốt lõi là làm cho REXML và Nokogiri nhìn thấy các
Signaturekhác nhau trong cùng một tài liệu XML, và điều này đã thực sự khả thi - Trong quá trình tham gia bug bounty, ahacker1 trước tiên đã tạo một exploit hành vi dựa trên khác biệt parser
- Lấy cảm hứng từ XML roundtrips vulnerabilities do Juho Forsén của Mattermost công bố năm 2021
- GitHub Security Lab cũng tạo một exploit khác dựa trên khác biệt parser bằng Ruby fuzzer ruzzy của Trail of Bits
- Exploit ví dụ chèn thêm một
Signaturechỉ Nokogiri nhìn thấy vào bên trong phần tửStatusDetail - Luồng xác minh khi đó bị tách ra như sau
SignedInfocủa signature mà Nokogiri nhìn thấy được chuẩn hóa- được xác minh bằng
SignatureValuelấy từ signature mà REXML nhìn thấy - assertion mà Nokogiri tìm thấy theo ID được chuẩn hóa và hash
- hash được so sánh với
DigestValuemà REXML đọc được
- Cuối cùng,
SignedInfohợp lệ và chữ ký hợp lệ khớp với nhau, còn assertion đã bị chỉnh sửa và digest được tính cho nó cũng khớp với nhau, nên ruby-saml chấp nhận assertion đó
Biện pháp giảm thiểu và giới hạn phát hiện
- Kiểm tra lỗi phân tích cú pháp khi parse SAML response bằng Nokogiri có thể chặn được một số exploit không công khai hiện đã biết
- Lỗi parse của Nokogiri không được ném ra dưới dạng ngoại lệ, nên phải kiểm tra trực tiếp thành viên
errorscủa tài liệu đã parse - Mã ví dụ dùng các tùy chọn
Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NONETvà phát sinh lỗi khidoc.errors.any? - Đây không phải bản sửa hoàn chỉnh, nhưng có thể khiến ít nhất một exploit không thể chạy được
- Hiện chưa có chỉ dấu xâm nhập đáng tin cậy nào được biết đến
- Một chỉ dấu tiềm năng chỉ hoạt động trong môi trường tương tự debug
- Việc công bố nó sẽ làm lộ quá nhiều chi tiết triển khai của exploit đang hoạt động nên không được công bố
- Cách kiểm tra được khuyến nghị là tìm các lần đăng nhập SAML đáng ngờ ở phía SP, chẳng hạn đăng nhập từ địa chỉ IP không phù hợp với vị trí dự kiến của người dùng
Hướng sửa và đối tượng cần cập nhật
- Bản sửa ban đầu không loại bỏ một trong hai parser XML do vấn đề tương thích API
- Vấn đề gốc rễ hơn là sự tách rời giữa xác minh hash và xác minh chữ ký, và sự tách rời này có thể bị khai thác thông qua khác biệt parser
- Việc loại bỏ một parser XML vốn đã được lên kế hoạch vì lý do khác, và có thể sẽ được thực hiện trong một bản phát hành lớn cùng với các cải tiến bổ sung
- Người dùng ruby-saml cần cập nhật lên 1.18.0 đã bao gồm bản vá
- Cũng cần kiểm tra các thư viện sử dụng ruby-saml
- Ví dụ: omniauth-saml
- Cần cập nhật các thư viện đó lên phiên bản tham chiếu tới bản ruby-saml đã được vá
- GitHub Security Lab dự kiến sẽ công bố exploit proof-of-concept trong tương lai tại GitHub Security Lab repository
Lịch trình công bố và ứng phó
- 2024-11-04: tiếp nhận báo cáo bug bounty chứng minh có thể vượt qua xác thực trên môi trường thử nghiệm GitHub đang đánh giá xác thực SAML bằng ruby-saml
- 2024-11-04: bắt đầu xác định và thử nghiệm các biện pháp giảm thiểu tiềm năng
- 2024-11-12: phát hiện lỗ hổng vượt qua xác thực thứ hai có thể vô hiệu hóa kế hoạch giảm thiểu đầu tiên
- 2024-11-13: liên hệ lần đầu với maintainer của ruby-saml là Sixto Martín
- 2024-11-14: khác biệt giữa hai parser được báo cáo cho ruby-saml và maintainer đã phản hồi ngay lập tức
- 2024-11-14: maintainer và ahacker1 bắt đầu làm việc trên các bản vá tiềm năng
- Một trong các ý tưởng ban đầu là loại bỏ một parser XML, nhưng không thể làm như vậy mà không phá vỡ tính tương thích ngược
- 2025-02-04: ahacker1 đề xuất một phương án sửa không tương thích ngược
- 2025-02-06: ahacker1 cũng đề xuất một phương án sửa tương thích ngược
- 2025-02-12: hết hạn mốc 90 ngày của advisory từ GitHub Security Lab
- 2025-02-16: maintainer bắt đầu làm theo hướng sửa dễ hiểu và vẫn giữ được tính tương thích ngược
- 2025-02-17: liên hệ lần đầu với GitLab để phối hợp phát hành ruby-saml và các bản phát hành sản phẩm GitLab on-premises
- 2025-03-12: phát hành phiên bản ruby-saml đã được vá
1 bình luận
Các ý kiến trên Hacker News
Tôi cho rằng triển khai SAML của GitHub là vô dụng
Ý định ban đầu là mang tài khoản cá nhân vào dùng trong doanh nghiệp; nó hoạt động phần nào bên trong trang GitHub, nhưng sau khi một ứng dụng đăng nhập bằng GitHub được phê duyệt ở cấp tổ chức, nó không ngăn được việc đọc tư cách thành viên của tổ chức
Phiên SAML chỉ cần thiết khi ứng dụng đó lấy dữ liệu bằng token nhận được từ người dùng đó, còn trên thực tế các công cụ SAST hầu như luôn dùng token của instance ứng dụng và chỉ cần có tài khoản GitHub trong tổ chức là đã cho xem mã nguồn
Tailscale đã sửa khi được báo, Sonarcloud thì yêu cầu đừng nói với ai, còn GitHub vài tuần sau trả lời rằng đó là “hành vi hoàn toàn được dự kiến”, nhưng không vendor nào được báo hiểu như vậy, và tài liệu cũng mâu thuẫn với câu trả lời của GitHub
Báo cáo lỗi bảo mật, kể cả tình cờ phát hiện, là việc chẳng mấy đáng công, và thật khó tưởng tượng việc làm nó như một nghề
GitHub đã rất thỉnh thoảng dùng địa chỉ email cá nhân làm mặc định khi merge PR công việc, nên nếu ai hỏi, tôi sẽ khuyên đừng trộn tài khoản cá nhân và công việc vào cùng một tài khoản, dù là trên GitHub hay ở đâu khác
Khi làm công cụ phân tích repository/commit/PR là https://dev.log.xyz, chúng tôi đã phải tốn rất nhiều công sức để đảm bảo “những gì xem được trên GitHub thì cũng xem được trên Devlog”, và toàn bộ trải nghiệm rất bực bội
Bộ chọn quyền OAuth của GitHub cũng quá rối rắm, khiến khó chắc chắn khi “đăng nhập bằng GitHub” thì đang chia sẻ thông tin nào của tổ chức nào
Thuyết phục các lập trình viên trẻ quen JavaScript rằng chỉ kiểm tra ở phía client là chưa đủ cũng đã khó, với người kinh doanh quyết định yêu cầu tính năng và ngân sách thì còn khó hơn nữa
Bất kỳ dạng kho lưu mật khẩu nào, thậm chí kho vật lý hay cả việc tái sử dụng mật khẩu, rốt cuộc có khi còn an toàn hơn
Cuối cùng thì chủ nghĩa tối giản lại thắng
Có đoạn viết: “GitHub doesn’t currently use ruby-saml for authentication, but began evaluating the use of the library with the intention of using an open source library for SAML authentication once more”
Gần đây tôi phải triển khai SAML, nên tiêu đề này chẳng làm tôi ngạc nhiên chút nào
Bản thân đặc tả SAML khá hợp lý, nhưng nền tảng của nó là chữ ký XML, và xa hơn nữa là chuẩn hóa XML, nếu có thể gọi là tiêu chuẩn thì đúng là điên rồ
Một đặc tả méo mó và suy đồi như vậy chỉ có thể do một ủy ban tạo ra; có lẽ đầu óc một cá nhân không thể nắm giữ rồi kết hợp những ý tưởng mâu thuẫn đến thế
Chỉ cần truyền chữ ký ngoài băng là SAML đã có thể trở thành thứ thú vị để triển khai
XML theo nghĩa đen là eXtensible Markup Language, vậy mà ủy ban tiêu chuẩn hóa SAML lại phát minh thêm ngôn ngữ cơ chế mở rộng của riêng họ đặt lên trên nó
Đặt thêm một protocol lên trên một protocol chỉ để chứa một lượng dữ liệu rất nhỏ, không khác nhiều thông tin đi trong cookie xác thực, là một kiểu ngu ngốc đặc thù mà chỉ những ủy ban lớn nhất và quan liêu nhất mới tạo ra được
Ý tưởng đăng nhập riêng vào các tài khoản khác nhau thì có vẻ ổn
Cấu trúc liên kết các tài khoản với nhau để có thể bị chiếm hàng loạt chỉ trong một lần về cơ bản là nguy hiểm hơn
Tôi cho rằng SAML, rộng hơn là XML-DSIG, theo đúng nghĩa đen là thứ tệ nhất trong các protocol bảo mật được dùng rộng rãi
Nói chung nên chuyển sang OAuth bằng mọi giá, và ít nhất tôi sẽ từ chối phụ thuộc vào thứ này khi đưa sản phẩm mới ra thị trường
Nó rất nguy hiểm, và trừ khi có đột phá trong việc xác minh định dạng một cách thực dụng, thật khó tưởng tượng đây sẽ là lỗ hổng DSIG cuối cùng hay là lỗ hổng tệ nhất
Chưa đụng gì đến mật mã học mà nó đã là đỉnh cao của tư duy kiểu phần mềm doanh nghiệp
Ai đó cần đào sâu thảm trạng ở các mailing list và tổ chức tiêu chuẩn hóa của những thứ như WS-* và OASIS/XACML
Có đúng vậy không?
Eo ôi, không ai nên dùng REXML trừ khi không còn lựa chọn nào khác
Nó sẵn sàng parse cả XML sai, rồi gây ra vô số vấn đề ở các bước sau
Theo nghĩa đen là nó parse XML bằng biểu thức chính quy, và là một ví dụ tuyệt vời cho thấy vì sao không nên làm vậy
Các dự án bắt đầu dùng Nokogiri không phải vì hiệu năng, mà vì tính đúng đắn
Đừng parse thứ không phải ngôn ngữ chính quy bằng biểu thức chính quy
Gần đây tôi thử o3, và mỗi khi cố sửa một vấn đề không liên quan đến thư viện trong một khối mã cụ thể, nó cứ thay đổi thư viện mà khối đó dùng
Tôi không thấy hiện tượng đó ở Sonnet
Có vẻ vấn đề rất dễ ngấm vào trong quá trình sửa như vậy, khi code bị đổi sang một thư viện/gem kém hơn vốn đã có sẵn trong codebase hoặc thư viện chuẩn nên test vẫn pass và cũng không cần đổi Gemfile
SAML không an toàn ngay từ thiết kế
Từ lâu đã có những bài viết giải thích tốt hơn, chẳng hạn https://joonas.fi/2021/08/saml-is-insecure-by-design/
Câu tôi nhớ trong một thread cũ ở đây là “hãy ký vào byte, không phải vào ý nghĩa”
Khác biệt giữa các parser là điều có thể dự đoán được, và đôi khi là không thể tránh khỏi
Việc bạn muốn lấy gì từ phản hồi đã ký là cực kỳ quan trọng
Một trong những thế tiến thoái lưỡng nan của TLS hiện đại là đôi khi bạn muốn tin cậy một CA nội bộ, và đó là con đường dễ đi; nhưng nếu bạn chấp nhận chứng chỉ CA của đối tác, và có nhiều đối tác, thì bạn không còn có thể chỉ nhìn vào chứng chỉ cuối nữa, mà root của chuỗi cũng trở nên quan trọng không kém trong việc ra quyết định
Vì vậy, nếu có thể, tôi cũng khuyên nên tránh thuật toán ký của AWS
V4 về lý thuyết là an toàn, nhưng AWS đã làm hỏng hai lần; SigV1 và SigV3 vốn không an toàn ngay từ thiết kế, vậy mà bằng cách nào đó vẫn vượt qua khâu rà soát thiết kế và được công khai ra ngoài
Bài viết rất hay
Như bài cũng nhắc tới, cần dành một tràng pháo tay lớn cho ahacker1
Anh ấy đang làm công việc rất tinh vi và giá trị để khiến các triển khai SAML an toàn hơn, và SSOReady cũng thật sự biết ơn công việc của anh ấy
Đầu tuần này WorkOS cũng đăng một bài hay về việc hợp tác với ahacker1: https://workos.com/blog/samlstorm
Có đoạn nói rằng “chúng tôi đã phát hiện một trường hợp có thể khai thác lỗ hổng này trong GitLab và đã thông báo cho đội bảo mật”; với những ai tò mò thì GitLab đã phát hành bản vá rồi
https://about.gitlab.com/releases/2025/03/12/patch-release-g...
Một bài liên quan là bài năm 2019 của Latacora, How (not) to sign a JSON object[1]
Tóm lại, việc ký các cấu trúc cây lồng nhau là khó và có nhiều cạm bẫy
Sẽ dễ hơn nếu phong bì chứa thông điệp dưới dạng chuỗi thô, rồi ký lên chính chuỗi thô đó
[1]: https://www.latacora.com/blog/2019/07/24/how-not-to/
Kết luận đơn giản hơn ở đây chẳng phải là hãy tìm chữ ký ở đúng vị trí nó phải nằm sao?
Đừng dùng XPath quá chung chung như “//ds:Signature” để rồi bắt được bất kỳ chữ ký nào ở một vị trí ngoài dự kiến
Không phải chỉ phẫu thuật cắt bỏ đúng thành phần nguy hiểm, mà phải vứt cả một phần “đứa bé cùng với nước tắm”, cắt bỏ hàng loạt và xử lý như hóa trị
Một quản trị viên IT có lòng tự trọng nên loại SAML khỏi kế hoạch tương lai
Toàn bộ ý tưởng SSO cũng đáng nghi, và XML parsing đã bị đánh trúng hai lần trong một tuần nên từ nay nên tránh
Có vấn đề gì với một chính sách thay XML bằng JSON?
Từ thiết kế, giao thức cho tới định dạng dữ liệu cũng phải như vậy
Thói quen và cân nhắc thiết kế của phát triển web thông thường không phù hợp với những gì mã bảo mật cần, và thường còn hoàn toàn trái ngược với những gì cần để viết mã đúng đắn
Hơi khó chịu là trong bài blog, khi giải thích lỗ hổng, lại bỏ qua chính khác biệt parser gây ra vấn đề
Như thể đã viết phần mở đầu câu chuyện rồi bỏ qua cao trào
Khi gọi
(...//ds:DigestValue).firstChild.nodeValue, họ không kiểm tra.firstChildcó phải là Node hay không; trong trường hợp gặp vấn đề, nó là CommentVì vậy phía chưa chuẩn hóa nhìn thấy chữ ký “bị che”, còn phía đã sửa và loại bỏ comment thì nhìn thấy Node; khi hai triển khai đưa ra phán đoán khác nhau về một tài liệu đã ký thì những chuyện dở khóc dở cười sẽ xảy ra
Chi tiết cụ thể chắc sẽ sớm được công bố