142 lines
7.5 KiB
Bash
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"
|