TECH 으로 돌아가기
TECH HACKER NEWS 오늘 9분 읽기 24 READS

SAML은 왜 20년째 계속 뚫릴까? Trail of Bits가 짚은 '설계의 프랙탈'

SAML은 왜 20년째 계속 뚫릴까? Trail of Bits가 짚은 '설계의 프랙탈'
SOURCE IMAGE · HACKER NEWS
SAML은 왜 20년째 계속 뚫릴까? Trail of Bits가 짚은 '설계의 프랙탈'

도입

회사에서 슬랙이나 지라, 사내 어드민에 들어갈 때 구글 워크스페이스나 Okta 계정으로 한 번만 로그인하면 되죠? 그 뒤에서 돌아가는 프로토콜이 대부분 SAML이에요. 2005년에 표준이 확정된 뒤 기업 SSO(싱글 사인온)의 사실상 표준으로 쓰이고 있는데요. 문제는 그 20년 동안 '서명 검증을 우회해서 아무 계정으로나 로그인되는' 취약점이 한 해가 멀다 하고 반복됐다는 거예요. 보안 감사 회사 Trail of Bits가 'SAML: A fractal of bad design'이라는 글로 이 반복의 원인을 정리했어요. 제목은 예전에 유명했던 'PHP: a fractal of bad design'을 빗댄 건데, 프랙탈이라는 표현이 뭐냐면 아무리 확대해서 들여다봐도 같은 모양의 문제가 계속 나온다는 뜻이에요. 개별 라이브러리의 실수가 아니라 설계 자체가 실수를 유도한다는 주장이죠.

SAML이 뭐냐면

등장인물은 둘이에요. IdP(Identity Provider)는 '이 사람 맞아요'라고 보증해주는 쪽, 예를 들어 Okta나 구글이고요. SP(Service Provider)는 그 보증을 믿고 문을 열어주는 쪽, 즉 여러분이 만든 서비스예요. 사용자가 SP에 접속하면 IdP로 보내고, IdP가 로그인을 확인한 뒤 'Assertion'이라는 XML 문서에 '이 사람은 alice@company.com이고 지금부터 5분간 유효함' 같은 내용을 적어 전자서명을 하고, 브라우저를 통해 SP로 돌려보내요. SP는 서명이 IdP 것이 맞는지 확인하고 로그인시켜요.

여기까지는 깔끔해 보이죠. 문제는 '서명된 XML 문서를 검증한다'는 한 문장 안에 지뢰가 몇 겹으로 묻혀 있다는 거예요.

첫 번째 층: XML은 해석이 하나가 아니에요

XML에는 네임스페이스, 주석, 엔티티, DTD, 인코딩, 공백 처리 같은 기능이 잔뜩 있고, 파서마다 이걸 조금씩 다르게 다뤄요. 2018년 Duo Labs가 찾은 취약점이 대표적인데요. 사용자 이름을 user@company.com<!---->.evil.com처럼 중간에 XML 주석을 끼워 넣으면, 서명 검증기는 이걸 하나의 텍스트로 보고 통과시키는데 정작 이름을 꺼내는 코드는 주석 앞부분만 읽는 라이브러리가 있었어요. 검증은 통과했는데 로그인되는 계정은 다른 사람인 거죠. 이런 걸 '파서 차이(parser differential)'라고 불러요. 같은 문서를 두 코드가 다르게 읽는 순간 보안이 무너져요.

2025년에 GitHub 보안팀이 ruby-saml에서 찾은 인증 우회도 같은 뿌리예요. 그 라이브러리는 REXML과 Nokogiri라는 두 XML 파서를 함께 쓰면서 하나로 서명을 검증하고 다른 하나로 내용을 읽었어요. 두 파서가 특정 문서를 다르게 해석하게 만들면 서명은 통과하면서 내용은 공격자 마음대로가 됐고, GitLab을 비롯한 수많은 서비스가 긴급 패치를 해야 했어요.

두 번째 층: 서명이 문서 '안'에 들어 있어요

보통 서명은 봉투 밖에 있어요. JWT는 헤더.본문.서명처럼 서명 대상이 딱 정해져 있죠. 그런데 SAML이 쓰는 XML Signature는 서명이 문서 안에 요소로 들어가고, '내가 서명한 부분은 ID가 abc123인 요소야'라고 참조로 가리켜요. 검증기는 그 ID를 찾아 서명을 확인하고, 애플리케이션은 '어설션'을 찾아 내용을 읽는데, 이 둘이 같은 요소를 가리킨다는 보장이 없어요.

이걸 노린 게 XML Signature Wrapping(XSW) 공격이에요. 비유하자면 도장이 찍힌 계약서 페이지는 그대로 두고 그 앞에 가짜 페이지를 끼워 넣는 거예요. 도장 확인하는 사람은 원래 페이지를 보고 '진짜네' 하고, 내용 읽는 사람은 맨 앞 가짜 페이지를 읽는 거죠. 2012년에 주요 SAML 구현 대부분이 이 공격에 뚫린다는 논문이 나왔는데, 십 년이 넘은 지금도 변종이 계속 나와요.

세 번째 층: 정규화라는 함정

서명을 확인하려면 문서를 바이트 단위로 똑같이 만들어야 하는데, XML은 같은 의미를 여러 형태로 쓸 수 있잖아요. 그래서 검증 전에 '정규화(canonicalization, C14N)'를 거쳐요. 공백을 정리하고 속성 순서를 맞추고 네임스페이스를 정돈하는 건데, 이 규격이 수십 페이지짜리라 여기서 또 구현 차이가 생겨요. 서명은 정규화된 형태에 대해 검증되는데 애플리케이션은 원본을 읽으니까 또 두 해석이 갈릴 틈이 생기는 거예요.

Trail of Bits가 '프랙탈'이라고 부르는 이유가 여기 있어요. 파서, 참조, 정규화, 알고리즘 협상, 만료 시간과 Audience 검사까지, 어느 층을 열어봐도 '두 코드가 같은 걸 다르게 볼 수 있다'는 동일한 모양의 문제가 반복되거든요. 라이브러리 개발자가 이 모든 층을 완벽하게 맞춰야 하는데 그건 사실상 불가능에 가깝다는 거예요.

업계 맥락: 그럼 OIDC는 안전한가요

OpenID Connect(OIDC)는 JSON과 JWT를 써요. 서명 대상이 명확하고 파서 차이가 생길 여지가 훨씬 적어요. 물론 JWT도 alg: none 허용이나 키 혼동 같은 사고가 있었지만, 그건 구현이 규격을 어긴 경우이고 SAML은 규격을 정확히 따라도 위험한 구조라는 차이가 있어요. 그래서 Okta, Microsoft Entra, 구글 모두 새 연동에는 OIDC를 권장하는 추세예요. 다만 대기업 레거시와 정부 조달 요건에서는 여전히 SAML을 요구하기 때문에 앞으로 10년은 더 같이 살아야 할 거예요.

한국 개발자에게 주는 시사점

B2B SaaS를 만들다 보면 고객사에서 'SAML 지원되나요?'라는 질문이 반드시 와요. 그때 지켜야 할 원칙이 있어요. 첫째, 절대 직접 구현하지 마세요. Spring Security SAML, @node-saml, python3-saml, ruby-saml처럼 검증된 라이브러리를 쓰고 그 라이브러리의 보안 공지를 꼭 구독하세요. 둘째, 설정에서 '서명된 요소만 사용', '어설션 전체 서명 필수', '허용 알고리즘 제한' 같은 옵션을 다 켜세요. 셋째, 가능하면 OIDC 연동을 먼저 제안하고 SAML은 정말 필요한 고객에게만 열어주세요. 넷째, WorkOS나 Auth0처럼 SSO를 대신 처리해주는 서비스도 진지하게 고려해보세요. 인증 코드는 직접 짤수록 책임만 늘어나거든요.

마무리

한 줄 정리하면, SAML의 반복되는 취약점은 개발자가 부주의해서가 아니라 '서명 검증하는 코드와 데이터 읽는 코드가 서로 다른 것을 볼 수 있는' 구조가 겹겹이 쌓여 있기 때문이에요.

여러분 회사는 SSO 연동을 어떻게 하고 계신가요? SAML 라이브러리 버전은 언제 마지막으로 확인하셨나요? 고객사가 SAML을 고집할 때 OIDC로 설득해본 경험이 있다면 어떻게 하셨는지도 궁금해요.


🔗 출처: Hacker News

SOURCE · HACKER NEWS
원문 전체 보기 → https://blog.trailofbits.com/2026/09/21/saml-a-fractal-of-ba...
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기
처리 중...