- ruby-saml 1.17.0 이하에서 발견된 CVE-2025-25291·CVE-2025-25292는 유효 서명 하나만으로 임의 사용자 로그인을 가능하게 해 SAML SSO 환경의 계정 탈취 위험을 키움
- 취약점은 서명 검증 경로에서 REXML과 Nokogiri가 같은 XML 문서의 서로 다른
Signature요소를 해석할 수 있는 파서 차이에서 비롯됨 SignedInfo,SignatureValue, assertion 해시,DigestValue가 서로 다른 파서 결과에서 조합되면서 각 검사는 통과하지만 해시와 서명의 연결이 끊어질 수 있음- GitHub는 ruby-saml 재도입 검토 중 버그바운티와 Security Lab 리뷰를 진행했고, GitLab에서 악용 가능한 인스턴스를 발견해 보안팀에 알렸으며, 현재 GitHub 인증에는 ruby-saml을 쓰지 않음
- 사용자는 ruby-saml 1.18.0으로 업데이트해야 하며,
omniauth-saml처럼 ruby-saml을 참조하는 라이브러리도 수정 버전을 참조하는 릴리스로 함께 올려야 함
ruby-saml 인증 우회의 영향
- ruby-saml 1.17.0 이하에서 중요도 높은 인증 우회 취약점 2건이 확인됨
- CVE-2025-25291
- CVE-2025-25292
- 공격자는 대상 조직의 SAML response 또는 assertion 검증에 쓰이는 키로 생성된 유효 서명 하나가 있으면 직접 SAML assertion을 만들어 임의 사용자로 로그인할 수 있음
- 가능한 서명 출처는 다음과 같음
- 권한이 낮은 다른 사용자의 서명된 assertion 또는 response
- 일부 경우 공개 접근 가능한 SAML IdP의 서명된 metadata
- 이 취약점은 결과적으로 계정 탈취 공격에 활용될 수 있음
- GitHub는 현재 인증에 ruby-saml을 사용하지 않지만, SAML 인증에 오픈소스 라이브러리를 다시 쓰는 방안을 검토하던 중 ruby-saml을 평가함
- ruby-saml은 다른 인기 프로젝트와 제품에서도 쓰이며, GitHub는 GitLab에서 악용 가능한 인스턴스를 발견해 GitLab 보안팀에 통지함
GitHub가 다시 ruby-saml을 검토한 이유
- GitHub는 2014년까지 ruby-saml을 사용했지만, 당시 필요한 기능이 부족해 자체 SAML 구현으로 이동함
- 이후 자체 구현에서도 암호화된 assertion 관련 취약점인 CVE-2024-9487 같은 버그바운티 보고가 있었고, GitHub는 ruby-saml 재도입을 검토하기 시작함
- 2024년 10월에는 ahacker1이 발견한 ruby-saml 인증 우회 취약점 CVE-2024-45409가 공개됨
- GitHub는 ruby-saml 전환 가능성을 더 면밀히 평가하기 위해 비공개 버그바운티를 시작함
- 선정된 연구자들에게 ruby-saml로 SAML 인증을 수행하는 GitHub 테스트 환경 접근을 제공함
- GitHub Security Lab도 ruby-saml의 공격 표면을 함께 검토함
두 XML 파서가 만든 검증 불일치
- 코드 리뷰 중 ahacker1과 GitHub Security Lab은 ruby-saml의 서명 검증 경로에서 두 XML 파서가 함께 쓰인다는 점을 확인함
- REXML: 순수 Ruby로 구현된 XML 파서
- Nokogiri: libxml2, libgumbo, JRuby용 Xerces 등을 감싼 API를 제공하며 XML과 HTML 파싱을 지원함
- 문제가 된 경로는
xml_security.rb의validate_signature메서드임 - 이 메서드는 첫 번째
Signature요소와 실제SignatureValue를 REXML로 읽음REXML::XPath.first(@working_copy, "//ds:Signature", {"ds"=>DSIG})
- 반면 같은 검증 흐름 안에서
Signature와SignedInfo는 Nokogiri로 다시 조회되고 정규화됨document.at_xpath('//ds:Signature', 'ds' => DSIG)noko_signed_info_element.canonicalize(canon_algorithm)
- assertion은 Nokogiri로 추출·정규화·해시 처리되지만, 비교 대상인
DigestValue는 REXML에서 나옴 - 최종 검증 재료가 다음처럼 갈라짐
- assertion은 Nokogiri로 추출·정규화한 뒤 해시됨
- 비교 대상 해시는 REXML이 읽은
DigestValue에서 옴 SignedInfo는 Nokogiri로 추출·정규화됨SignatureValue는 REXML로 추출됨
SAML 서명 검증에서 끊어진 보안 연결
- SAML response는 IdP에서 SP로 로그인 사용자 정보를 XML 형식으로 전달함
- HTTP POST binding을 쓰면 SAML response가 사용자의 브라우저를 거쳐 SP로 이동하므로, 사용자가 메시지를 변조하지 못하게 서명 검증이 필요함
- 단순화한 SAML response에서 중요한 정보는 보통
Assertion내부의Subject와NameID에 들어 있음 - 일반적으로 assertion 또는 전체 SAML response가 서명될 수 있음
- assertion이 서명된 경우 검증은 두 단계로 진행됨
Signature를 제거한 assertion을 정규화하고 해시해DigestValue와 비교함SignedInfo를 정규화하고SignatureValue로 서명을 검증함
- 이번 취약점에서는 두 단계가 각각 통과하더라도 서로 같은 데이터를 보장하지 못함
- 해시는 실제 assertion의 해시일 수 있음
- 서명은 다른
SignedInfo요소에 대한 서명일 수 있음
- 필요한 보안 속성은 해시된 내용, 해시, 서명이 직접 연결되는 것이며, 검증 후에는 실제 검증된 부분에서만 정보를 읽어야 함
실제 익스플로잇 구성 방식
- 핵심 조건은 REXML과 Nokogiri가 같은 XML 문서에서 서로 다른
Signature를 보게 만드는 것이었고, 실제로 가능했음 - ahacker1은 버그바운티 참여 중 파서 차이를 이용한 동작 익스플로잇을 먼저 만듦
- Mattermost의 Juho Forsén이 2021년에 공개한 XML roundtrips vulnerabilities에서 영감을 받음
- GitHub Security Lab은 Trail of Bits의 Ruby 퍼저 ruzzy를 이용해 다른 파서 차이 기반 익스플로잇을 만듦
- 예시 익스플로잇은
StatusDetail요소 안에 Nokogiri에만 보이는 추가Signature를 넣음 - 검증 흐름은 다음처럼 분리됨
- Nokogiri가 보는 signature의
SignedInfo가 정규화됨 - REXML이 보는 signature에서 추출한
SignatureValue로 검증됨 - Nokogiri가 ID로 찾은 assertion이 정규화·해시됨
- REXML이 읽은
DigestValue와 해시가 비교됨
- Nokogiri가 보는 signature의
- 결국 유효한
SignedInfo와 유효한 서명이 서로 맞고, 조작된 assertion과 그 계산된 digest도 서로 맞아 ruby-saml이 assertion을 받아들임
완화책과 탐지 한계
- Nokogiri로 SAML response를 파싱할 때 파싱 오류를 확인하면 현재 알려진 비공개 익스플로잇 일부를 막을 수 있음
- Nokogiri 파싱 오류는 예외로 발생하지 않으므로, 파싱된 문서의
errors멤버를 직접 확인해야 함 - 예시 코드는
Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NONET옵션을 사용하고,doc.errors.any?일 때 오류를 발생시킴 - 이 방식은 완전한 수정은 아니지만, 최소 하나의 익스플로잇을 실행 불가능하게 만듦
- 신뢰할 수 있는 침해 지표는 알려져 있지 않음
- 잠재적 지표 하나는 debug 유사 환경에서만 동작함
- 이를 공개하려면 동작 익스플로잇 구현 세부사항을 너무 많이 드러내야 해 공개하지 않음
- 권장 확인 방법은 SP 측에서 사용자의 예상 위치와 맞지 않는 IP 주소의 SAML 로그인 같은 의심스러운 로그인을 찾는 것임
수정 방향과 업데이트 대상
- 초기 수정은 API 호환성 문제 때문에 XML 파서 하나를 제거하지 않음
- 더 근본적인 문제는 해시 검증과 서명 검증의 분리였고, 이 분리가 파서 차이를 통해 악용 가능해짐
- XML 파서 하나를 제거하는 작업은 다른 이유로 이미 계획되어 있었으며, 추가 개선과 함께 주요 릴리스에서 이뤄질 가능성이 있음
- ruby-saml 사용자는 수정이 포함된 1.18.0으로 업데이트해야 함
- ruby-saml을 사용하는 라이브러리도 함께 확인해야 함
- 예: omniauth-saml
- 해당 라이브러리가 수정된 ruby-saml 버전을 참조하는 버전으로 업데이트되어야 함
- GitHub Security Lab은 향후 GitHub Security Lab repository에 개념증명 익스플로잇을 공개할 예정임
공개와 대응 일정
- 2024-11-04: ruby-saml로 SAML 인증을 평가하던 GitHub 테스트 환경에 대해 인증 우회를 입증한 버그바운티 보고가 접수됨
- 2024-11-04: 잠재적 완화책 식별과 테스트 작업이 시작됨
- 2024-11-12: 첫 번째 완화 계획을 무력화하는 두 번째 인증 우회가 발견됨
- 2024-11-13: ruby-saml maintainer Sixto Martín과 최초 접촉함
- 2024-11-14: 두 파서 차이가 ruby-saml에 보고됐고 maintainer가 즉시 응답함
- 2024-11-14: maintainer와 ahacker1이 잠재적 패치 작업을 시작함
- 초기 아이디어 중 하나는 XML 파서 하나를 제거하는 것이었지만, 하위 호환성을 깨지 않고는 가능하지 않았음
- 2025-02-04: ahacker1이 하위 호환성이 없는 수정안을 제안함
- 2025-02-06: ahacker1이 하위 호환 가능한 수정안도 제안함
- 2025-02-12: GitHub Security Lab advisory의 90일 기한이 종료됨
- 2025-02-16: maintainer가 하위 호환성을 유지하고 이해하기 쉬운 수정 방향으로 작업을 시작함
- 2025-02-17: ruby-saml 릴리스와 GitLab 온프레미스 제품 릴리스를 조율하기 위해 GitLab과 최초 접촉함
- 2025-03-12: 수정된 ruby-saml 버전이 릴리스됨