SAML и рождение индустрии SSO

Security Assertion Markup Language (SAML) — это XML-язык разметки для заявлений безопасности. Создан в 2002 году Комитетом технических услуг безопасности (SSTC) Организации по развитию стандартов структурированной информации (OASIS). Старт уже не очень удачный с точки зрения современных стандартов. XML, несмотря на ряд преимуществ, гораздо более сложен, чем новые альтернативы вроде JSON. Кроме того, комитет комитетов с множеством заседаний — идеальный рецепт для "кухонного" дизайна протокола (водопадная методология, big design up front и прочее). И действительно, четыре (!) XML-протокола безопасности были объединены в один:

…интеллектуальная собственность, внесённая в SSTC:

  • Security Services Markup Language (S2ML) от Netegrity
  • AuthXML от Securant
  • XML Trust Assertion Service Specification (X-TASS) от VeriSign
  • Information Technology Markup Language (ITML) от Jamcracker

SAML: История

Однако спрос на такой протокол был очевидным. Когда интернет переходил с Web 1.0 девяностых на Web 2.0 начала нулевых, пользователи и организации нуждались в простом способе аутентификации на множество новых веб-сервисов. Академический сектор был главным двигателем этого движения, но не единственным: Central Authentication Service (CAS) в 2002 году в Йеле, Shibboleth IdP в 2003 году от Internet2 (консорциума исследовательских университетов), ADFS в 2003 году от Microsoft и simpleSAMLphp примерно в 2007 году от Uninett, государственной норвежской компании с тесными связями с академией. Все эти проекты аутентификации в конечном итоге поддерживали SAML в том или ином виде. Как и ARPANET раньше, университеты были в авангарде развития интернета и были первыми потребителями веб-сервисов. После того как этот базовый уровень доступности протокола был установлен, коммерческая индустрия взяла его и устремилась к многомиллиардной индустрии.

Индустрия SSO, идентификации и поставщиков аутентификации также начинала складываться в начале нулевых, но по-настоящему расцвела несколько лет спустя: Ping Identity (2002), OneLogin (2009), Okta (2009) и Duo Security (2010). Эти компании по сути были построены на протоколе SAML, за исключением Duo, которая представила свой первый SSO-продукт в 2015 году. Там началась история автора: он работал над первым продуктом Duo — Access Gateway (DAG) для развёртывания на предприятиях, который был построен на simpleSAMLphp и, конечно, протоколе SAML. Там он познакомился с SAML интимно и потратил много лет на изучение его длинной спецификации. Был там и когда Kelby Ludwig обнаружил XML comment bypass, но об атаках и недостатках SAML поговорим позже. Достаточно сказать, что индустрия SSO и поставщиков аутентификации бурно развивалась, и большая её часть была построена на протоколе SAML.

Трещина в броне

Атаки на обёртывание XML-подписей (XSW) — это проверия стрела в пятку Ахиллеса SAML. Хотя исследования как обёртывания подписей (2005, 2008 и 2009), так и самого SAML (2008) проводились раньше, но крёстным отцом всего этого считается работа "On Breaking SAML: Be Whoever You Want to Be" (2012). Она проверила теорию на практике и привела к автоматизированному способу проверки на XSW-атаки. Это было путеводной звездой при реализации DAG. Именно это заставило выбрать simpleSAMLphp в качестве основы. PHP, особенно в то время, не славился безопасностью, но послужной список simpleSAMLphp говорил сам за себя. simpleSAMLphp устойчива была к XSW в то время, когда почти никто не знал, что это такое:

Послужной список безопасности simpleSAMLphp (источник: On Breaking SAML)
Послужной список безопасности simpleSAMLphp (источник: On Breaking SAML)

Несмотря на то, что XSW были в центре внимания в этой статье 2012 года, они всё ещё присутствуют сегодня. Если мы знаем класс багов, то почему не можем его исправить? Но прежде чем входить в недостатки SAML, нужно рассмотреть зыбкий фундамент, на котором он построен: XML.

XML нельзя назвать успешным, когда дело касается послужного списка безопасности. Эти классы багов были бы более знакомы разработчикам девяностых, но тем не менее остаются присутствовать в XML и сегодня: XXE, расширение сущностей ("billion laughs"), получение DTD (SSRF), XPath/XQuery/XInclude/XSLT/CDATA-инъекции и многое другое. Библиотеке SAML нужно обработать все эти классы багов, прежде чем даже приступить к самой функциональности SAML.

В дополнение к классам багов безопасности, есть еще и чистая сложность XML по сравнению с чем-то вроде JSON. В XML есть теги, элементы, атрибуты, комментарии, пространства имён, разметка против содержания, схемы, CDATA, DOCTYPE и более того. В JSON, по сути, есть ключи, значения, объекты и списки. Сложность обычно противоречит безопасности, и это одна из причин, по которой SAML считается фракталом плохого дизайна.

Фрактал плохого дизайна

SAML предоставляет достаточно возможностей для обучения дизайну протокола. В этом разделе будут рассмотрены пять изъянов, которые считаются критичными для долгосрочной жизнеспособности SAML как протокола аутентификации. Эти изъяны также можно использовать при проектировании новых протоколов аутентификации. То есть можно либо избежать изъяна, либо взять его обратный вариант и попытаться встроить это в протокол.

Построен на XML

Как упомянуто выше, SAML построен на XML, и XML сложен, но это не вина комитета. XML — это то, что у них было в то время, и это то, что люди использовали. JSON был "открыт" в 2001 году, но это было как раз в то время, когда заседал комитет SAML, и маловероятно, что они спроектировали бы протокол аутентификации вокруг экспериментального нового формата. Особенно когда это ориентировано на JavaScript, а вы пишете много Java.

Можно было бы разработать количественное измерение сложности XML против JSON или SAML против JWT/OIDC (например, количество слов в спецификации/RFC, количество нормативных слов и т. д.), но это потребовало бы целой статьи или доклада. В целях остаться в теме я воздержусь от этого здесь и достаточно сказать, что XML значительно более сложен, чем что-то вроде JSON.

Нормализация

Нормализация (C14N) — это то, что вы делаете, когда хотите взять беспорядок XML, вычислить хеш и получить согласованные результаты. Другими словами, если SP и IdP не могут договориться о согласованном представлении данных XML, то байты не совпадут, подписи не совпадут и аутентификация не сработает. Однако это легче сказать, чем сделать.

Баги нормализации позволили обойти XML-комментарии Kelby в 2018 году:

Нормализация XML (источник: Identity Theft)
Нормализация XML (источник: Identity Theft)

Нормализация часто предшествует разностям парсеров и/или багам "round-trip", которые используют большинство современных SAML-атак:

Встроенные подписи

Встроенные подписи — не очень далёкий кузен нормализации. Коротко: если вы пытаетесь вставить подпись в полезную нагрузку данных, которые вы подписываете, то вас ждут проблемы. Давайте сравним JWT и SAML в этом смысле:

Подписи JWT против SAML (источник: jwt.io и samltool.io)
Подписи JWT против SAML (источник: jwt.io и samltool.io)

В примере JWT выше синяя подпись отделена от полезной нагрузки JSON и разделена в JWT точкой («.»). В примере SAML элемент Signature вставлен («встроен») в элемент Assertion. Проблема в том, что очень сложно получить представление данных байт-в-байт, нормализованное, когда вы также его изменяете! Тем более когда у вас есть сложный формат вроде XML и сложные правила нормализации.

Дизайн "кухонной мойки"

Этот недостаток дизайна по сути преобразуется в принцип YAGNI (you aren't gonna need it). Это не совсем справедливая характеристика, потому что части спецификации SAML, которые используются в реальном мире, менялись на протяжении 20 лет (извините, SOAP и artifact binding). Однако дело в том, что 99% современных реализаций SAML используют очень похожую структуру данных и подмножество спецификации. Любая аутентификация SAML, с которой вы столкнётесь в реальном мире сегодня, вероятно, избегает 90% спецификации. Это добавляет значительную сложность для в основном неиспользуемых функций.

Если бы я добавлял поддержку SAML к чему-то новому, я бы рассмотрел, помимо всех стандартных проверок SAML, также отклонение любого сообщения, которое не имеет того же формата, который генерируют Okta, Onelogin, Google или Shib.

Thomas Ptacek, 2021

Окостенение

SAML был спроектирован в другую эпоху для других времён и не получил необходимых обновлений. Эти проблемы обычно имеют практическое значение, а не теоретическое. Под окостенением я понимаю примерно следующее:

  1. OIDC предполагает HTTP, в то время как SAML независим от транспорта. Конечно, существуют HTTP-привязки SAML и они используются наиболее часто, но они не обязательны. Это даёт SAML определённую степень гибкости, но также означает, что эта гибкость должна быть хорошо определена, должна быть реализована где-то и может содержать баги. SAML появился в то время, когда HTTP + TLS ещё не был доминирующей основой коммуникации веб-сервисов, и никогда не примирился с этой современной ситуацией. HTTPS позволяет OIDC переложить зашифрованную, доверенную коммуникацию на уровень транспорта.
  2. OIDC обычно предполагает подключённую топологию сети, в то время как SAML — нет. Наиболее распространённый поток OIDC (код авторизации) предполагает, что OpenID Provider (OP) и Relying Party (RP) могут общаться напрямую (OIDC OP/RP == SAML IdP/SP). Конечно, implicit flow с form post существует, но сегодня его очень редко видят. Кроме того, SAML имеет artifact binding для прямой коммуникации, но это также очень редко. Суть в том, что если OP и RP могут общаться напрямую, то это снимает давление на полезную нагрузку ответа аутентификации, чтобы она содержала всю информацию, необходимую для принятия решения об аутентификации и авторизации. Это уменьшает размер полезной нагрузки и сложность. OP и RP в OIDC могут вместо этого обмениваться информацией в побочном канале.
  3. OIDC эволюционировал органически на протяжении лет, в то время как SAML был в основном спроектирован заранее. OIDC включает в себя десятки спецификаций и RFC, которые эволюционировали органически на протяжении многих лет. Эти документы обычно создавались для решения конкретной потребности, а не для попытки предугадать все будущие потребности и построить это с нуля. Это похоже на agile-методологию против waterfall, как описано в первом разделе. Рассмотрите следующую неполную временную шкалу:
    1. Опубликована спецификация OpenID Connect 1.0 (2014)
    2. JOSE-стек финализирован для JW{S,E,K,A,T} через RFC 7515-7519 (2015)
    3. PKCE опубликован через RFC 7636 (2015)
    4. PKCE для мобильных/родных приложений опубликован через RFC 8252 (2017)
    5. Device authorization grant для IoT-устройств опубликован через RFC 8628 (2019)
    6. Demonstrating proof of possession (DPoP) для MFA-рабочих процессов опубликован через RFC 9449 (2023)
    7. PKCE для SPA опубликован через RFC 10017 (2026)

Исторические обстоятельства также интересны. Например, SAML набирал обороты в эпоху VPN и сегментации сети, отсюда пункт (2) выше. Если IdP или SP находились за корпоративным брандмауэром и не могли напрямую говорить друг с другом, то весь процесс развёртывания прерывался и этот поставщик терял торговую сделку. SAML должен был бесперебойно учитывать эту ситуацию. Модель BeyondCorp компании Google и архитектура нулевого доверия перевернули это представление с ног на голову в 2014 году. Кроме того, SAML не предусмотрел революции мобильных, SPA и IoT и не имел ответов, когда эти технологии появились на сцене в конце нулевых. Хотя SAML всё ещё широко используется в корпоративных средах, эти макроскопические события помогли начать долгое, медленное снижение протокола. Гибкость и слабая связь позволяют быстро адаптироваться в постоянно меняющихся IT-средах.

Все дороги ведут к OIDC

Идеального протокола нет, но в плане решения все дороги ведут к OIDC. Насколько я понимаю, единственный сценарий развёртывания, в котором SAML имел преимущество — это сети, где SP и IdP не могут общаться напрямую. OIDC implicit flow с form post предоставляет все те же компоненты. Это фактически один из наиболее чистых планов миграции, которые я видел в индустрии.

Итак, что я могу сделать, если я поставщик сервиса (то есть SP) и я хотел бы интегрироваться в экосистему SSO без SAML? Просто поддерживайте OIDC. Забудьте про SAML. По-видимому, Fly.io и Tailscale уже это делают:

Нам удалось держать линию на OIDC до сих пор. То же самое сделала Tailscale. Если Tailscale может держать линию, учитывая, кому они продают, я думаю, что большинство организаций могут. Действительно, старайтесь избегать SAML. Помните, как поставщик, вы часто конкурируете с компаниями, которые вообще не делают реальную интеграцию SSO.

Thomas Ptacek, 2024

Итак, что я могу сделать, если я поставщик идентификации или аутентификации (то есть IdP) и я хотел бы отойти от SAML? Ну, в зависимости от количества ваших клиентов, это может быть долгая дорога. Но как вы едите слона? По одному куску за раз. Это хорошо пройденный путь в индустрии: разработайте план устаревания, сообщите об этом клиентам, прекратите подключение новых клиентов к интеграциям SAML, предоставьте существующим клиентам SAML эквивалентные конфигурации OIDC, установите дату закрытия и приступайте к работе.

SAML имел хороший 25-летний период. Он дал рождение индустрии SSO, помог защитить бесчисленное количество аутентификаций, улучшил UX аутентификации на десятки веб-сервисов и создал миллиарды долларов экономического воздействия. Мы должны быть благодарны создателям протокола SAML. Он дал нам отличный пример обучения дизайну протокола и эволюции за очень динамичный период в индустрии технологий.

Если вы хотите прочитать больше об исторических анализах тем безопасности, то ознакомьтесь с "Marshal madness: A brief history of Ruby deserialization exploits".

Свяжитесь с нами если вы интересуетесь аудитом дизайна протокола или хотели бы провести обзор вашей системы аутентификации.