Files
claudetools/.claude/skills/remediation-tool/scripts/user-breach-check.sh
Howard Enos 2f0bc654a1 sync: auto-sync from ACG-TECH03L at 2026-04-20 14:15:01
Author: Howard Enos
Machine: ACG-TECH03L
Timestamp: 2026-04-20 14:15:01
2026-04-20 14:15:07 -07:00

142 lines
7.5 KiB
Bash

#!/usr/bin/env bash
# Run the 10-point breach check on a single user.
# Usage: user-breach-check.sh <tenant-id-or-domain> <upn>
# Writes raw JSON to /tmp/remediation-tool/{tenant-id}/user-breach/{user-slug}/
# Prints a summary table to stdout.
set -euo pipefail
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
TENANT_INPUT="${1:?usage: user-breach-check.sh <tenant-id|domain> <upn>}"
UPN="${2:?usage: user-breach-check.sh <tenant-id|domain> <upn>}"
TENANT_ID=$("$SCRIPT_DIR/resolve-tenant.sh" "$TENANT_INPUT")
GT=$("$SCRIPT_DIR/get-token.sh" "$TENANT_ID" investigator)
EXO=$("$SCRIPT_DIR/get-token.sh" "$TENANT_ID" investigator-exo) || EXO=""
USER_SLUG=$(echo "$UPN" | tr '@.' '__')
OUT="/tmp/remediation-tool/$TENANT_ID/user-breach/$USER_SLUG"
mkdir -p "$OUT"
echo "[info] tenant=$TENANT_ID user=$UPN out=$OUT"
# --- 0. Resolve user object ID ---
curl -s -H "Authorization: Bearer $GT" \
"https://graph.microsoft.com/v1.0/users/${UPN}?\$select=id,displayName,userPrincipalName,mail,accountEnabled,createdDateTime,lastPasswordChangeDateTime" \
> "$OUT/00_user.json"
UID_=$(jq -r '.id // empty' "$OUT/00_user.json")
if [[ -z "$UID_" ]]; then
echo "ERROR: user not found or Graph returned error" >&2
cat "$OUT/00_user.json" >&2
exit 1
fi
echo "[info] object id: $UID_"
# --- 1. Inbox rules (Graph v1.0 — visible only) ---
curl -s -H "Authorization: Bearer $GT" \
"https://graph.microsoft.com/v1.0/users/${UPN}/mailFolders/inbox/messageRules" \
> "$OUT/01_inbox_rules_graph.json" &
# --- 2. Mailbox settings (forwarding flags) ---
curl -s -H "Authorization: Bearer $GT" \
"https://graph.microsoft.com/v1.0/users/${UPN}/mailboxSettings" \
> "$OUT/02_mailbox_settings.json" &
# --- 4. OAuth consents + app role assignments ---
curl -s -H "Authorization: Bearer $GT" \
"https://graph.microsoft.com/v1.0/users/${UPN}/oauth2PermissionGrants" \
> "$OUT/04a_oauth_grants.json" &
curl -s -H "Authorization: Bearer $GT" \
"https://graph.microsoft.com/v1.0/users/${UPN}/appRoleAssignments" \
> "$OUT/04b_app_role_assignments.json" &
# --- 5. Authentication methods ---
curl -s -H "Authorization: Bearer $GT" \
"https://graph.microsoft.com/v1.0/users/${UPN}/authentication/methods" \
> "$OUT/05_auth_methods.json" &
wait
# --- 6. Sign-ins 30d (v1.0 — interactive only) ---
FROM=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)
curl -s -H "Authorization: Bearer $GT" \
"https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=userId%20eq%20'${UID_}'%20and%20createdDateTime%20ge%20${FROM}&\$top=200" \
> "$OUT/06_signins.json" &
# --- 7. Directory audits (targetResources = user) 30d ---
curl -s -H "Authorization: Bearer $GT" \
"https://graph.microsoft.com/v1.0/auditLogs/directoryAudits?\$filter=activityDateTime%20ge%20${FROM}%20and%20targetResources/any(t:t/id%20eq%20'${UID_}')&\$top=200" \
> "$OUT/07_dir_audits.json" &
# --- 8. Risky user + risk detections (403 if app lacks IdentityRiskyUser scope) ---
curl -s -H "Authorization: Bearer $GT" \
"https://graph.microsoft.com/v1.0/identityProtection/riskyUsers/${UID_}" \
> "$OUT/08a_risky_user.json" &
curl -s -H "Authorization: Bearer $GT" \
"https://graph.microsoft.com/v1.0/identityProtection/riskDetections?\$filter=userId%20eq%20'${UID_}'&\$top=100" \
> "$OUT/08b_risk_detections.json" &
# --- 9. Sent items (last 25) ---
curl -s -H "Authorization: Bearer $GT" \
"https://graph.microsoft.com/v1.0/users/${UPN}/mailFolders/sentitems/messages?\$top=25&\$orderby=sentDateTime%20desc&\$select=subject,toRecipients,sentDateTime,from" \
> "$OUT/09_sent.json" &
# --- 10. Deleted items (last 25) ---
curl -s -H "Authorization: Bearer $GT" \
"https://graph.microsoft.com/v1.0/users/${UPN}/mailFolders/deleteditems/messages?\$top=25&\$orderby=receivedDateTime%20desc&\$select=subject,from,receivedDateTime" \
> "$OUT/10_deleted.json" &
wait
# --- 3. Exchange REST (hidden rules + delegates + SendAs + Get-Mailbox) ---
if [[ -n "$EXO" ]]; then
EX_URL="https://outlook.office365.com/adminapi/beta/${TENANT_ID}/InvokeCommand"
curl -s -X POST -H "Authorization: Bearer $EXO" -H "Content-Type: application/json" "$EX_URL" \
-d "{\"CmdletInput\":{\"CmdletName\":\"Get-InboxRule\",\"Parameters\":{\"Mailbox\":\"${UPN}\",\"IncludeHidden\":true}}}" \
> "$OUT/03a_InboxRule_hidden.json" &
curl -s -X POST -H "Authorization: Bearer $EXO" -H "Content-Type: application/json" "$EX_URL" \
-d "{\"CmdletInput\":{\"CmdletName\":\"Get-MailboxPermission\",\"Parameters\":{\"Identity\":\"${UPN}\"}}}" \
> "$OUT/03b_MailboxPermission.json" &
curl -s -X POST -H "Authorization: Bearer $EXO" -H "Content-Type: application/json" "$EX_URL" \
-d "{\"CmdletInput\":{\"CmdletName\":\"Get-RecipientPermission\",\"Parameters\":{\"Identity\":\"${UPN}\"}}}" \
> "$OUT/03c_RecipientPermission.json" &
curl -s -X POST -H "Authorization: Bearer $EXO" -H "Content-Type: application/json" "$EX_URL" \
-d "{\"CmdletInput\":{\"CmdletName\":\"Get-Mailbox\",\"Parameters\":{\"Identity\":\"${UPN}\"}}}" \
> "$OUT/03d_Mailbox.json" &
wait
else
echo "[warn] no Exchange token; skipping check 3 (hidden rules/delegates/SendAs/mailbox forwarding flags)"
fi
# --- Summary table ---
echo ""
echo "=== Summary: $UPN ==="
jq -r '"account_enabled: \(.accountEnabled) lastPwChange: \(.lastPasswordChangeDateTime) created: \(.createdDateTime)"' "$OUT/00_user.json"
echo "01 inbox_rules (Graph): $(jq '.value | length // "error"' "$OUT/01_inbox_rules_graph.json")"
echo "02 forwarding: fwdSmtp=$(jq -r '.automaticRepliesSetting.status // "n/a"' "$OUT/02_mailbox_settings.json" 2>/dev/null) (see mailbox Get-Mailbox for forwarding fields)"
echo "04a oauth_grants: $(jq '.value | length // "error"' "$OUT/04a_oauth_grants.json")"
echo "04b app_role_assignments: $(jq '.value | length // "error"' "$OUT/04b_app_role_assignments.json")"
echo "05 auth_methods: $(jq '.value | length // "error"' "$OUT/05_auth_methods.json")"
echo "06 signins (30d, interactive): $(jq '.value | length // "error"' "$OUT/06_signins.json") non-US: $(jq '[.value[]?|select(.location.countryOrRegion != "US" and .location.countryOrRegion != null)] | length' "$OUT/06_signins.json" 2>/dev/null)"
echo "07 dir_audits (30d): $(jq '.value | length // "error"' "$OUT/07_dir_audits.json")"
echo "08 risky_user: $(jq -r '.riskLevel // .error.code // "none"' "$OUT/08a_risky_user.json" 2>/dev/null)"
echo "08 risk_detections: $(jq '.value | length // (.error.code // "error")' "$OUT/08b_risk_detections.json")"
echo "09 sent (recent 25): $(jq '.value | length // "error"' "$OUT/09_sent.json")"
echo "10 deleted (recent 25): $(jq '.value | length // "error"' "$OUT/10_deleted.json")"
if [[ -f "$OUT/03a_InboxRule_hidden.json" ]]; then
HIDDEN=$(jq '.value | length // (.error.code // "?")' "$OUT/03a_InboxRule_hidden.json" 2>/dev/null || echo "?")
echo "03a hidden_inbox_rules: $HIDDEN"
echo "03b mailbox_permissions: $(jq '[.value[]? | select(.User != "NT AUTHORITY\\SELF")] | length // "?"' "$OUT/03b_MailboxPermission.json" 2>/dev/null) non-SELF"
echo "03c send_as: $(jq '[.value[]? | select(.Trustee != "NT AUTHORITY\\SELF")] | length // "?"' "$OUT/03c_RecipientPermission.json" 2>/dev/null) non-SELF"
echo "03d mailbox_forwarding: fwdAddr=$(jq -r '.value[0].ForwardingAddress // "null"' "$OUT/03d_Mailbox.json" 2>/dev/null) fwdSmtp=$(jq -r '.value[0].ForwardingSmtpAddress // "null"' "$OUT/03d_Mailbox.json" 2>/dev/null)"
else
echo "03 exchange_rest: SKIPPED (no exchange token — tenant likely needs Exchange Admin role assigned)"
fi
echo ""
echo "[info] raw artifacts: $OUT"