
PoC с использованием loopback для уязвимости SAML-брокера в Keycloak 26.8.1: процентно-кодированное дублированное имя SAMLResponse обходит проверку подписи при перенаправлении, позволяя войти в систему как другой пользователь.
Тот, у кого нет учётной записи Keycloak, может завершить корпоративный вход через SAML как другой человек. Он один раз входит у вышестоящего поставщика идентификации, сохраняет этот подписанный редирект и подставляет перед ним второй SAML-документ под именем в процентном кодировании. Keycloak проверяет подлинную подпись и затем выполняет вход под именем из поддельного документа. Для этого нужен SAML-брокер, подписывающий ответы HTTP-Redirect, с включённой проверкой подписи и отключёнными подписями утверждений, что и настраивается при импорте метаданных. Это не разделение по точке с запятой из issue #53354.
Класс: OAuth leftover Доступ: Удалённый (без аутентификации в Keycloak; нужен SAML-брокер, подписывающий редиректы)
Keycloak 26.8.1 (24ed1dad) - Red Hat
| ID | CVE пока нет |
| Класс | OAuth leftover |
| Доступ | Удалённый (без учётной записи Keycloak; нужна одна подлинная подпись HTTP-Redirect от IdP) |
| CWE | CWE-287 / CWE-347 / CWE-116 |
| CVSS | Высокий: 7.4 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N |
| Продукт | Keycloak |
| Затронуто | 26.8.1 (24ed1dadf3f246a555adb6d3d4a99e9b3003fb3b); Quarkus 3.40.1; образ quay.io/keycloak/keycloak:26.8.1 |
| Аутентификация | отсутствует в Keycloak. Нужен брокер поставщика идентификации SAML с validateSignature=true и отсутствующим или ложным wantAssertionsSigned. |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | только 127.0.0.1:18084 |
Нужен NameID жертвы (обычно email) и один настоящий подписанный HTTP-Redirect от этого IdP. Пароль Keycloak не нужен. Почтовый ящик не нужен. IdP только с POST не выдаёт эту подпись. ACS по GET остаётся смонтированным, даже когда импорт метаданных предпочитает POST.
| Привилегия | Метод / точка входа | Разведка / целостность / доступность | Доказано в лаборатории или выведено |
|---|---|---|---|
| Привязать поддельный SAML-документ, пока подлинная подпись всё ещё проверяется | GET broker ACS с SAML%52esponse=<forged> перед подписанным SAMLResponse | Целостность (аутентификация) | Доказано в лаборатории. Контрольный email [email protected]. Атакующий email [email protected] |
| Создать пользователя-жертву через first-broker-login | callback.authenticated по поддельному NameID | Целостность (создание учётной записи) | Доказано в лаборатории. Число жертв 0 до разделённого запроса, 1 после |
| Сессия как существующий брокерский пользователь, включая администратора, использующего тот же IdP | Тот же ACS, NameID этого пользователя | Целостность (захват учётной записи) | Выведено. Лаборатория создала жертву при первом входе |
| Сохранить RelayState и сессию аутентификации | RelayState остаётся подписанным значением | Целостность | Исходный код. Подпись покрывает RelayState |
| Негативный контроль | Подлинный подписанный редирект без лишнего параметра | н/д | Доказано в лаборатории. Контроль завершается под email атакующего |
Остаётся закрытым в этом leftover
| Ось | Метод / точка входа | Почему закрыто |
|---|---|---|
| Целостность | Разделение по точке с запятой SAMLResponse=...;SAMLResponse=... | issue #53354. quarkus.http.use-semicolon-as-query-param-delimiter=false |
| Целостность | Неподписанное утверждение при wantAssertionsSigned=true | handleLoginResponse тогда требует подпись утверждения |
| Целостность | Нет SAML-брокера или IdP только с POST без подписи редиректа | Злоумышленник никогда не получает SigAlg + Signature при GET |
| Целостность | RCE на хосте, Admin REST без аутентификации | Вне класса |
| Целостность | Форма имени по умолчанию VERIFY_PROFILE | Доказано в лаборатории как шаг завершения, а не опровержение. Форма уже содержит брокерский email |
Я зафиксировал Keycloak 26.8.1. RESTEasy Reactive берёт @QueryParam("SAMLResponse") из Vert.x, который выполняет percent-декодирование имён параметров запроса, поэтому SAML%52esponse схлопывается в SAMLResponse, и побеждает первое значение. verifyRedirectSignature пересобирает подписанную строку из getQueryParameters(false) по исходному запросу, где это имя остаётся другим ключом.
Уже зафиксированные ошибочные пути: трактовка #53354 как этого остатка (percent-кодированное имя, а не точка с запятой); следование за 302-ответами ACS в лабораторном клиенте и отбрасывание AUTH_SESSION_ID; восприятие пустых firstName/lastName при VERIFY_PROFILE как промаха (email уже является brokered NameID); reverse shell. Театр.
Python 3.11+ с cryptography. Требуется Docker.
python3 keycloak-saml-redirect-param-split-Abraxas-Labs.py
или:
cd lab
./run.sh
Цель — только http://127.0.0.1:18084. Образ quay.io/keycloak/keycloak:26.8.1. Лаунчер выполняет chdir в lab/ и запускает run.sh. Лабораторный IdP работает в том же процессе. Он никогда не обращается к реальному провайдеру идентификации.
IOC image=quay.io/keycloak/keycloak:26.8.1 label-version=26.8.1
IOC serverinfo-version=26.8.1
IOC validate-signature=true want-assertions-signed=absent
IOC review-profile=DISABLED
IOC [email protected]
IOC victim-before=0
IOC [email protected]
IOC victim-after=1
SUCCESS KC-H04-SAML-REDIRECT kcs04b8n1
Проверяйте тот же документ запроса, который разбирает эндпоинт. Выполняйте процентное декодирование имён параметров перед восстановлением подписи или отклоняйте запрос, содержащий после декодирования более одного SAMLResponse. Включите wantAssertionsSigned для редирект-брокеров, включая путь импорта метаданных.
24ed1dadf3f246a555adb6d3d4a99e9b3003fb3b)SAMLEndpoint.redirectBinding (SAMLEndpoint.java:182-183, :779-813, :896-914)SamlProtocolUtils.verifyRedirectSignature (SamlProtocolUtils.java:216-249)KeycloakUriInfo.getQueryParameters (KeycloakUriInfo.java:147-163)GNU Affero GPL v3.0. См. LICENSE. Только для лаборатории на loopback. Без гарантий.
Abraxas Labs [email protected]
Пожалуйста, указывайте авторство CVE за Abraxas Labs.