Synced files: - Quote wizard frontend (all components, hooks, types, config) - API updates (config, models, routers, schemas, services) - Client work (bg-builders, gurushow) - Scripts (BGB Lesley termination, CIPP, Datto, migration) - Temp files (Bardach contacts, VWP investigation, misc) - Credentials and session logs - Email service, PHP API, session logs Machine: ACG-M-L5090 Timestamp: 2026-03-10 19:11:00 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
200 lines
6.6 KiB
Python
200 lines
6.6 KiB
Python
"""
|
|
VWP BEC Incident - Send phishing notification to all affected recipients.
|
|
Sends from JR's account via Microsoft Graph API sendMail endpoint.
|
|
"""
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import os
|
|
|
|
# === Configuration ===
|
|
TENANT_ID = "5c53ae9f-7071-4248-b834-8685b646450f"
|
|
APP_ID = "fabb3421-8b34-484b-bc17-e46de9703418"
|
|
APP_SECRET = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO"
|
|
JR_USER_ID = "0af923d0-48c5-4cc1-8553-c60625802815"
|
|
JR_EMAIL = "j-r@valleywideplastering.com"
|
|
|
|
RECIPIENTS_FILE = r"D:\ClaudeTools\temp\vwp_exchange_recipients.json"
|
|
|
|
# Addresses to exclude (lowercase for comparison)
|
|
EXCLUDE_EXACT = {
|
|
"j-r@valleywideplastering.com",
|
|
"jr@valleywideplastering.com",
|
|
"jr@casarica.net",
|
|
"jrsuperwall@gmail.com",
|
|
"kathya@azappliancehome.com.com", # malformed double .com
|
|
}
|
|
|
|
EXCLUDE_DOMAINS = ["bidmail.com", "onmicrosoft.com"]
|
|
|
|
|
|
def get_access_token():
|
|
"""Acquire OAuth2 token via client credentials flow."""
|
|
token_url = f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token"
|
|
result = subprocess.run(
|
|
[
|
|
"curl", "-s", "-X", "POST", token_url,
|
|
"-H", "Content-Type: application/x-www-form-urlencoded",
|
|
"-d", f"client_id={APP_ID}&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&client_secret={APP_SECRET}&grant_type=client_credentials",
|
|
],
|
|
capture_output=True, text=True
|
|
)
|
|
resp = json.loads(result.stdout)
|
|
if "access_token" not in resp:
|
|
print("[ERROR] Failed to get access token:")
|
|
print(json.dumps(resp, indent=2))
|
|
sys.exit(1)
|
|
print("[OK] Access token acquired")
|
|
return resp["access_token"]
|
|
|
|
|
|
def load_recipients():
|
|
"""Load and filter recipients from the JSON file."""
|
|
with open(RECIPIENTS_FILE, encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
raw = data["all_unique_recipients"]
|
|
print(f"[INFO] Raw recipient count: {len(raw)}")
|
|
|
|
# Strip surrounding single quotes and whitespace
|
|
cleaned = [addr.strip().strip("'").strip() for addr in raw]
|
|
|
|
# Filter and deduplicate (case-insensitive)
|
|
seen = set()
|
|
filtered = []
|
|
for addr in cleaned:
|
|
lower = addr.lower()
|
|
|
|
# Skip excluded exact addresses
|
|
if lower in EXCLUDE_EXACT:
|
|
continue
|
|
|
|
# Skip excluded domain patterns
|
|
if any(domain in lower for domain in EXCLUDE_DOMAINS):
|
|
continue
|
|
|
|
# Deduplicate
|
|
if lower not in seen:
|
|
seen.add(lower)
|
|
filtered.append(addr)
|
|
|
|
print(f"[INFO] After filtering and dedup: {len(filtered)} BCC recipients")
|
|
return filtered
|
|
|
|
|
|
def send_notification(token, bcc_recipients):
|
|
"""Send the notification email via Graph API sendMail endpoint."""
|
|
subject = 'Security Notice - Do Not Open Box.com File "Valley Wide Plastering, INC......pdf"'
|
|
|
|
body_html = (
|
|
'<p>You recently received a file sharing invitation from my Box.com account '
|
|
'for a file named "Valley Wide Plastering, INC......pdf".</p>'
|
|
'\n\n'
|
|
'<p>This file was <strong>NOT</strong> sent by me. My email account was temporarily '
|
|
'compromised, and the attacker used it to distribute this malicious link through '
|
|
'Box.com. The issue has been resolved and my account has been secured.</p>'
|
|
'\n\n'
|
|
'<p><strong>Please take the following steps:</strong></p>'
|
|
'\n'
|
|
'<ol>'
|
|
'<li>Do NOT open or click the Box.com link if you haven\'t already</li>'
|
|
'<li>If you DID click the link or entered any credentials on the page, please '
|
|
'change your password immediately and notify your IT department</li>'
|
|
'<li>Delete the Box.com sharing notification email from your inbox</li>'
|
|
'<li>If you created a Box.com account as a result of this invitation, consider removing it</li>'
|
|
'</ol>'
|
|
'\n\n'
|
|
'<p>We apologize for the inconvenience. If you have any questions or concerns, '
|
|
'please contact our office directly.</p>'
|
|
'\n\n'
|
|
'<p>JR Guerrero<br>Valley Wide Plastering, Inc.</p>'
|
|
)
|
|
|
|
payload = {
|
|
"message": {
|
|
"subject": subject,
|
|
"body": {
|
|
"contentType": "HTML",
|
|
"content": body_html
|
|
},
|
|
"toRecipients": [
|
|
{"emailAddress": {"address": JR_EMAIL}}
|
|
],
|
|
"bccRecipients": [
|
|
{"emailAddress": {"address": addr}} for addr in bcc_recipients
|
|
]
|
|
},
|
|
"saveToSentItems": True
|
|
}
|
|
|
|
# Write payload to temp file to avoid command-line length limits
|
|
tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False, encoding="utf-8")
|
|
json.dump(payload, tmp, ensure_ascii=False)
|
|
tmp.close()
|
|
|
|
send_url = f"https://graph.microsoft.com/v1.0/users/{JR_USER_ID}/sendMail"
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
[
|
|
"curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
|
|
"-X", "POST", send_url,
|
|
"-H", f"Authorization: Bearer {token}",
|
|
"-H", "Content-Type: application/json",
|
|
"-d", f"@{tmp.name}",
|
|
],
|
|
capture_output=True, text=True
|
|
)
|
|
status_code = result.stdout.strip()
|
|
print(f"[INFO] HTTP status code: {status_code}")
|
|
|
|
if status_code == "202":
|
|
print("[SUCCESS] Notification email sent successfully!")
|
|
else:
|
|
print(f"[ERROR] Unexpected status code: {status_code}")
|
|
# Re-run to capture response body for debugging
|
|
result2 = subprocess.run(
|
|
[
|
|
"curl", "-s",
|
|
"-X", "POST", send_url,
|
|
"-H", f"Authorization: Bearer {token}",
|
|
"-H", "Content-Type: application/json",
|
|
"-d", f"@{tmp.name}",
|
|
],
|
|
capture_output=True, text=True
|
|
)
|
|
print(result2.stdout[:2000])
|
|
finally:
|
|
os.unlink(tmp.name)
|
|
|
|
|
|
def main():
|
|
print("=" * 60)
|
|
print("VWP BEC Incident - Phishing Notification Sender")
|
|
print("=" * 60)
|
|
|
|
# Step 1: Load and filter recipients
|
|
bcc_recipients = load_recipients()
|
|
|
|
# Print all recipients for verification
|
|
print("\n[INFO] BCC recipient list:")
|
|
for i, addr in enumerate(bcc_recipients, 1):
|
|
print(f" {i:3d}. {addr}")
|
|
print(f"\n[INFO] Total BCC recipients: {len(bcc_recipients)}")
|
|
print(f"[INFO] To recipient: {JR_EMAIL}")
|
|
|
|
# Step 2: Get access token
|
|
token = get_access_token()
|
|
|
|
# Step 3: Send the email
|
|
print(f"\n[INFO] Sending notification email...")
|
|
send_notification(token, bcc_recipients)
|
|
|
|
print("\n[OK] Done.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|