diff --git a/config/managed_schema.json b/config/managed_schema.json index fa279af..bd4aabc 100644 --- a/config/managed_schema.json +++ b/config/managed_schema.json @@ -160,6 +160,76 @@ "default": "" } } + }, + "domainSquatting": { + "title": "Domain Squatting Detection", + "description": "Configuration for domain squatting detection to protect against typosquatting, homoglyphs, and combosquatting attacks. Enable/disable is controlled by the detection rules JSON. Domains are automatically extracted from the URL allowlist.", + "type": "object", + "properties": { + "deviationThreshold": { + "title": "Deviation Threshold", + "description": "Maximum number of character differences (Levenshtein distance) to trigger detection. Lower values are stricter.", + "type": "integer", + "minimum": 1, + "maximum": 5, + "default": 2 + }, + "algorithms": { + "title": "Detection Algorithms", + "description": "Enable or disable specific detection algorithms", + "type": "object", + "properties": { + "levenshtein": { + "title": "Levenshtein Distance", + "description": "Detect domains with small character differences", + "type": "boolean", + "default": true + }, + "homoglyph": { + "title": "Homoglyph Detection", + "description": "Detect confusable characters (e.g., 'a' vs 'а')", + "type": "boolean", + "default": true + }, + "typosquat": { + "title": "Typosquatting Detection", + "description": "Detect common typing mistakes and character swaps", + "type": "boolean", + "default": true + }, + "combosquat": { + "title": "Combosquatting Detection", + "description": "Detect domains with added prefixes/suffixes", + "type": "boolean", + "default": true + } + } + }, + "protectedDomains": { + "title": "Additional Protected Domains", + "description": "OPTIONAL: Additional domains to protect beyond those automatically extracted from the URL allowlist. Normally you should just add domains to the URL allowlist instead.", + "type": "array", + "items": { + "type": "string", + "title": "Domain", + "description": "Domain name to protect (e.g., 'company.com')" + }, + "default": [] + }, + "action": { + "title": "Action", + "description": "Action to take when domain squatting is detected", + "type": "string", + "enum": ["block", "warn", "log"], + "default": "block" + }, + "logDetections": { + "title": "Log Detections", + "description": "Log all domain squatting detections to activity log", + "type": "boolean", + "default": true + } + } } } } \ No newline at end of file diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 6023154..38ff855 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -3,6 +3,10 @@ - [About](README.md) - [Firefox Support](firefox-support.md) +## Features + +- [Domain Squatting Detection](features/domain-squatting-detection.md) + ## Deployment - [Chrome/Edge Deployment Instructions](deployment/chrome-edge-deployment-instructions/README.md) diff --git a/docs/features/domain-squatting-detection.md b/docs/features/domain-squatting-detection.md new file mode 100644 index 0000000..3530aa4 --- /dev/null +++ b/docs/features/domain-squatting-detection.md @@ -0,0 +1,226 @@ +# Domain Squatting Detection + +Domain squatting protection helps keep you safe from fake websites that try to trick you by using look-alike domain names. Attackers create these fake domains to steal your login credentials. + +## What is Domain Squatting? + +Domain squatting (sometimes called "typosquatting") is when attackers register website addresses that are intentionally similar to legitimate sites. For example: + +- `micros0ft.com` (using a zero instead of the letter O) +- `microsоft.com` (using a Cyrillic "о" that looks like an English "o") +- `login-microsoft.com` (adding extra words to a real domain) + +These fake sites often look exactly like the real Microsoft login page, but they're designed to capture your username and password. + +## How Check Protects You + +Check automatically watches for these fake domains using four smart detection methods: + +### 1. **Character Difference Detection** +Spots domains where characters are changed, missing, or swapped around. + +**Examples Check catches:** +- `microsft.com` → missing the letter "o" +- `micorsoft.com` → letters swapped ("or" instead of "ro") +- `microosoft.com` → extra letter added + +### 2. **Look-Alike Character Detection** +Finds domains using special characters that look similar to normal letters. + +**Examples Check catches:** +- `micrоsoft.com` → uses a Cyrillic "о" that looks like an English "o" +- `microsоft.com` → mixes different alphabet characters +- `micro𝐬oft.com` → uses special Unicode characters + +### 3. **Typing Mistake Detection** +Identifies domains based on common typing errors and keyboard slip-ups. + +**Examples Check catches:** +- `micrisoft.com` → finger slipped to nearby key +- `microssoft.com` → double-typed a letter +- `microosft.com` → typo mixing up letters + +### 4. **Suspicious Word Combination Detection** +Spots domains that add words before or after legitimate domains to look more official. + +**Examples Check catches:** +- `secure-microsoft.com` +- `login-microsoft-verify.com` +- `microsoft-auth.com` +- `official-microsoft-support.com` + +Common suspicious words attackers use: `login`, `secure`, `verify`, `official`, `support`, `auth`, `signin`, `portal` + +## What Domains Are Protected? + +Check protects **30+ popular domains** by default, including: + +**Microsoft Services:** +- microsoft.com, microsoftonline.com, office.com, outlook.com, onedrive.com, and more + +**Other Popular Services:** +- google.com, github.com, facebook.com, amazon.com, apple.com, paypal.com, and more + +**Plus: Your URL Allowlist** + +{% hint style="info" %} +**Unified Protection:** Check uses your [URL Allowlist](../settings/detection-rules.md#url-allowlist-regex-or-url-with-wildcards) for double protection. Any domains you add there are automatically protected from squatting attempts too! + +For example, if you add `https://yourcompany.com/*` to your allowlist, Check will also protect against fake domains like `yourcompany.net` or `your-company.com`. +{% endhint %} + +## How It Works in Practice + +When you visit a website, Check automatically: + +1. **Checks** if the domain looks similar to any protected domain +2. **Analyzes** using all four detection methods +3. **Warns** you if it finds a suspicious match +4. **Blocks** the page if it's clearly a phishing attempt + +You don't need to do anything - the protection works automatically in the background! + +## Configuration + +{% hint style="warning" %} +**For most users**: Domain squatting detection works automatically with default settings. You don't need to change anything! +{% endhint %} + +### Page Blocking Control + +Check has an **"Enable Page Blocking"** setting in the extension options that controls how suspicious pages are handled: + +- **Page Blocking Enabled** + **Action: "block"** = Page is completely blocked with full-page warning +- **Page Blocking Enabled** + **Action: "warn"** = Warning banner shown, page remains accessible +- **Page Blocking Disabled** = Warning banner shown regardless of action setting (never blocks) + +This gives you control over whether you want aggressive blocking or just warnings for suspicious domains. + +### For Advanced Users and IT Departments + +Domain squatting detection is configured in your detection rules file (not in the Settings UI). This follows the same pattern as other advanced security features like Rogue Apps Detection. + +#### How to Configure + +Edit your `rules/detection-rules.json` file to customize: + +**Enable/Disable Detection:** +```json +{ + "domain_squatting": { + "enabled": true, // Turn detection on/off + "action": "block" // Action when detected: "block" or "warn" + } +} +``` + +**Set Action Type:** +```json +{ + "domain_squatting": { + "action": "block" // "block" = full page block, "warn" = banner only + } +} +``` +Note: Page blocking also requires "Enable Page Blocking" to be turned ON in settings. + +**Adjust Sensitivity** (how strict the checking is): +```json +{ + "domain_squatting": { + "deviation_threshold": 2 + } +} +``` +- Lower numbers (1) = Very strict, catches fewer variations +- Higher numbers (3-5) = More lenient, catches more variations +- Default is 2 (recommended for most organizations) + +**Choose Detection Methods:** +```json +{ + "domain_squatting": { + "algorithms": { + "levenshtein": true, + "homoglyph": true, + "typosquat": true, + "combosquat": true + } + } +} +``` + +You can turn individual detection methods on/off. We recommend keeping all four enabled for maximum protection. + +## For MSPs and Enterprise IT + +### Enterprise Policy Management + +Domain squatting detection can be managed through Group Policy (GPO) or Microsoft Intune, just like other Check settings. + +**What You Can Control via Policy:** +- Detection sensitivity (character difference threshold) +- Which detection methods are active +- Additional protected domains specific to your organization + +**What's in the Rules File:** +- Enable/disable domain squatting detection +- Default protected domains list +- Detection rules and patterns + +This separation gives you flexibility - you control the core security settings through your detection rules file, while still allowing policy-based customization for different clients or departments. + +### Adding Organization-Specific Domains + +{% hint style="info" %} +**Use the URL Allowlist!** + +The easiest way to protect your organization's domains is to add them to the URL Allowlist in Detection Rules settings. This automatically: +1. Prevents false positives on your internal sites +2. Protects those domains from squatting attempts +3. Works without modifying detection rules files +{% endhint %} + +**Example:** Adding `https://contoso.com/*` to your allowlist protects against fake domains like: +- `cont0so.com` (zero instead of o) +- `contos0.com` (zero at the end) +- `login-contoso.com` (suspicious prefix) + +### CIPP Reporting and Webhooks + +Domain squatting detections are automatically reported through your existing Check monitoring: + +- **Activity Logs**: View all domain squatting warnings and blocks +- **CIPP Integration**: Squatting detections appear in your CIPP logbook +- **Webhooks**: Configure webhooks to receive `domain_squatting_detected` events + +See [General Settings](../settings/general.md) for configuring reporting and webhooks. + +## Troubleshooting + +### "Check blocked a legitimate site" + +If Check blocks a site you trust: + +1. **Add it to your URL Allowlist** in Detection Rules settings +2. The site will be both allowed and protected from squatting +3. Report the false positive to help improve Check + +### "A phishing site wasn't detected" + +Domain squatting detection works alongside Check's other phishing protections. If a site gets through: + +1. Use "Report False Negative" if you encounter a phishing site +2. Check will update rules to catch it in the future +3. Your report helps protect the entire community + +### "Settings are grayed out" + +If you can't see or change domain squatting settings, your IT department has configured these centrally. This is normal for managed deployments - contact your IT team if you need adjustments. + +## Related Documentation + +- [Detection Rules](../settings/detection-rules.md) - Configure your URL allowlist +- [General Settings](../settings/general.md) - Set up reporting and webhooks +- [Enterprise Deployment](../deployment/) - Deploy Check across your organization +- [Creating Detection Rules](../advanced/creating-detection-rules.md) - Advanced rule customization diff --git a/docs/settings/detection-rules.md b/docs/settings/detection-rules.md index 4f3c466..1babb7b 100644 --- a/docs/settings/detection-rules.md +++ b/docs/settings/detection-rules.md @@ -41,6 +41,14 @@ MSPs and IT departments commonly need to exclude phishing training platforms (li Add URLs or patterns that should be excluded from phishing detection. This is useful for internal company sites or trusted third-party services that might trigger false positives. +**Dual Protection:** Your allowlist serves two purposes: +1. **Prevents false positives** - Sites you add won't be flagged as phishing +2. **Domain squatting protection** - Domains extracted from your allowlist are automatically protected against typosquatting and look-alike attacks + +For example, adding `https://yourcompany.com/*` will both allow that site AND protect against fake domains like `yourcompany.net`, `your-company.com`, or `y0urcompany.com`. + +Learn more about [Domain Squatting Detection](../features/domain-squatting-detection.md). + **How it works:** Your allowlist patterns are **added to** (not replacing) the default CyberDrain exclusions, providing additional protection without losing baseline coverage. You can use: diff --git a/logs forced re-scan.txt b/logs forced re-scan.txt new file mode 100644 index 0000000..7bf2813 --- /dev/null +++ b/logs forced re-scan.txt @@ -0,0 +1,161 @@ +[M365-Protection] 📊 POPUP REQUEST: Using fallback detection - unknown +content.js:88 [M365-Protection] 🔄 POPUP REQUEST: Re-triggering analysis (forced) +content.js:88 [M365-Protection] 🔄 FORCED RE-SCAN: User manually triggered re-scan from popup +content.js:88 [M365-Protection] 🚀 Starting protection analysis (re-run) for https://sass.fraigutra.ai.in/a6dp3btdlbucn?4cb80078830f9112a-b9830f36eb056ff3b8ac1fcddfe67/ +content.js:88 [M365-Protection] 📄 Page info: 724 elements, 553666 chars content +content.js:88 [M365-Protection] Page blocking disabled in settings - running analysis only (no protective action) +content.js:88 [M365-Protection] 🔄 Forced re-scan initiated (scan count: 3) +content.js:88 [M365-Protection] Starting rule-driven Microsoft 365 protection (scan #3), protection DISABLED +content.js:88 [M365-Protection] Console capture enabled (developer mode active) +content.js:88 [M365-Protection] Checking origin: "https://sass.fraigutra.ai.in" +content.js:88 [M365-Protection] Trusted login patterns: Array(7) +content.js:88 [M365-Protection] Microsoft domain patterns: Array(24) +content.js:88 [M365-Protection] Is trusted login domain: false +content.js:88 [M365-Protection] Is Microsoft domain: false +content.js:88 [M365-Protection] ❌ NON-TRUSTED ORIGIN - Continuing analysis +content.js:88 [M365-Protection] Origin "https://sass.fraigutra.ai.in" not in trusted login patterns +content.js:88 [M365-Protection] Expected to match pattern like: "^https://login\.microsoftonline\.com$" +content.js:88 [M365-Protection] Trusted login patterns loaded: YES +content.js:88 [M365-Protection] Analyzing domain "sass.fraigutra.ai.in" - proceeding with content-based detection +content.js:88 [M365-Protection] ✗ Missing primary element: idPartnerPL +content.js:88 [M365-Protection] ✗ Missing primary element: loginfmt +content.js:88 [M365-Protection] ✗ Missing primary element: aadcdn_msauth +content.js:88 [M365-Protection] ✗ Missing primary element: urlMsaSignUp +content.js:88 [M365-Protection] ✗ Missing primary element: i0116_element +content.js:88 [M365-Protection] ✗ Missing primary element: aadcdn_background_image +content.js:88 [M365-Protection] ✗ Missing secondary element: page_title_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: meta_description_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: meta_og_title_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: favicon_microsoft +content.js:88 [M365-Protection] ✓ Found secondary element: ms_form_dimensions (weight: 0.5) +content.js:88 [M365-Protection] ✓ Found secondary element: ms_button_colors (weight: 1.5) +content.js:88 [M365-Protection] ✗ Missing secondary element: segoe_ui_font +content.js:88 [M365-Protection] ✓ Found secondary element: ms_container_layout (weight: 0.5) +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_external_css +content.js:88 [M365-Protection] ✓ Found secondary element: password_input_field (weight: 0.5) +content.js:88 [M365-Protection] ✓ Found secondary element: login_form_element (weight: 0.5) +content.js:88 [M365-Protection] ✓ Found secondary element: ms_login_placeholder_text (weight: 1) +content.js:88 [M365-Protection] 🔍 High-confidence Microsoft elements detected (Weight: 4.5, Elements: 6) - will check phishing indicators +content.js:88 [M365-Protection] ⚠️ Microsoft elements detected but not full login page - checking for phishing indicators +content.js:88 [M365-Protection] 🔍 processPhishingIndicators: detectionRules available: true +content.js:88 [M365-Protection] 🔍 Testing 27 phishing indicators against: +content.js:88 [M365-Protection] - Page source length: 718647 chars +content.js:88 [M365-Protection] - Page text length: 553666 chars +content.js:88 [M365-Protection] - Current URL: https://sass.fraigutra.ai.in/a6dp3btdlbucn?4cb80078830f9112a-b9830f36eb056ff3b8ac1fcddfe67/ +content.js:88 [M365-Protection] - Injected elements excluded: 0 +content.js:88 [M365-Protection] 📋 All 27 indicators loaded: +content.js:88 [M365-Protection] 1. phi_001: (?:secure-?(?:microsoft|office|365|outlook)) (high) +content.js:88 [M365-Protection] 2. phi_031_suspicious_query_length_combined: [code-driven: unknown] (medium) +content.js:88 [M365-Protection] 3. phi_033_suspicious_event_listeners: [code-driven: pattern_count] (high) +content.js:88 [M365-Protection] 4. phi_004: [code-driven: all_of] (medium) +content.js:88 [M365-Protection] 5. phi_005: data:text/html.*(?:microsoft|office|365|outlook).*... (critical) +content.js:88 [M365-Protection] 6. phi_007: \*customcss.*(?!aadcdn\.msftauthimages\.net) (high) +content.js:88 [M365-Protection] 7. phi_012_suspicious_resources: [code-driven: resource_from_domain] (high) +content.js:88 [M365-Protection] 8. phi_006: [code-driven: all_of] (high) +content.js:88 [M365-Protection] 9. phi_010_aad_fingerprint: [code-driven: all_of] (critical) +content.js:88 [M365-Protection] 10. phi_011_missing_elements: (?:microsoft|office|365).{0,2000}(?:type=["']passw... (high) +content.js:88 [M365-Protection] 11. phi_013_form_action_mismatch: [code-driven: all_of] (critical) +content.js:88 [M365-Protection] 12. phi_014_devtools_blocking: [code-driven: all_of] (high) +content.js:88 [M365-Protection] 13. phi_015_code_obfuscation: [code-driven: all_of] (high) +content.js:88 [M365-Protection] 14. phi_008: content-security-policy-report-only.*(?!.*msauth\.... (critical) +content.js:88 [M365-Protection] 15. phi_019_malicious_obfuscation: (?:(?:var|let|const)\s+\w+\s*=\s*(?:atob|unescape)... (critical) +content.js:88 [M365-Protection] 16. phi_001_enhanced: [code-driven: has_but_not] (critical) +content.js:88 [M365-Protection] 17. phi_002: [code-driven: all_of] (high) +content.js:88 [M365-Protection] 18. phi_003: [code-driven: all_of] (high) +content.js:88 [M365-Protection] 19. phi_020_grammar_typos: [code-driven: substring_count] (medium) +content.js:88 [M365-Protection] 20. phi_021_suspicious_url_structure: (?<=://[^/]+)(?:/[a-zA-Z0-9]{20,}(?:/[a-zA-Z0-9]{8... (medium) +content.js:88 [M365-Protection] 21. phi_022_obfuscated_script_names: (?:src=["'][^"']*[a-zA-Z0-9]{12,20}\.js["']|[a-zA-... (medium) +content.js:88 [M365-Protection] 22. phi_017_microsoft_brand_abuse: [code-driven: all_of] (high) +content.js:88 [M365-Protection] 23. phi_023_css_selection_blocking: [code-driven: substring_present] (low) +content.js:88 [M365-Protection] 24. phi_024_randomized_css_classes: class\s*=\s*["'][a-z]+_[a-z]+_\d{3}["'] (medium) +content.js:88 [M365-Protection] 25. phi_025_honeypot_fields: (?:position\s*:\s*absolute\s*!important\s*;[^}]*le... (low) +content.js:88 [M365-Protection] 26. phi_029_fake_dead_links: [code-driven: pattern_count] (medium) +content.js:88 [M365-Protection] 27. phi_030_empty_tag_obfuscation: [code-driven: pattern_count] (medium) +content.js:88 [M365-Protection] ⏱️ PERF: Attempting background processing with Web Worker +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 0/27 - phi_001 +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 3/27 - phi_004 +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 6/27 - phi_012_suspicious_resources +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 9/27 - phi_011_missing_elements +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 12/27 - phi_015_code_obfuscation +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 15/27 - phi_001_enhanced +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 18/27 - phi_020_grammar_typos +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 21/27 - phi_017_microsoft_brand_abuse +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 24/27 - phi_025_honeypot_fields +content.js:88 [M365-Protection] ⏱️ PERF: Background processing completed successfully in 568ms +content.js:88 [M365-Protection] ⏱️ Phishing indicators check (Web Worker): 18 threats found, score: 233, processing time: 568ms +content.js:88 [M365-Protection] 🚨 PHISHING INDICATORS FOUND on non-Microsoft page: 18 threats +(anonymous) @ content.js:88 +content.js:88 [M365-Protection] 📋 Detailed threat breakdown: +content.js:88 [M365-Protection] 1. [MEDIUM] phi_031_suspicious_query_length_combined (confidence: 0.7) +content.js:88 [M365-Protection] Suspiciously long query parameter value in URL, Microsoft branding, and password field or form present (possible phishing) +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 2. [HIGH] phi_033_suspicious_event_listeners (confidence: 0.9) +content.js:88 [M365-Protection] Form with submit listeners that modify action attribute +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 3. [MEDIUM] phi_004 (confidence: 0.65) +content.js:88 [M365-Protection] Urgency tactics targeting Microsoft users +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 4. [HIGH] phi_012_suspicious_resources (confidence: 0.9) +content.js:88 [M365-Protection] Custom CSS loaded from unauthorized domain +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 5. [HIGH] phi_006 (confidence: 0.8) +content.js:88 [M365-Protection] Microsoft-branded login form not posting to Microsoft domain +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 6. [CRITICAL] phi_010_aad_fingerprint (confidence: 0.98) +content.js:88 [M365-Protection] AAD-like login interface on non-Microsoft domain +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 7. [HIGH] phi_011_missing_elements (confidence: 0.85) +content.js:88 [M365-Protection] Microsoft branding without required authentication elements +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 8. [CRITICAL] phi_013_form_action_mismatch (confidence: 0.95) +content.js:88 [M365-Protection] Microsoft-branded password form with non-Microsoft action +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 9. [HIGH] phi_014_devtools_blocking (confidence: 0.9) +content.js:88 [M365-Protection] Page attempts to block or detect developer tools usage +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 10. [HIGH] phi_015_code_obfuscation (confidence: 0.85) +content.js:88 [M365-Protection] Page contains suspicious JavaScript obfuscation patterns commonly used in malware +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 11. [CRITICAL] phi_001_enhanced (confidence: 0.95) +content.js:88 [M365-Protection] Enhanced detection of domains mimicking Microsoft services with security/login keywords (excludes legitimate SSO) +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 12. [HIGH] phi_002 (confidence: 0.85) +content.js:88 [M365-Protection] Impersonation of Microsoft security team (excludes legitimate SSO and third-party auth) +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 13. [HIGH] phi_003 (confidence: 0.85) +content.js:88 [M365-Protection] Common Microsoft 365 phishing keywords and variations +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 14. [MEDIUM] phi_020_grammar_typos (confidence: 0.7) +content.js:88 [M365-Protection] Multiple grammar/spelling errors indicative of phishing +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 15. [HIGH] phi_017_microsoft_brand_abuse (confidence: 0.95) +content.js:88 [M365-Protection] Microsoft branding combined with authentication terms on non-Microsoft domain +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 16. [LOW] phi_023_css_selection_blocking (confidence: 0.85) +content.js:88 [M365-Protection] CSS prevents text selection - anti-analysis technique (supporting evidence - should not block alone) +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 17. [MEDIUM] phi_029_fake_dead_links (confidence: 0.95) +content.js:88 [M365-Protection] Obfuscated links with empty tags - phishing technique (supporting evidence - should not block alone) +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 18. [MEDIUM] phi_030_empty_tag_obfuscation (confidence: 0.9) +content.js:88 [M365-Protection] Multiple empty tags used to obfuscate text (supporting evidence - should not block alone) +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] ⚠️ PROTECTION DISABLED: Would block critical threats but showing warning banner instead +(anonymous) @ content.js:88 +content.js:88 [M365-Protection] Registered injected element: DIV#ms365-warning-banner +content.js:88 [M365-Protection] Registered injected element: DIV#no-id +content.js:88 [M365-Protection] Registered injected element: DIV#check-banner-left +content.js:88 [M365-Protection] Registered injected element: DIV#no-id +content.js:88 [M365-Protection] Registered injected element: SPAN#no-id +content.js:88 [M365-Protection] Registered injected element: STRONG#no-id +content.js:88 [M365-Protection] Registered injected element: SMALL#no-id +content.js:88 [M365-Protection] Registered injected element: BUTTON#no-id +content.js:88 [M365-Protection] Warning banner displayed and all elements registered for exclusion +content.js:88 [M365-Protection] Registered injected element: DIV#check-banner-branding +content.js:88 [M365-Protection] Registered injected element: IMG#no-id +content.js:88 [M365-Protection] Registered injected element: DIV#no-id +content.js:88 [M365-Protection] Registered injected element: SPAN#no-id +content.js:88 [M365-Protection] CIPP reporting disabled or no server URL configured +content.js:88 [M365-Protection] 📊 POPUP REQUEST: Returning stored detection results - blocked +content.js:88 [M365-Protection] DOM monitoring stopped +content.js:88 [M365-Protection] 🛑 DOM monitoring timeout reached - stopping \ No newline at end of file diff --git a/losg natural scan.txt b/losg natural scan.txt new file mode 100644 index 0000000..eb6abfa --- /dev/null +++ b/losg natural scan.txt @@ -0,0 +1,292 @@ +content.js:88 [M365-Protection] Console capture enabled (developer mode active) +content.js:88 [M365-Protection] Loaded detection rules from background script cache +content.js:88 [M365-Protection] Set up 7 trusted login patterns from cache +content.js:88 [M365-Protection] Set up 24 Microsoft domain patterns from cache +content.js:88 [M365-Protection] Checking origin: "https://sass.fraigutra.ai.in" +content.js:88 [M365-Protection] Trusted login patterns: Array(7) +content.js:88 [M365-Protection] Microsoft domain patterns: Array(24) +content.js:88 [M365-Protection] Is trusted login domain: false +content.js:88 [M365-Protection] Is Microsoft domain: false +content.js:88 [M365-Protection] ❌ NON-TRUSTED ORIGIN - Continuing analysis +content.js:88 [M365-Protection] Origin "https://sass.fraigutra.ai.in" not in trusted login patterns +content.js:88 [M365-Protection] Expected to match pattern like: "^https://login\.microsoftonline\.com$" +content.js:88 [M365-Protection] Trusted login patterns loaded: YES +content.js:88 [M365-Protection] Analyzing domain "sass.fraigutra.ai.in" - proceeding with content-based detection +content.js:88 [M365-Protection] ✗ Missing primary element: idPartnerPL +content.js:88 [M365-Protection] ✗ Missing primary element: loginfmt +content.js:88 [M365-Protection] ✗ Missing primary element: aadcdn_msauth +content.js:88 [M365-Protection] ✗ Missing primary element: urlMsaSignUp +content.js:88 [M365-Protection] ✗ Missing primary element: i0116_element +content.js:88 [M365-Protection] ✗ Missing primary element: aadcdn_background_image +content.js:88 [M365-Protection] ✗ Missing secondary element: page_title_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: meta_description_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: meta_og_title_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: favicon_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_form_dimensions +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_button_colors +content.js:88 [M365-Protection] ✗ Missing secondary element: segoe_ui_font +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_container_layout +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_external_css +content.js:88 [M365-Protection] ✗ Missing secondary element: password_input_field +content.js:88 [M365-Protection] ✗ Missing secondary element: login_form_element +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_login_placeholder_text +content.js:88 [M365-Protection] 📄 Insufficient Microsoft indicators (Weight: 0, Elements: 0, Primary: 0) - skipping phishing indicators for performance +content.js:88 [M365-Protection] ✅ Page analysis result: Site appears legitimate (not Microsoft-related, no phishing indicators checked) +content.js:88 [M365-Protection] � Setting up DOM monitoring - phishing pages may inject Microsoft content dynamically +content.js:88 [M365-Protection] Setting up DOM monitoring for delayed content +content.js:88 [M365-Protection] Current page has 12 elements +content.js:88 [M365-Protection] Page title: "​" +content.js:88 [M365-Protection] Body content length: 1187 chars +content.js:88 [M365-Protection] 🔍 Dynamic script monitoring enabled +VM13:66 A parser-blocking, cross site (i.e. different eTLD+1) script, https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js, is invoked via document.write. The network request for this script MAY be blocked by the browser in this or a future page load due to poor network connectivity. If blocked in this page load, it will be confirmed in a subsequent console message. See https://www.chromestatus.com/feature/5718547946799104 for more details. +eval @ VM13:66 +VM13:66 A parser-blocking, cross site (i.e. different eTLD+1) script, https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js, is invoked via document.write. The network request for this script MAY be blocked by the browser in this or a future page load due to poor network connectivity. If blocked in this page load, it will be confirmed in a subsequent console message. See https://www.chromestatus.com/feature/5718547946799104 for more details. +eval @ VM13:66 +VM9:1 A parser-blocking, cross site (i.e. different eTLD+1) script, https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js, is invoked via document.write. The network request for this script MAY be blocked by the browser in this or a future page load due to poor network connectivity. If blocked in this page load, it will be confirmed in a subsequent console message. See https://www.chromestatus.com/feature/5718547946799104 for more details. +(anonymous) @ VM9:1 +content.js:88 [M365-Protection] DOM mutation: Adding script element +content.js:88 [M365-Protection] DOM change detected: script element added - triggering re-scan +content.js:88 [M365-Protection] 🔄 Significant DOM changes detected - scheduling protection analysis (debounced) +content.js:88 [M365-Protection] Page now has 27 elements +a6dp3btdlbucn?4cb80078830f9112a-b9830f36eb056ff3b8ac1fcddfe67/:1 Tracking Prevention blocked access to storage for https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js. +a6dp3btdlbucn?4cb80078830f9112a-b9830f36eb056ff3b8ac1fcddfe67/:1 Tracking Prevention blocked access to storage for https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js. +a6dp3btdlbucn?4cb80078830f9112a-b9830f36eb056ff3b8ac1fcddfe67/:1 Tracking Prevention blocked access to storage for https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js. +a6dp3btdlbucn?4cb80078830f9112a-b9830f36eb056ff3b8ac1fcddfe67/:1 Tracking Prevention blocked access to storage for https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js. +content.js:88 [M365-Protection] 🚀 Starting protection analysis (re-run) for https://sass.fraigutra.ai.in/a6dp3btdlbucn?4cb80078830f9112a-b9830f36eb056ff3b8ac1fcddfe67/ +content.js:88 [M365-Protection] 📄 Page info: 27 elements, 461315 chars content +content.js:88 [M365-Protection] Page blocking disabled in settings - running analysis only (no protective action) +content.js:88 [M365-Protection] Page source changed: 2277:2867677973 -> 467427:2361889222 +content.js:88 [M365-Protection] Starting rule-driven Microsoft 365 protection (scan #2), protection DISABLED +content.js:88 [M365-Protection] Console capture enabled (developer mode active) +content.js:88 [M365-Protection] Checking origin: "https://sass.fraigutra.ai.in" +content.js:88 [M365-Protection] Trusted login patterns: Array(7) +content.js:88 [M365-Protection] Microsoft domain patterns: Array(24) +content.js:88 [M365-Protection] Is trusted login domain: false +content.js:88 [M365-Protection] Is Microsoft domain: false +content.js:88 [M365-Protection] ❌ NON-TRUSTED ORIGIN - Continuing analysis +content.js:88 [M365-Protection] Origin "https://sass.fraigutra.ai.in" not in trusted login patterns +content.js:88 [M365-Protection] Expected to match pattern like: "^https://login\.microsoftonline\.com$" +content.js:88 [M365-Protection] Trusted login patterns loaded: YES +content.js:88 [M365-Protection] Analyzing domain "sass.fraigutra.ai.in" - proceeding with content-based detection +content.js:88 [M365-Protection] ✗ Missing primary element: idPartnerPL +content.js:88 [M365-Protection] ✗ Missing primary element: loginfmt +content.js:88 [M365-Protection] ✗ Missing primary element: aadcdn_msauth +content.js:88 [M365-Protection] ✗ Missing primary element: urlMsaSignUp +content.js:88 [M365-Protection] ✗ Missing primary element: i0116_element +content.js:88 [M365-Protection] ✗ Missing primary element: aadcdn_background_image +content.js:88 [M365-Protection] ✗ Missing secondary element: page_title_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: meta_description_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: meta_og_title_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: favicon_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_form_dimensions +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_button_colors +content.js:88 [M365-Protection] ✗ Missing secondary element: segoe_ui_font +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_container_layout +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_external_css +content.js:88 [M365-Protection] ✗ Missing secondary element: password_input_field +content.js:88 [M365-Protection] ✗ Missing secondary element: login_form_element +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_login_placeholder_text +content.js:88 [M365-Protection] 📄 Insufficient Microsoft indicators (Weight: 0, Elements: 0, Primary: 0) - skipping phishing indicators for performance +content.js:88 [M365-Protection] ✅ Page analysis result: Site appears legitimate (not Microsoft-related, no phishing indicators checked) +content.js:88 [M365-Protection] � Setting up DOM monitoring - phishing pages may inject Microsoft content dynamically +content.js:88 [M365-Protection] ⏰ Fallback timer detected significant content - re-running analysis (check 2/5) +content.js:88 [M365-Protection] 🚀 Starting protection analysis (re-run) for https://sass.fraigutra.ai.in/a6dp3btdlbucn?4cb80078830f9112a-b9830f36eb056ff3b8ac1fcddfe67/ +content.js:88 [M365-Protection] 📄 Page info: 557 elements, 532580 chars content +content.js:88 [M365-Protection] Page blocking disabled in settings - running analysis only (no protective action) +content.js:88 [M365-Protection] Page source changed: 467427:2361889222 -> 601514:2584326084 +content.js:88 [M365-Protection] Starting rule-driven Microsoft 365 protection (scan #3), protection DISABLED +content.js:88 [M365-Protection] Console capture enabled (developer mode active) +content.js:88 [M365-Protection] Checking origin: "https://sass.fraigutra.ai.in" +content.js:88 [M365-Protection] Trusted login patterns: Array(7) +content.js:88 [M365-Protection] Microsoft domain patterns: Array(24) +content.js:88 [M365-Protection] Is trusted login domain: false +content.js:88 [M365-Protection] Is Microsoft domain: false +content.js:88 [M365-Protection] ❌ NON-TRUSTED ORIGIN - Continuing analysis +content.js:88 [M365-Protection] Origin "https://sass.fraigutra.ai.in" not in trusted login patterns +content.js:88 [M365-Protection] Expected to match pattern like: "^https://login\.microsoftonline\.com$" +content.js:88 [M365-Protection] Trusted login patterns loaded: YES +content.js:88 [M365-Protection] Analyzing domain "sass.fraigutra.ai.in" - proceeding with content-based detection +content.js:88 [M365-Protection] ✗ Missing primary element: idPartnerPL +content.js:88 [M365-Protection] ✗ Missing primary element: loginfmt +content.js:88 [M365-Protection] ✗ Missing primary element: aadcdn_msauth +content.js:88 [M365-Protection] ✗ Missing primary element: urlMsaSignUp +content.js:88 [M365-Protection] ✗ Missing primary element: i0116_element +content.js:88 [M365-Protection] ✗ Missing primary element: aadcdn_background_image +content.js:88 [M365-Protection] ✗ Missing secondary element: page_title_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: meta_description_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: meta_og_title_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: favicon_microsoft +content.js:88 [M365-Protection] ✓ Found secondary element: ms_form_dimensions (weight: 0.5) +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_button_colors +content.js:88 [M365-Protection] ✗ Missing secondary element: segoe_ui_font +content.js:88 [M365-Protection] ✓ Found secondary element: ms_container_layout (weight: 0.5) +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_external_css +content.js:88 [M365-Protection] ✗ Missing secondary element: password_input_field +content.js:88 [M365-Protection] ✗ Missing secondary element: login_form_element +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_login_placeholder_text +content.js:88 [M365-Protection] 📄 Insufficient Microsoft indicators (Weight: 1, Elements: 2, Primary: 0) - skipping phishing indicators for performance +content.js:88 [M365-Protection] ✅ Page analysis result: Site appears legitimate (not Microsoft-related, no phishing indicators checked) +content.js:88 [M365-Protection] � Setting up DOM monitoring - phishing pages may inject Microsoft content dynamically +/id9aLSPMH2qQzoEiID9D4FsZHOFwugwsnfktiqAw01U9fFEtALghuEpe:1 Failed to load resource: the server responded with a status of 404 () +content.js:88 [M365-Protection] ⏰ Fallback timer detected significant content - re-running analysis (check 3/5) +content.js:88 [M365-Protection] 🚀 Starting protection analysis (re-run) for https://sass.fraigutra.ai.in/a6dp3btdlbucn?4cb80078830f9112a-b9830f36eb056ff3b8ac1fcddfe67/ +content.js:88 [M365-Protection] 📄 Page info: 722 elements, 553567 chars content +content.js:88 [M365-Protection] Page blocking disabled in settings - running analysis only (no protective action) +content.js:88 [M365-Protection] Page source changed: 601514:2584326084 -> 718294:2325107531 +content.js:88 [M365-Protection] Starting rule-driven Microsoft 365 protection (scan #4), protection DISABLED +content.js:88 [M365-Protection] Console capture enabled (developer mode active) +content.js:88 [M365-Protection] Checking origin: "https://sass.fraigutra.ai.in" +content.js:88 [M365-Protection] Trusted login patterns: Array(7) +content.js:88 [M365-Protection] Microsoft domain patterns: Array(24) +content.js:88 [M365-Protection] Is trusted login domain: false +content.js:88 [M365-Protection] Is Microsoft domain: false +content.js:88 [M365-Protection] ❌ NON-TRUSTED ORIGIN - Continuing analysis +content.js:88 [M365-Protection] Origin "https://sass.fraigutra.ai.in" not in trusted login patterns +content.js:88 [M365-Protection] Expected to match pattern like: "^https://login\.microsoftonline\.com$" +content.js:88 [M365-Protection] Trusted login patterns loaded: YES +content.js:88 [M365-Protection] Analyzing domain "sass.fraigutra.ai.in" - proceeding with content-based detection +content.js:88 [M365-Protection] ✗ Missing primary element: idPartnerPL +content.js:88 [M365-Protection] ✗ Missing primary element: loginfmt +content.js:88 [M365-Protection] ✗ Missing primary element: aadcdn_msauth +content.js:88 [M365-Protection] ✗ Missing primary element: urlMsaSignUp +content.js:88 [M365-Protection] ✗ Missing primary element: i0116_element +content.js:88 [M365-Protection] ✗ Missing primary element: aadcdn_background_image +content.js:88 [M365-Protection] ✗ Missing secondary element: page_title_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: meta_description_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: meta_og_title_microsoft +content.js:88 [M365-Protection] ✗ Missing secondary element: favicon_microsoft +content.js:88 [M365-Protection] ✓ Found secondary element: ms_form_dimensions (weight: 0.5) +content.js:88 [M365-Protection] ✓ Found secondary element: ms_button_colors (weight: 1.5) +content.js:88 [M365-Protection] ✗ Missing secondary element: segoe_ui_font +content.js:88 [M365-Protection] ✓ Found secondary element: ms_container_layout (weight: 0.5) +content.js:88 [M365-Protection] ✗ Missing secondary element: ms_external_css +content.js:88 [M365-Protection] ✓ Found secondary element: password_input_field (weight: 0.5) +content.js:88 [M365-Protection] ✓ Found secondary element: login_form_element (weight: 0.5) +content.js:88 [M365-Protection] ✓ Found secondary element: ms_login_placeholder_text (weight: 1) +content.js:88 [M365-Protection] 🔍 High-confidence Microsoft elements detected (Weight: 4.5, Elements: 6) - will check phishing indicators +content.js:88 [M365-Protection] ⚠️ Microsoft elements detected but not full login page - checking for phishing indicators +content.js:88 [M365-Protection] 🔍 processPhishingIndicators: detectionRules available: true +content.js:88 [M365-Protection] 🔍 Testing 27 phishing indicators against: +content.js:88 [M365-Protection] - Page source length: 718294 chars +content.js:88 [M365-Protection] - Page text length: 553567 chars +content.js:88 [M365-Protection] - Current URL: https://sass.fraigutra.ai.in/a6dp3btdlbucn?4cb80078830f9112a-b9830f36eb056ff3b8ac1fcddfe67/ +content.js:88 [M365-Protection] - Injected elements excluded: 0 +content.js:88 [M365-Protection] 📋 All 27 indicators loaded: +content.js:88 [M365-Protection] 1. phi_001: (?:secure-?(?:microsoft|office|365|outlook)) (high) +content.js:88 [M365-Protection] 2. phi_031_suspicious_query_length_combined: [code-driven: unknown] (medium) +content.js:88 [M365-Protection] 3. phi_033_suspicious_event_listeners: [code-driven: pattern_count] (high) +content.js:88 [M365-Protection] 4. phi_004: [code-driven: all_of] (medium) +content.js:88 [M365-Protection] 5. phi_005: data:text/html.*(?:microsoft|office|365|outlook).*... (critical) +content.js:88 [M365-Protection] 6. phi_007: \*customcss.*(?!aadcdn\.msftauthimages\.net) (high) +content.js:88 [M365-Protection] 7. phi_012_suspicious_resources: [code-driven: resource_from_domain] (high) +content.js:88 [M365-Protection] 8. phi_006: [code-driven: all_of] (high) +content.js:88 [M365-Protection] 9. phi_010_aad_fingerprint: [code-driven: all_of] (critical) +content.js:88 [M365-Protection] 10. phi_011_missing_elements: (?:microsoft|office|365).{0,2000}(?:type=["']passw... (high) +content.js:88 [M365-Protection] 11. phi_013_form_action_mismatch: [code-driven: all_of] (critical) +content.js:88 [M365-Protection] 12. phi_014_devtools_blocking: [code-driven: all_of] (high) +content.js:88 [M365-Protection] 13. phi_015_code_obfuscation: [code-driven: all_of] (high) +content.js:88 [M365-Protection] 14. phi_008: content-security-policy-report-only.*(?!.*msauth\.... (critical) +content.js:88 [M365-Protection] 15. phi_019_malicious_obfuscation: (?:(?:var|let|const)\s+\w+\s*=\s*(?:atob|unescape)... (critical) +content.js:88 [M365-Protection] 16. phi_001_enhanced: [code-driven: has_but_not] (critical) +content.js:88 [M365-Protection] 17. phi_002: [code-driven: all_of] (high) +content.js:88 [M365-Protection] 18. phi_003: [code-driven: all_of] (high) +content.js:88 [M365-Protection] 19. phi_020_grammar_typos: [code-driven: substring_count] (medium) +content.js:88 [M365-Protection] 20. phi_021_suspicious_url_structure: (?<=://[^/]+)(?:/[a-zA-Z0-9]{20,}(?:/[a-zA-Z0-9]{8... (medium) +content.js:88 [M365-Protection] 21. phi_022_obfuscated_script_names: (?:src=["'][^"']*[a-zA-Z0-9]{12,20}\.js["']|[a-zA-... (medium) +content.js:88 [M365-Protection] 22. phi_017_microsoft_brand_abuse: [code-driven: all_of] (high) +content.js:88 [M365-Protection] 23. phi_023_css_selection_blocking: [code-driven: substring_present] (low) +content.js:88 [M365-Protection] 24. phi_024_randomized_css_classes: class\s*=\s*["'][a-z]+_[a-z]+_\d{3}["'] (medium) +content.js:88 [M365-Protection] 25. phi_025_honeypot_fields: (?:position\s*:\s*absolute\s*!important\s*;[^}]*le... (low) +content.js:88 [M365-Protection] 26. phi_029_fake_dead_links: [code-driven: pattern_count] (medium) +content.js:88 [M365-Protection] 27. phi_030_empty_tag_obfuscation: [code-driven: pattern_count] (medium) +content.js:88 [M365-Protection] ⏱️ PERF: Attempting background processing with Web Worker +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 0/27 - phi_001 +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 3/27 - phi_004 +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 6/27 - phi_012_suspicious_resources +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 9/27 - phi_011_missing_elements +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 12/27 - phi_015_code_obfuscation +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 15/27 - phi_001_enhanced +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 18/27 - phi_020_grammar_typos +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 21/27 - phi_017_microsoft_brand_abuse +content.js:88 [M365-Protection] ⏱️ PERF: Background processing 24/27 - phi_025_honeypot_fields +content.js:88 [M365-Protection] ⏱️ PERF: Background processing completed successfully in 735ms +content.js:88 [M365-Protection] ⏱️ Phishing indicators check (Web Worker): 18 threats found, score: 233, processing time: 735ms +content.js:88 [M365-Protection] 🚨 PHISHING INDICATORS FOUND on non-Microsoft page: 18 threats +(anonymous) @ content.js:88 +content.js:88 [M365-Protection] 📋 Detailed threat breakdown: +content.js:88 [M365-Protection] 1. [MEDIUM] phi_031_suspicious_query_length_combined (confidence: 0.7) +content.js:88 [M365-Protection] Suspiciously long query parameter value in URL, Microsoft branding, and password field or form present (possible phishing) +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 2. [HIGH] phi_033_suspicious_event_listeners (confidence: 0.9) +content.js:88 [M365-Protection] Form with submit listeners that modify action attribute +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 3. [MEDIUM] phi_004 (confidence: 0.65) +content.js:88 [M365-Protection] Urgency tactics targeting Microsoft users +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 4. [HIGH] phi_012_suspicious_resources (confidence: 0.9) +content.js:88 [M365-Protection] Custom CSS loaded from unauthorized domain +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 5. [HIGH] phi_006 (confidence: 0.8) +content.js:88 [M365-Protection] Microsoft-branded login form not posting to Microsoft domain +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 6. [CRITICAL] phi_010_aad_fingerprint (confidence: 0.98) +content.js:88 [M365-Protection] AAD-like login interface on non-Microsoft domain +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 7. [HIGH] phi_011_missing_elements (confidence: 0.85) +content.js:88 [M365-Protection] Microsoft branding without required authentication elements +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 8. [CRITICAL] phi_013_form_action_mismatch (confidence: 0.95) +content.js:88 [M365-Protection] Microsoft-branded password form with non-Microsoft action +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 9. [HIGH] phi_014_devtools_blocking (confidence: 0.9) +content.js:88 [M365-Protection] Page attempts to block or detect developer tools usage +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 10. [HIGH] phi_015_code_obfuscation (confidence: 0.85) +content.js:88 [M365-Protection] Page contains suspicious JavaScript obfuscation patterns commonly used in malware +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 11. [CRITICAL] phi_001_enhanced (confidence: 0.95) +content.js:88 [M365-Protection] Enhanced detection of domains mimicking Microsoft services with security/login keywords (excludes legitimate SSO) +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 12. [HIGH] phi_002 (confidence: 0.85) +content.js:88 [M365-Protection] Impersonation of Microsoft security team (excludes legitimate SSO and third-party auth) +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 13. [HIGH] phi_003 (confidence: 0.85) +content.js:88 [M365-Protection] Common Microsoft 365 phishing keywords and variations +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 14. [MEDIUM] phi_020_grammar_typos (confidence: 0.7) +content.js:88 [M365-Protection] Multiple grammar/spelling errors indicative of phishing +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 15. [HIGH] phi_017_microsoft_brand_abuse (confidence: 0.95) +content.js:88 [M365-Protection] Microsoft branding combined with authentication terms on non-Microsoft domain +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 16. [LOW] phi_023_css_selection_blocking (confidence: 0.85) +content.js:88 [M365-Protection] CSS prevents text selection - anti-analysis technique (supporting evidence - should not block alone) +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 17. [MEDIUM] phi_029_fake_dead_links (confidence: 0.95) +content.js:88 [M365-Protection] Obfuscated links with empty tags - phishing technique (supporting evidence - should not block alone) +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] 18. [MEDIUM] phi_030_empty_tag_obfuscation (confidence: 0.9) +content.js:88 [M365-Protection] Multiple empty tags used to obfuscate text (supporting evidence - should not block alone) +content.js:88 [M365-Protection] Matched in: page source +content.js:88 [M365-Protection] ⚠️ PROTECTION DISABLED: Would block critical threats but showing warning banner instead +(anonymous) @ content.js:88 +content.js:88 [M365-Protection] Registered injected element: DIV#ms365-warning-banner +content.js:88 [M365-Protection] Registered injected element: DIV#no-id +content.js:88 [M365-Protection] Registered injected element: DIV#check-banner-left +content.js:88 [M365-Protection] Registered injected element: DIV#no-id +content.js:88 [M365-Protection] Registered injected element: SPAN#no-id +content.js:88 [M365-Protection] Registered injected element: STRONG#no-id +content.js:88 [M365-Protection] Registered injected element: SMALL#no-id +content.js:88 [M365-Protection] Registered injected element: BUTTON#no-id +content.js:88 [M365-Protection] Warning banner displayed and all elements registered for exclusion +content.js:88 [M365-Protection] Registered injected element: DIV#check-banner-branding +content.js:88 [M365-Protection] Registered injected element: IMG#no-id +content.js:88 [M365-Protection] Registered injected element: DIV#no-id +content.js:88 [M365-Protection] Registered injected element: SPAN#no-id +content.js:88 [M365-Protection] CIPP reporting disabled or no server URL configured +content.js:88 [M365-Protection] 🔍 Fallback timer scanning cleaned page source while banner is displayed +content.js:88 [M365-Protection] 🛑 runProtection() called but banner already displayed - ignoring re-scan +content.js:88 [M365-Protection] 📊 POPUP REQUEST: Returning stored detection results - blocked +content.js:88 [M365-Protection] 🔍 POPUP REQUEST: Getting detection details +content.js:88 [M365-Protection] DOM monitoring stopped +content.js:88 [M365-Protection] 🛑 DOM monitoring timeout reached - stopping \ No newline at end of file diff --git a/options/options.css b/options/options.css index 5e4ab92..a81a7aa 100644 --- a/options/options.css +++ b/options/options.css @@ -722,6 +722,96 @@ body { letter-spacing: 0.3px; } +/* Collapsible config sections */ +.config-section-collapsible { + margin-bottom: 12px; + border: 1px solid var(--border-color); + border-radius: var(--radius); + overflow: hidden; + background: var(--surface-color); +} + +.config-section-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + cursor: pointer; + user-select: none; + background: var(--surface-color); + transition: background-color 0.2s ease; +} + +.config-section-header:hover { + background: var(--background-color); +} + +.config-section-header-title { + font-weight: 600; + color: var(--primary-color); + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 8px; +} + +.config-section-toggle { + color: var(--text-muted); + font-size: 20px; + transition: transform 0.2s ease; + line-height: 1; + transform: rotate(180deg); +} + +.config-section-collapsible.expanded .config-section-toggle { + transform: rotate(90deg); +} + +.config-section-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.6s ease-out; +} + +.config-section-collapsible.expanded .config-section-content { + max-height: 2000px; /* Large enough for content */ + transition: max-height 0.8s ease-in; +} + +.config-section-body { + padding: 12px 16px; + border-top: 1px solid var(--border-color); +} + +/* Nested collapsible for lists */ +.config-list-toggle { + color: var(--primary-color); + cursor: pointer; + text-decoration: underline; + font-size: 12px; + margin-top: 8px; + display: inline-block; + transition: color 0.2s ease; +} + +.config-list-toggle:hover { + color: var(--primary-hover); +} + +.config-list-expanded { + margin-top: 0; + margin-bottom: 8px; + padding-left: 0; + display: none; + transition: all 0.5s ease; +} + +.config-list-expanded.visible { + display: block; +} + .config-raw-json { white-space: pre-wrap; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; diff --git a/options/options.html b/options/options.html index 7a69aea..93ea755 100644 --- a/options/options.html +++ b/options/options.html @@ -169,6 +169,11 @@

Generic Webhook

Rogue App Detected + +

Add URLs or regex patterns to allowlist from detection. Use simple URLs with * wildcards (e.g., https://google.com/*) or advanced regex patterns. These will be added to the exclusion rules without replacing the entire ruleset. This allowlist also protects the extracted domains from typosquatting, homoglyphs, and other domain squatting attacks.

@@ -260,10 +265,16 @@

Detection Configuration

Configuration Overview

- +
+ + +
Loading configuration...
@@ -566,35 +577,5 @@ - diff --git a/options/options.js b/options/options.js index a24dc1f..3ca12aa 100644 --- a/options/options.js +++ b/options/options.js @@ -66,6 +66,8 @@ class CheckOptions { this.elements.configDisplay = document.getElementById("configDisplay"); this.elements.toggleConfigView = document.getElementById("toggleConfigView"); + this.elements.expandCollapseAll = + document.getElementById("expandCollapseAll"); // Rule Playground elements this.elements.playgroundRulesInput = document.getElementById("playgroundRulesInput"); @@ -164,6 +166,11 @@ class CheckOptions { this.toggleConfigView() ); + // Expand/collapse all sections + this.elements.expandCollapseAll?.addEventListener("click", () => + this.toggleExpandCollapseAll() + ); + // Simulate enterprise mode toggle (dev only) this.elements.simulateEnterpriseMode?.addEventListener("change", () => this.toggleSimulateEnterpriseMode() @@ -173,6 +180,9 @@ class CheckOptions { this.elements.refreshDetectionRules?.addEventListener("click", () => this.refreshDetectionRules() ); + + // Domain squatting management + // (View default domains now in Configuration Overview) // Playground actions this.elements.runRuleTestBtn?.addEventListener("click", () => this.runRulePlaygroundTest()); @@ -956,6 +966,7 @@ class CheckOptions { "false_positive_report", "page_blocked", "rogue_app_detected", + "domain_squatting_detected", "threat_detected", "validation_event" ]; @@ -1210,6 +1221,7 @@ class CheckOptions { "false_positive_report", "page_blocked", "rogue_app_detected", + "domain_squatting_detected", "threat_detected", "validation_event" ].filter(eventType => @@ -1426,332 +1438,280 @@ class CheckOptions { if (!this.elements.configDisplay) return; const sections = []; + let sectionId = 0; + + // Helper function to create collapsible section + const createCollapsibleSection = (title, content, expanded = false) => { + const id = `config-section-${sectionId++}`; + return ` +
+
+
${title}
+ +
+
+
+ ${content} +
+
+
+ `; + }; - // Basic info - sections.push(` -
-
Basic Information
-
Version: ${ - config.version || "Unknown" - }
-
Last Updated: ${ - config.lastUpdated || "Unknown" - }
-
Description: ${ - config.description || "No description" - }
-
- `); + // Helper function to create expandable list + const createExpandableList = (items, label, showCount = 5) => { + if (!items || items.length === 0) return ''; + + const listId = `list-${sectionId++}`; + const displayed = items.slice(0, showCount); + const remaining = items.slice(showCount); + + let html = displayed.map(item => `
• ${item}
`).join(''); + + if (remaining.length > 0) { + html += ` +
+ ${remaining.map(item => `
• ${item}
`).join('')} +
+
+ ▸ Show ${remaining.length} more ${label} +
+ `; + } + + return html; + }; + + // Basic Information - Open by default + const basicInfoContent = ` +
Version: ${config.version || "Unknown"}
+
Last Updated: ${config.lastUpdated || "Unknown"}
+
Description: ${config.description || "No description"}
+ `; + sections.push(createCollapsibleSection('Basic Information', basicInfoContent, true)); // Detection Thresholds if (config.thresholds) { - sections.push(` -
-
Detection Thresholds
-
Legitimate Site Threshold: ${config.thresholds.legitimate}%
-
Suspicious Site Threshold: ${config.thresholds.suspicious}%
-
Phishing Site Threshold: ${config.thresholds.phishing}%
-
- `); + const thresholdsContent = ` +
Legitimate Site Threshold: ${config.thresholds.legitimate}%
+
Suspicious Site Threshold: ${config.thresholds.suspicious}%
+
Phishing Site Threshold: ${config.thresholds.phishing}%
+ `; + sections.push(createCollapsibleSection('Detection Thresholds', thresholdsContent, false)); } // Trusted Login Patterns - if ( - config.trusted_login_patterns && - config.trusted_login_patterns.length > 0 - ) { - sections.push(` -
-
Trusted Login Patterns (${ - config.trusted_login_patterns.length - })
- ${config.trusted_login_patterns - .slice(0, 5) - .map((pattern) => `
• ${pattern}
`) - .join("")} - ${ - config.trusted_login_patterns.length > 5 - ? `
... and ${ - config.trusted_login_patterns.length - 5 - } more
` - : "" - } -
- `); - } - - // Microsoft 365 Detection Requirements - if (config.m365_detection_requirements) { - const req = config.m365_detection_requirements; - const primaryCount = req.primary_elements - ? req.primary_elements.length - : 0; - const secondaryCount = req.secondary_elements - ? req.secondary_elements.length - : 0; - - sections.push(` -
-
Microsoft 365 Detection Requirements
-
Primary Elements: ${primaryCount}
-
Secondary Elements: ${secondaryCount}
-
Description: ${ - req.description || "No description" - }
-
- `); + if (config.trusted_login_patterns && config.trusted_login_patterns.length > 0) { + const patternsContent = createExpandableList(config.trusted_login_patterns, 'patterns', 5); + sections.push(createCollapsibleSection( + `Trusted Login Patterns (${config.trusted_login_patterns.length})`, + patternsContent, + false + )); } // Microsoft Domain Patterns - if ( - config.microsoft_domain_patterns && - config.microsoft_domain_patterns.length > 0 - ) { - sections.push(` -
-
Microsoft Domain Patterns (${ - config.microsoft_domain_patterns.length - })
- ${config.microsoft_domain_patterns - .slice(0, 10) - .map((pattern) => `
• ${pattern}
`) - .join("")} - ${ - config.microsoft_domain_patterns.length > 10 - ? `
... and ${ - config.microsoft_domain_patterns.length - 10 - } more
` - : "" - } -
- `); + if (config.microsoft_domain_patterns && config.microsoft_domain_patterns.length > 0) { + const domainsContent = createExpandableList(config.microsoft_domain_patterns, 'domains', 10); + sections.push(createCollapsibleSection( + `Microsoft Domain Patterns (${config.microsoft_domain_patterns.length})`, + domainsContent, + false + )); + } + + // Domain Squatting Detection + if (config.domain_squatting) { + const ds = config.domain_squatting; + let squattingContent = ` +
Enabled: ${ds.enabled ? 'Yes' : 'No'}
+
Deviation Threshold: ${ds.deviation_threshold || 2}
+
Action: ${ds.action || 'block'}
+
Severity: ${ds.severity || 'high'}
+ `; + + if (ds.algorithms) { + squattingContent += `
Algorithms:
`; + if (ds.algorithms.levenshtein !== false) squattingContent += `
✓ Levenshtein Distance
`; + if (ds.algorithms.homoglyph !== false) squattingContent += `
✓ Homoglyph Detection
`; + if (ds.algorithms.typosquat !== false) squattingContent += `
✓ Typosquatting
`; + if (ds.algorithms.combosquat !== false) squattingContent += `
✓ Combosquatting
`; + } + + if (ds.protected_domains && ds.protected_domains.length > 0) { + squattingContent += `
Protected Domains (${ds.protected_domains.length}):
`; + squattingContent += createExpandableList(ds.protected_domains, 'domains', 10); + } + + sections.push(createCollapsibleSection('Domain Squatting Detection', squattingContent, false)); } - // Exclusion System - if (config.exclusion_system) { - const exclusions = config.exclusion_system; - const domainPatterns = exclusions.domain_patterns || []; - const legitimateContexts = - exclusions.context_indicators?.legitimate_contexts || []; - const legitimateSsoPatterns = - exclusions.context_indicators?.legitimate_sso_patterns || []; - const suspiciousContexts = - exclusions.context_indicators?.suspicious_contexts || []; - - sections.push(` -
-
Exclusion System
-
Domain Patterns: ${ - domainPatterns.length - }
-
Legitimate Context Indicators: ${ - legitimateContexts.length - }
-
Legitimate SSO Patterns: ${ - legitimateSsoPatterns.length - }
-
Suspicious Context Indicators: ${ - suspiciousContexts.length - }
-
Description: ${ - exclusions.description || "No description" - }
- ${ - domainPatterns.length > 0 - ? `
-
Sample Domain Patterns:
- ${domainPatterns - .slice(0, 5) - .map((pattern) => `
• ${pattern}
`) - .join("")} - ${ - domainPatterns.length > 5 - ? `
... and ${ - domainPatterns.length - 5 - } more
` - : "" - } -
` - : "" - } -
- `); + // Rogue Apps Detection + if (config.rogue_apps_detection) { + const rogue = config.rogue_apps_detection; + const rogueContent = ` +
Enabled: ${rogue.enabled ? 'Yes' : 'No'}
+
Source URL: ${rogue.source_url || 'None'}
+
Cache Duration: ${Math.round((rogue.cache_duration || 0) / 3600000)}h
+
Update Interval: ${Math.round((rogue.update_interval || 0) / 3600000)}h
+
Detection Action: ${rogue.detection_action || 'None'}
+
Auto Update: ${rogue.auto_update ? 'Yes' : 'No'}
+ `; + sections.push(createCollapsibleSection('Rogue Apps Detection', rogueContent, false)); } // Phishing Indicators Summary if (config.phishing_indicators && config.phishing_indicators.length > 0) { const indicatorTypes = {}; - const criticalCount = config.phishing_indicators.filter( - (indicator) => indicator.severity === "critical" - ).length; + const criticalCount = config.phishing_indicators.filter(i => i.severity === 'critical').length; + const codeDrivenCount = config.phishing_indicators.filter(i => i.code_driven).length; config.phishing_indicators.forEach((indicator) => { - const type = indicator.type || "unknown"; + const type = indicator.type || indicator.category || 'unknown'; indicatorTypes[type] = (indicatorTypes[type] || 0) + 1; }); - const indicatorSections = Object.entries(indicatorTypes) - .map( - ([type, count]) => - `
${type}: ${count}
` - ) - .join(""); - - // Code-driven indicators summary - const codeDrivenIndicators = config.phishing_indicators.filter(r => r.code_driven); - let codeDrivenHtml = ''; - if (codeDrivenIndicators.length > 0) { - codeDrivenHtml = `
Code-Driven Indicators: ${codeDrivenIndicators.length}
`; - } + let indicatorsContent = ` +
Total Indicators: ${config.phishing_indicators.length}
+
Critical Severity: ${criticalCount}
+
Code-Driven: ${codeDrivenCount}
+
By Type:
+ `; + + Object.entries(indicatorTypes).forEach(([type, count]) => { + indicatorsContent += `
${type}: ${count}
`; + }); - sections.push(` -
-
Phishing Indicators (${config.phishing_indicators.length} total)
-
Critical Severity Rules: ${criticalCount}
- ${indicatorSections} - ${codeDrivenHtml} -
- `); - } - - // Legacy format support - Trusted origins - if (config.trusted_origins && config.trusted_origins.length > 0) { - sections.push(` -
-
Trusted Origins (${ - config.trusted_origins.length - })
- ${config.trusted_origins - .map((origin) => `
• ${origin}
`) - .join("")} -
- `); + sections.push(createCollapsibleSection('Phishing Indicators', indicatorsContent, false)); } - // Legacy format support - Pattern categories - const patternSections = []; - if (config.phishing && config.phishing.length > 0) { - patternSections.push( - `
Phishing Patterns: ${config.phishing.length}
` - ); - } - if (config.malicious && config.malicious.length > 0) { - patternSections.push( - `
Malicious Patterns: ${config.malicious.length}
` - ); - } - if (config.suspicious && config.suspicious.length > 0) { - patternSections.push( - `
Suspicious Patterns: ${config.suspicious.length}
` - ); - } - if (config.legitimate_patterns && config.legitimate_patterns.length > 0) { - patternSections.push( - `
Legitimate Patterns: ${config.legitimate_patterns.length}
` - ); - } + // Microsoft 365 Detection Requirements + if (config.m365_detection_requirements) { + const req = config.m365_detection_requirements; + const primaryCount = req.primary_elements ? req.primary_elements.length : 0; + const secondaryCount = req.secondary_elements ? req.secondary_elements.length : 0; - if (patternSections.length > 0) { - sections.push(` -
-
Legacy Pattern Categories
- ${patternSections.join("")} -
- `); + const m365Content = ` +
Primary Elements: ${primaryCount}
+
Secondary Elements: ${secondaryCount}
+
Description: ${req.description || 'No description'}
+ `; + sections.push(createCollapsibleSection('Microsoft 365 Detection Requirements', m365Content, false)); } - // Rogue apps detection - if (config.rogue_apps_detection) { - const rogue = config.rogue_apps_detection; - sections.push(` -
-
Rogue Apps Detection
-
Enabled: ${ - rogue.enabled ? "Yes" : "No" - }
-
Source: ${ - rogue.source_url ? rogue.source_url : "None" - }
-
Cache Duration: ${Math.round( - (rogue.cache_duration || 0) / 3600000 - )}h
-
Update Interval: ${Math.round( - (rogue.update_interval || 0) / 3600000 - )}h
-
Detection Action: ${ - rogue.detection_action || "None" - }
-
Auto Update: ${ - rogue.auto_update ? "Yes" : "No" - }
-
- `); + // Exclusion System + if (config.exclusion_system) { + const exclusions = config.exclusion_system; + const domainPatterns = exclusions.domain_patterns || []; + const legitimateContexts = exclusions.context_indicators?.legitimate_contexts || []; + const legitimateSsoPatterns = exclusions.context_indicators?.legitimate_sso_patterns || []; + const suspiciousContexts = exclusions.context_indicators?.suspicious_contexts || []; + + let exclusionsContent = ` +
Domain Patterns: ${domainPatterns.length}
+
Legitimate Context Indicators: ${legitimateContexts.length}
+
Legitimate SSO Patterns: ${legitimateSsoPatterns.length}
+
Suspicious Context Indicators: ${suspiciousContexts.length}
+ `; + + if (domainPatterns.length > 0) { + exclusionsContent += `
Domain Patterns:
`; + exclusionsContent += createExpandableList(domainPatterns, 'patterns', 5); + } + + sections.push(createCollapsibleSection('Exclusion System', exclusionsContent, false)); } - // Configuration statistics + // Configuration Statistics let totalPatterns = 0; - if (config.phishing_indicators) - totalPatterns += config.phishing_indicators.length; + if (config.phishing_indicators) totalPatterns += config.phishing_indicators.length; if (config.phishing) totalPatterns += config.phishing.length; if (config.malicious) totalPatterns += config.malicious.length; if (config.suspicious) totalPatterns += config.suspicious.length; - if (config.legitimate_patterns) - totalPatterns += config.legitimate_patterns.length; + if (config.legitimate_patterns) totalPatterns += config.legitimate_patterns.length; - let totalDetectionElements = 0; - if (config.m365_detection_requirements) { - if (config.m365_detection_requirements.primary_elements) - totalDetectionElements += - config.m365_detection_requirements.primary_elements.length; - if (config.m365_detection_requirements.secondary_elements) - totalDetectionElements += - config.m365_detection_requirements.secondary_elements.length; + let criticalRules = 0; + if (config.phishing_indicators) { + criticalRules = config.phishing_indicators.filter(i => i.severity === 'critical').length; } - let totalExclusions = 0; - if (config.exclusion_system) { - if (config.exclusion_system.domain_patterns) - totalExclusions += config.exclusion_system.domain_patterns.length; - if (config.exclusion_system.context_indicators?.legitimate_contexts) - totalExclusions += - config.exclusion_system.context_indicators.legitimate_contexts.length; - if (config.exclusion_system.context_indicators?.legitimate_sso_patterns) - totalExclusions += - config.exclusion_system.context_indicators.legitimate_sso_patterns - .length; - if (config.exclusion_system.context_indicators?.suspicious_contexts) - totalExclusions += - config.exclusion_system.context_indicators.suspicious_contexts.length; + const statsContent = ` +
Total Detection Patterns: ${totalPatterns}
+
Trusted Login Patterns: ${config.trusted_login_patterns ? config.trusted_login_patterns.length : 0}
+
Microsoft Domain Patterns: ${config.microsoft_domain_patterns ? config.microsoft_domain_patterns.length : 0}
+
Critical Severity Rules: ${criticalRules}
+ `; + sections.push(createCollapsibleSection('Configuration Statistics', statsContent, false)); + + this.elements.configDisplay.innerHTML = sections.join(''); + + // Add event delegation for collapsible sections + this.elements.configDisplay.querySelectorAll('[data-toggle-section]').forEach(header => { + header.addEventListener('click', () => { + const sectionId = header.getAttribute('data-toggle-section'); + this.toggleConfigSection(sectionId); + }); + }); + + // Add event delegation for expandable lists + this.elements.configDisplay.querySelectorAll('[data-toggle-list]').forEach(toggle => { + toggle.addEventListener('click', () => { + const listId = toggle.getAttribute('data-toggle-list'); + this.toggleConfigList(listId); + }); + }); + } + + toggleConfigSection(sectionId) { + const section = document.querySelector(`[data-section-id="${sectionId}"]`); + if (section) { + section.classList.toggle('expanded'); } + } - let criticalRules = 0; - if (config.phishing_indicators) { - criticalRules = config.phishing_indicators.filter( - (indicator) => indicator.severity === "critical" - ).length; - } - - sections.push(` -
-
Configuration Statistics
-
Total Detection Patterns: ${totalPatterns}
-
Microsoft 365 Detection Elements: ${totalDetectionElements}
-
Trusted Login Patterns: ${ - config.trusted_login_patterns - ? config.trusted_login_patterns.length - : 0 - }
-
Microsoft Domain Patterns: ${ - config.microsoft_domain_patterns - ? config.microsoft_domain_patterns.length - : 0 - }
-
Critical Severity Rules: ${criticalRules}
-
Total Exclusions: ${totalExclusions}
-
- `); + toggleConfigList(listId) { + const list = document.getElementById(listId); + const toggle = list?.nextElementSibling; + if (list && toggle) { + const isVisible = list.classList.contains('visible'); + list.classList.toggle('visible'); + if (isVisible) { + const remaining = list.querySelectorAll('.config-item').length; + const label = toggle.textContent.match(/more (.+)$/)?.[1] || 'items'; + toggle.textContent = `▸ Show ${remaining} more ${label}`; + } else { + toggle.textContent = `▾ Hide details`; + } + } + } + + toggleExpandCollapseAll() { + const sections = document.querySelectorAll('.config-section-collapsible'); + if (!sections.length) return; - this.elements.configDisplay.innerHTML = sections.join(""); + // Check if any section is collapsed + const hasCollapsed = Array.from(sections).some(section => !section.classList.contains('expanded')); + + // If any are collapsed, expand all. Otherwise, collapse all. + sections.forEach(section => { + if (hasCollapsed) { + section.classList.add('expanded'); + } else { + section.classList.remove('expanded'); + } + }); + + // Update button text + if (this.elements.expandCollapseAll) { + const icon = this.elements.expandCollapseAll.querySelector(".material-icons"); + if (hasCollapsed) { + icon.textContent = "unfold_less"; + this.elements.expandCollapseAll.innerHTML = 'unfold_less Collapse All'; + } else { + icon.textContent = "unfold_more"; + this.elements.expandCollapseAll.innerHTML = 'unfold_more Expand All'; + } + } } toggleConfigView() { @@ -1806,6 +1766,11 @@ class CheckOptions { console.log("Simulate Enterprise Mode:", this.simulateEnterpriseMode); + // If disabling, reload the original branding configuration first + if (!this.simulateEnterpriseMode) { + await this.loadBrandingConfiguration(); + } + // Reload the policy information to apply/remove enterprise restrictions await this.loadPolicyInfo(); @@ -1815,7 +1780,7 @@ class CheckOptions { // Show notification to user const mode = this.simulateEnterpriseMode ? "enabled" : "disabled"; this.showToast( - `Enterprise simulation mode ${mode}. Page will reflect policy restrictions.`, + `Enterprise simulation mode ${mode}. Page will reflect ${this.simulateEnterpriseMode ? 'policy restrictions' : 'original settings'}.`, "info" ); } @@ -2512,10 +2477,20 @@ class CheckOptions { } else { console.log("👤 No managed policies found - user mode"); + // Clear managed policies + this.managedPolicies = null; + + // Restore original branding (not enterprise branding) + await this.loadBrandingConfiguration(); + this.applyBranding(); + // Hide policy badge if (this.elements.policyBadge) { this.elements.policyBadge.style.display = "none"; } + + // Re-enable all fields that might have been disabled by policies + this.enableAllPolicyManagedFields(); } } catch (error) { console.error("Failed to load policy info:", error); @@ -2655,6 +2630,76 @@ class CheckOptions { } } + enableAllPolicyManagedFields() { + // Re-enable all fields that might have been disabled by policies + const allFields = [ + this.elements.showNotifications, + this.elements.enableValidPageBadge, + this.elements.enablePageBlocking, + this.elements.enableCippReporting, + this.elements.cippServerUrl, + this.elements.cippTenantId, + this.elements.customRulesUrl, + this.elements.updateInterval, + this.elements.urlAllowlist, + this.elements.enableDebugLogging, + this.elements.companyName, + this.elements.companyURL, + this.elements.productName, + this.elements.supportEmail, + this.elements.primaryColor, + this.elements.logoUrl, + ]; + + allFields.forEach((element) => { + if (element) { + element.disabled = false; + element.title = ''; + element.classList.remove("policy-managed"); + + // Remove lock icons + const lockIcon = element.parentNode?.querySelector(".policy-lock"); + if (lockIcon) { + lockIcon.remove(); + } + } + }); + + // Re-enable the save button and restore original text + if (this.elements.saveSettings) { + this.elements.saveSettings.title = "Save all settings"; + this.elements.saveSettings.textContent = "Save Settings"; + this.elements.saveSettings.classList.remove("managed-mode"); + } + + // Re-show tabs that might have been hidden + const restrictedTabs = ["general", "detection", "branding"]; + restrictedTabs.forEach((tabName) => { + const menuItem = document.querySelector(`[data-section="${tabName}"]`); + if (menuItem) { + const listItem = menuItem.closest("li"); + if (listItem) { + listItem.style.display = ""; + } + } + }); + + // Remove enterprise notice + const enterpriseNotice = document.querySelector(".enterprise-notice"); + if (enterpriseNotice) { + enterpriseNotice.remove(); + } + + // Remove development notice + const devNotice = document.querySelector(".development-notice"); + if (devNotice) { + devNotice.remove(); + } + + // Clear enterprise managed flag + this.isEnterpriseManaged = false; + } + applyEnterpriseRestrictions(policies) { // Set enterprise managed flag this.isEnterpriseManaged = true; @@ -3566,8 +3611,75 @@ class CheckOptions { toggleIcon.textContent = "dark_mode"; } } + + async viewDefaultDomains() { + try { + // Load detection rules to get default protected domains + const result = await chrome.storage.local.get(['detection_rules_cache']); + const cachedRules = result?.detection_rules_cache?.rules; + + let defaultDomains = []; + if (cachedRules && cachedRules.domain_squatting && cachedRules.domain_squatting.protected_domains) { + defaultDomains = cachedRules.domain_squatting.protected_domains; + } else { + // Fallback to loading from local file + const response = await fetch(chrome.runtime.getURL('rules/detection-rules.json')); + const rules = await response.json(); + if (rules.domain_squatting && rules.domain_squatting.protected_domains) { + defaultDomains = rules.domain_squatting.protected_domains; + } + } + + if (defaultDomains.length === 0) { + this.showToast('No default protected domains found', 'warning'); + return; + } + + // Show confirm dialog with default domains + const domainList = defaultDomains.map(d => `• ${d}`).join('\n'); + const message = `Default Protected Domains (${defaultDomains.length}):\n\n${domainList}\n\nThese domains are protected by default. You can add additional domains in the "Protected Domains" field above.`; + + await this.showConfirmDialog( + 'Default Protected Domains', + message + ); + } catch (error) { + console.error('Failed to load default domains:', error); + this.showToast('Failed to load default protected domains', 'error'); + } + } } +// Minimal config summary: just show code-driven indicator count and key settings +function renderConfigSummary(config) { + const codeDrivenDiv = document.getElementById('codeDrivenIndicators'); + const keySettingsUl = document.getElementById('configKeySettings'); + if (!config || !config.phishing_indicators) return; + // Show only code-driven indicator count + const codeDrivenCount = config.phishing_indicators.filter(r => r.code_driven).length; + codeDrivenDiv.innerHTML = `
Code-Driven Indicators: ${codeDrivenCount}
`; + // Show some key settings + keySettingsUl.innerHTML = ''; + if (config.version) keySettingsUl.innerHTML += `
  • Rules Version: ${config.version}
  • `; + if (config.lastUpdated) keySettingsUl.innerHTML += `
  • Last Updated: ${config.lastUpdated}
  • `; + if (config.detection_settings && config.detection_settings.block_threshold !== undefined) { + keySettingsUl.innerHTML += `
  • Block Threshold: ${config.detection_settings.block_threshold}
  • `; + } + if (config.detection_settings && config.detection_settings.warn_threshold !== undefined) { + keySettingsUl.innerHTML += `
  • Warn Threshold: ${config.detection_settings.warn_threshold}
  • `; + } +} + +// Patch into config loading logic +(function() { + const origShowConfig = window.showConfigDisplay; + window.showConfigDisplay = function(config) { + if (typeof origShowConfig === 'function') origShowConfig(config); + renderConfigSummary(config); + document.getElementById('configSummary').style.display = ''; + }; +})(); + // Initialize options page when DOM is loaded document.addEventListener("DOMContentLoaded", () => { window.checkOptions = new CheckOptions(); diff --git a/rules/detection-rules.json b/rules/detection-rules.json index a8bdeac..747f60c 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -1928,5 +1928,52 @@ "log_matches": true, "auto_update": true, "fallback_on_error": true + }, + "domain_squatting": { + "description": "Domain squatting detection configuration to protect against typosquatting, homoglyphs, and combosquatting attacks", + "enabled": true, + "action": "block", + "deviation_threshold": 2, + "algorithms": { + "levenshtein": true, + "homoglyph": true, + "typosquat": true, + "combosquat": true + }, + "protected_domains": [ + "microsoft.com", + "microsoftonline.com", + "office.com", + "office365.com", + "outlook.com", + "hotmail.com", + "live.com", + "onedrive.com", + "sharepoint.com", + "azure.com", + "windows.com", + "xbox.com", + "skype.com", + "linkedin.com", + "github.com", + "google.com", + "gmail.com", + "facebook.com", + "twitter.com", + "instagram.com", + "amazon.com", + "apple.com", + "paypal.com", + "netflix.com", + "dropbox.com", + "salesforce.com", + "adobe.com", + "zoom.us", + "slack.com", + "atlassian.com", + "shopify.com" + ], + "severity": "high", + "log_detections": true } } diff --git a/scripts/background.js b/scripts/background.js index 8d50fe8..33e5ba8 100644 --- a/scripts/background.js +++ b/scripts/background.js @@ -11,6 +11,7 @@ import { ConfigManager } from "./modules/config-manager.js"; import { PolicyManager } from "./modules/policy-manager.js"; import { DetectionRulesManager } from "./modules/detection-rules-manager.js"; import { WebhookManager } from "./modules/webhook-manager.js"; +import { DomainSquattingDetector } from "./modules/domain-squatting-detector.js"; import logger from "./utils/logger.js"; import { store as storeLog } from "./utils/background-logger.js"; @@ -290,6 +291,7 @@ class CheckBackground { this.policyManager = new PolicyManager(); this.detectionRulesManager = new DetectionRulesManager(); this.rogueAppsManager = new RogueAppsManager(); + this.domainSquattingDetector = new DomainSquattingDetector(); this.webhookManager = new WebhookManager(this.configManager); this.isInitialized = false; this.initializationPromise = null; @@ -398,6 +400,15 @@ class CheckBackground { // Initialize detection rules manager await this.detectionRulesManager.initialize(); + + // Initialize domain squatting detector with rules and URL allowlist + const detectionRules = this.detectionRulesManager.cachedRules; + if (detectionRules) { + const config = await this.configManager.getConfig(); + const urlAllowlist = config?.urlAllowlist || []; + await this.domainSquattingDetector.initialize(detectionRules, urlAllowlist); + logger.log("Domain squatting detector initialized"); + } await this.refreshPolicy(); @@ -1411,6 +1422,15 @@ class CheckBackground { rules, message: "Detection rules updated", }); + + // Also update domain squatting detector with new rules and URL allowlist + const updatedRules = await this.detectionRulesManager.getDetectionRules(); + if (updatedRules && this.domainSquattingDetector) { + const config = await this.configManager.getConfig(); + const urlAllowlist = config?.urlAllowlist || []; + await this.domainSquattingDetector.initialize(updatedRules, urlAllowlist); + logger.log("Domain squatting detector updated with new rules"); + } } catch (error) { logger.error( "Check: Failed to force update detection rules:", @@ -1419,6 +1439,27 @@ class CheckBackground { sendResponse({ success: false, error: error.message }); } break; + + case "check_domain_squatting": + try { + const { domain } = message; + if (!domain) { + sendResponse({ success: false, error: "Domain parameter required" }); + break; + } + + const result = this.domainSquattingDetector.checkDomain(domain); + // Include the action from rules configuration + if (result && result.detected) { + const rules = await this.detectionRulesManager.getDetectionRules(); + result.action = rules?.domain_squatting?.action || 'block'; + } + sendResponse({ success: true, result }); + } catch (error) { + logger.error("Check: Failed to check domain squatting:", error); + sendResponse({ success: false, error: error.message }); + } + break; case "UPDATE_CONFIG": try { @@ -1440,6 +1481,15 @@ class CheckBackground { updatedConfig?.enableValidPageBadge || this.policy?.EnableValidPageBadge; + // Update domain squatting detector with new configuration + // If URL allowlist changed, reinitialize detector to extract new domains + if (this.domainSquattingDetector) { + const detectionRules = this.detectionRulesManager.cachedRules; + const urlAllowlist = updatedConfig?.urlAllowlist || []; + await this.domainSquattingDetector.initialize(detectionRules, urlAllowlist); + logger.log("Domain squatting detector configuration updated"); + } + // If badge was disabled, remove badges from all tabs if (previousBadgeEnabled && !newBadgeEnabled) { logger.log( diff --git a/scripts/blocked.js b/scripts/blocked.js index a998c31..6bda05d 100644 --- a/scripts/blocked.js +++ b/scripts/blocked.js @@ -25,7 +25,8 @@ function parseUrlParams() { if (detailsParam) { try { - const details = JSON.parse(decodeURIComponent(detailsParam)); + // URLSearchParams.get() already decodes the URI component, so don't decode again + const details = JSON.parse(detailsParam); console.log("Parsed details:", details); // Store details globally for false positive reporting @@ -50,8 +51,15 @@ function parseUrlParams() { document.getElementById("blockReason").textContent = details.reason; } - // Update threat category based on rule description or score - if (details.ruleDescription) { + // Update threat category based on type or rule description + if (details.type === "domain_squatting") { + document.getElementById("threatCategory").textContent = "Domain Squatting"; + // Custom messaging for domain squatting + if (details.protectedDomain) { + document.getElementById("blockReason").textContent = + `This website's domain closely resembles "${details.protectedDomain}" but is NOT the legitimate site. Entering your credentials here could compromise your account.`; + } + } else if (details.ruleDescription) { document.getElementById("threatCategory").textContent = details.ruleDescription; } else if (details.rule) { @@ -756,8 +764,53 @@ function populateTechnicalDetails(details) { console.log("Details.threats:", details.threats); console.log("Details.phishingIndicators:", details.phishingIndicators); console.log("Details.foundIndicators:", details.foundIndicators); + console.log("Details.type:", details.type); + + // Handle domain squatting specific details + if (details.type === "domain_squatting") { + console.log("Populating domain squatting details"); + + // Detection Scores - use confidence for domain squatting + if (details.confidence !== undefined) { + document.getElementById("techScore").textContent = `${Math.round(details.confidence * 100)}%`; + } + document.getElementById("techThreshold").textContent = "Domain Similarity"; + + // Threat Analysis - use techniques + let indicatorCount = 0; + if (details.techniques && Array.isArray(details.techniques)) { + indicatorCount = details.techniques.length; + document.getElementById("techIndicatorCount").textContent = indicatorCount; + + // Set severity + const severityElement = document.getElementById("techSeverity"); + const severityMap = { critical: "CRITICAL", high: "HIGH", medium: "MEDIUM", low: "LOW" }; + const severityText = severityMap[details.severity] || details.severity.toUpperCase(); + severityElement.innerHTML = `${severityText}`; + + // Populate techniques as indicators + populatePhishingIndicatorsList(details.techniques, details); + } + + // Detection method + document.getElementById("techDetectionMethod").textContent = "Domain Squatting Detection"; + + // Page Information + if (details.testDomain) { + document.getElementById("techPageTitle").textContent = `Suspicious Domain: ${details.testDomain}`; + } + if (details.protectedDomain) { + const userAgent = document.getElementById("techUserAgent"); + userAgent.textContent = `Impersonating: ${details.protectedDomain}`; + } + document.getElementById("techTimestamp").textContent = details.detectionTime + ? new Date(details.detectionTime).toLocaleString() + : new Date().toLocaleString(); + + return; // Exit early for domain squatting + } - // Detection Scores + // Detection Scores for phishing detection if (details.score !== undefined) { document.getElementById("techScore").textContent = details.score; } @@ -765,7 +818,7 @@ function populateTechnicalDetails(details) { document.getElementById("techThreshold").textContent = details.threshold; } - // Threat Analysis - Use multiple data sources + // Threat Analysis - Use multiple data sources for phishing let phishingIndicators = []; let indicatorCount = 0; @@ -889,6 +942,7 @@ function populateTechnicalDetails(details) { function populatePhishingIndicatorsList(indicators, details) { console.log("=== POPULATING PHISHING INDICATORS LIST ==="); console.log("Indicators to display:", indicators); + console.log("Details type:", details.type); const container = document.getElementById("techPhishingIndicators"); @@ -919,7 +973,27 @@ function populatePhishingIndicatorsList(indicators, details) { return; } - // Create formatted list of indicators + // Handle domain squatting techniques differently + if (details.type === "domain_squatting") { + console.log("Displaying domain squatting techniques"); + const techniquesHTML = indicators + .map((technique) => { + const techniqueName = technique.technique || technique.id || "Unknown Technique"; + const description = technique.description || "Domain similarity detected"; + + return `
    + ${techniqueName}
    + ${description} +
    `; + }) + .join(""); + + container.innerHTML = techniquesHTML; + console.log("Populated domain squatting techniques with", indicators.length, "techniques"); + return; + } + + // Create formatted list of indicators for phishing const indicatorHTML = indicators .map((indicator) => { const id = indicator.id || indicator.type || "Unknown"; diff --git a/scripts/content.js b/scripts/content.js index 60a2522..b21e116 100644 --- a/scripts/content.js +++ b/scripts/content.js @@ -30,8 +30,8 @@ if (window.checkExtensionLoaded) { let developerConsoleLoggingEnabled = false; // Cache for developer console logging setting let showingBanner = false; // Flag to prevent DOM monitoring loops when showing banners let escalatedToBlock = false; // Flag to indicate page has been escalated to block - stop all monitoring - const MAX_SCANS = 5; // Prevent infinite scanning - reduced for performance - const SCAN_COOLDOWN = 1200; // 1200ms between scans - increased for performance + const MAX_SCANS = 8; // Allow more rescans for dynamically loaded content + const SCAN_COOLDOWN = 800; // 800ms between scans - allow faster rescans for dynamic content const THREAT_TRIGGERED_COOLDOWN = 500; // Shorter cooldown for threat-triggered re-scans const WARNING_THRESHOLD = 3; // Block if 4+ warning threats found (escalation threshold) const PHISHING_PROCESSING_TIMEOUT = 10000; // 10 second timeout for phishing indicator processing @@ -3944,7 +3944,138 @@ if (window.checkExtensionLoaded) { }` ); - // Step 4: Pre-check domain for obvious non-threats only + // Step 4: Check for domain squatting (typosquatting, homoglyphs, etc.) + // This runs BEFORE phishing detection to catch domain-based threats early + try { + const domainSquattingResult = await chrome.runtime.sendMessage({ + type: "check_domain_squatting", + domain: window.location.hostname + }); + + if (domainSquattingResult?.success && domainSquattingResult.result?.detected) { + const squattingData = domainSquattingResult.result; + logger.warn(`⚠️ DOMAIN SQUATTING DETECTED:`); + logger.warn(` Test Domain: ${squattingData.testDomain}`); + logger.warn(` Protected Domain: ${squattingData.protectedDomain}`); + logger.warn(` Techniques: ${squattingData.techniques.map(t => t.technique).join(', ')}`); + logger.warn(` Severity: ${squattingData.severity}`); + logger.warn(` Confidence: ${(squattingData.confidence * 100).toFixed(1)}%`); + logger.warn(` Action: ${squattingData.action || 'warn'}`); + + // Check if notifications should be shown + const showNotifications = config.showNotifications !== false; + + // Determine if we should block the page + // Requires: 1) enablePageBlocking is ON, 2) domain_squatting action is "block" + logger.debug(` enablePageBlocking: ${config.enablePageBlocking}`); + logger.debug(` squattingData.action: ${squattingData.action}`); + const shouldBlock = squattingData.action === 'block' && + config.enablePageBlocking !== false; + logger.debug(` shouldBlock: ${shouldBlock}`); + + // Log domain squatting detection + logProtectionEvent({ + type: "threat_detected", + action: shouldBlock ? "blocked" : "warned", + url: location.href, + origin: currentOrigin, + reason: `Domain squatting detected: ${squattingData.techniques.map(t => t.description).join('; ')}`, + severity: squattingData.severity, + redirectTo: null, + clientId: null, + ruleType: "domain_squatting", + squattingDetails: squattingData + }); + + // Send CIPP report for domain squatting detection + sendCippReport({ + type: "domain_squatting_detected", + url: defangUrl(location.href), + origin: currentOrigin, + testDomain: squattingData.testDomain, + protectedDomain: squattingData.protectedDomain, + techniques: squattingData.techniques.map(t => ({ + technique: t.technique, + description: t.description, + details: t.details + })), + severity: squattingData.severity, + confidence: squattingData.confidence, + action: shouldBlock ? "blocked" : "warned", + reason: `Domain squatting detected: ${squattingData.techniques.map(t => t.description).join('; ')}` + }); + + // Send domain_squatting_detected webhook + chrome.runtime + .sendMessage({ + type: "send_webhook", + webhookType: "domain_squatting_detected", + data: { + url: defangUrl(location.href), + testDomain: squattingData.testDomain, + protectedDomain: squattingData.protectedDomain, + techniques: squattingData.techniques.map(t => ({ + technique: t.technique, + description: t.description + })), + severity: squattingData.severity, + confidence: squattingData.confidence, + action: shouldBlock ? "blocked" : "warned", + reason: `Domain squatting detected: ${squattingData.techniques.map(t => t.description).join('; ')}` + }, + }) + .catch((err) => { + logger.debug("Failed to send domain squatting webhook:", err); + }); + + if (shouldBlock) { + // Block the page - redirect to blocked page with domain squatting context + const techniquesDesc = squattingData.techniques.map(t => + `${t.technique}: ${t.description}` + ).join('; '); + + await showBlockingOverlay( + `Domain Squatting: This site closely resembles "${squattingData.protectedDomain}" but is not the legitimate site`, + { + type: "domain_squatting", + severity: squattingData.severity, + testDomain: squattingData.testDomain, + protectedDomain: squattingData.protectedDomain, + techniques: squattingData.techniques, + confidence: squattingData.confidence, + reason: `Domain squatting detected: ${techniquesDesc}`, + detectionMethod: "domain-squatting", + detectionTime: Date.now() + } + ); + return; // Stop processing, page is blocked + } else if (showNotifications) { + // Show warning banner for domain squatting (only if notifications enabled) + const techniquesDesc = squattingData.techniques.map(t => + `${t.technique}: ${t.description}` + ).join('\n'); + + showWarningBanner( + `⚠️ POTENTIAL DOMAIN SQUATTING: This domain closely resembles "${squattingData.protectedDomain}"`, + { + type: "domain_squatting", + severity: squattingData.severity, + reason: `Domain squatting techniques detected:\n${techniquesDesc}`, + protectedDomain: squattingData.protectedDomain, + confidence: squattingData.confidence, + techniques: squattingData.techniques + } + ); + } + + // If we showed a warning but not blocking, continue with phishing detection + // If we blocked, we already returned above + } + } catch (squattingError) { + logger.debug("Domain squatting check failed or disabled:", squattingError.message); + } + + // Step 5: Pre-check domain for obvious non-threats only // NOTE: We removed the restrictive domain check that was blocking training platforms // like KnowBe4. Phishing simulations use legitimate domains but copy Microsoft UI. // Let content-based detection handle all cases. @@ -3956,7 +4087,7 @@ if (window.checkExtensionLoaded) { `Analyzing domain "${currentDomain}" - proceeding with content-based detection` ); - // Step 5: Check if page is an MS logon page (using rule file requirements) + // Step 6: Check if page is an MS logon page (using rule file requirements) const msDetection = detectMicrosoftElements(); if (!msDetection.isLogonPage) { // Check if page has ANY Microsoft-related elements before running expensive phishing indicators @@ -5342,6 +5473,8 @@ if (window.checkExtensionLoaded) { }); // Fallback: Check periodically for content that might have loaded without triggering observer + let fallbackCheckCount = 0; + const MAX_FALLBACK_CHECKS = 5; // Allow up to 5 fallback checks const checkInterval = setInterval(() => { // Stop if page has been escalated to block if (escalatedToBlock) { @@ -5360,17 +5493,27 @@ if (window.checkExtensionLoaded) { return; } + fallbackCheckCount++; const currentElementCount = document.querySelectorAll("*").length; const hasSignificantContent = document.body?.textContent?.length > 1000; if (hasSignificantContent && currentElementCount > 50) { logger.log( - "⏰ Fallback timer detected significant content - re-running analysis" + `⏰ Fallback timer detected significant content - re-running analysis (check ${fallbackCheckCount}/${MAX_FALLBACK_CHECKS})` ); - clearInterval(checkInterval); runProtection(true); + + // Stop after MAX_FALLBACK_CHECKS successful rescans + if (fallbackCheckCount >= MAX_FALLBACK_CHECKS) { + logger.log("⏰ Maximum fallback checks reached - stopping"); + clearInterval(checkInterval); + } + } else if (fallbackCheckCount >= MAX_FALLBACK_CHECKS) { + // Stop after MAX_FALLBACK_CHECKS attempts even if no significant content + logger.debug("⏰ Maximum fallback check attempts reached - stopping"); + clearInterval(checkInterval); } - }, 2000); + }, 1500); // Check every 1.5 seconds // Stop monitoring after 30 seconds to prevent resource drain setTimeout(() => { @@ -5789,6 +5932,20 @@ if (window.checkExtensionLoaded) { bannerIcon = "🔍"; bannerColor = "linear-gradient(135deg, #2196f3, #1976d2)"; // Blue for scanning } + // Check for domain squatting detection - tailored messaging + else if ( + analysisData?.type === "domain_squatting" || + reason.toLowerCase().includes("domain squatting") || + reason.toLowerCase().includes("typosquat") + ) { + bannerTitle = "⚠️ Suspicious Domain Detected"; + bannerIcon = "🔗"; + bannerColor = "linear-gradient(135deg, #ff5722, #d84315)"; // Same orange-red as high risk warnings + // Override reason text for domain squatting to be more user-friendly + if (analysisData?.protectedDomain) { + reason = `This website's domain looks similar to "${analysisData.protectedDomain}" but is NOT the legitimate site. Be careful entering any credentials.`; + } + } // Check for rogue app detection else if ( analysisData?.type === "rogue_app_on_legitimate_domain" || @@ -5810,7 +5967,7 @@ if (window.checkExtensionLoaded) { // Layout: left branding slot, absolutely centered message block, dismiss button on right. const bannerContent = ` -
    +
    ${bannerIcon} @@ -5818,10 +5975,10 @@ if (window.checkExtensionLoaded) { ${reason}${detailsText}
    + font-size:14px;font-weight:bold;line-height:1;box-sizing:border-box;font-family:monospace;z-index:3;">×
    `; // Check if banner already exists @@ -6400,11 +6557,11 @@ if (window.checkExtensionLoaded) { if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { - setTimeout(runProtection, 100); // Small delay to ensure DOM is stable + setTimeout(runProtection, 500); // Longer delay to allow dynamic content to load }); } else { - // DOM already ready - setTimeout(runProtection, 100); + // DOM already ready - wait longer since content may still be loading + setTimeout(runProtection, 800); } } catch (error) { logger.error("Failed to initialize protection:", error.message); diff --git a/scripts/modules/domain-squatting-detector.js b/scripts/modules/domain-squatting-detector.js new file mode 100644 index 0000000..2632733 --- /dev/null +++ b/scripts/modules/domain-squatting-detector.js @@ -0,0 +1,574 @@ +/** + * Domain Squatting Detector Module + * Detects typosquatting, homoglyphs, combosquatting, and other domain impersonation techniques + */ + +import logger from '../utils/logger.js'; + +export class DomainSquattingDetector { + constructor() { + this.protectedDomains = []; + this.enabled = true; + this.deviationThreshold = 2; // Maximum Levenshtein distance + this.algorithms = { + levenshtein: true, + homoglyph: true, + typosquat: true, + combosquat: true + }; + + // Common homoglyphs (confusable characters) + this.homoglyphs = { + 'a': ['à', 'á', 'â', 'ã', 'ä', 'å', 'ā', 'ă', 'ą', 'α', 'а'], + 'b': ['ḃ', 'ḅ', 'ḇ', 'ь', 'в'], + 'c': ['ć', 'ĉ', 'ċ', 'ç', 'č', 'ϲ', 'с'], + 'd': ['ď', 'ḋ', 'ḍ', 'ḏ', 'ḑ', 'ḓ', 'ԁ', 'ժ'], + 'e': ['è', 'é', 'ê', 'ë', 'ē', 'ĕ', 'ė', 'ę', 'ě', 'е', 'ε'], + 'g': ['ĝ', 'ğ', 'ġ', 'ģ', 'ց', 'ǥ'], + 'h': ['ĥ', 'ḣ', 'ḥ', 'ḧ', 'ḩ', 'ḫ', 'һ', 'հ'], + 'i': ['ì', 'í', 'î', 'ï', 'ĩ', 'ī', 'ĭ', 'į', 'ı', 'і', 'ι'], + 'j': ['ĵ', 'ј'], + 'k': ['ķ', 'ḱ', 'ḳ', 'ḵ', 'κ', 'к'], + 'l': ['ĺ', 'ļ', 'ľ', 'ḷ', 'ḹ', 'ḻ', 'ḽ', 'ӏ', 'ℓ'], + 'm': ['ḿ', 'ṁ', 'ṃ', 'м'], + 'n': ['ñ', 'ń', 'ņ', 'ň', 'ṅ', 'ṇ', 'ṉ', 'ṋ', 'п'], + 'o': ['ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ō', 'ŏ', 'ő', 'ο', 'о', 'օ'], + 'p': ['ṕ', 'ṗ', 'р', 'ρ'], + 'q': ['ԛ'], + 'r': ['ŕ', 'ŗ', 'ř', 'ṙ', 'ṛ', 'ṝ', 'ṟ', 'г'], + 's': ['ś', 'ŝ', 'ş', 'š', 'ṡ', 'ṣ', 'ṥ', 'ṧ', 'ṩ', 'ѕ'], + 't': ['ţ', 'ť', 'ṫ', 'ṭ', 'ṯ', 'ṱ', 'т', 'τ'], + 'u': ['ù', 'ú', 'û', 'ü', 'ũ', 'ū', 'ŭ', 'ů', 'ű', 'ų', 'υ', 'и'], + 'v': ['ṽ', 'ṿ', 'ν', 'ѵ'], + 'w': ['ŵ', 'ẁ', 'ẃ', 'ẅ', 'ẇ', 'ẉ', 'ẘ', 'ԝ'], + 'x': ['ẋ', 'ẍ', 'х', 'χ'], + 'y': ['ý', 'ÿ', 'ŷ', 'ẏ', 'ẙ', 'ỳ', 'ỵ', 'у', 'ү'], + 'z': ['ź', 'ż', 'ž', 'ẑ', 'ẓ', 'ẕ', 'ᴢ'], + '0': ['о', 'ο', '᧐'], + '1': ['l', 'і', 'Ӏ', 'ǀ'], + '3': ['з', 'ʒ', 'ȝ'], + '5': ['ƽ'], + '6': ['б'], + '8': ['ց'], + '9': ['ԛ', 'ց'] + }; + + // Reverse lookup for homoglyphs + this.homoglyphReverse = {}; + this.buildReverseHomoglyphMap(); + } + + /** + * Build reverse lookup map for homoglyphs + */ + buildReverseHomoglyphMap() { + for (const [base, variants] of Object.entries(this.homoglyphs)) { + for (const variant of variants) { + this.homoglyphReverse[variant] = base; + } + } + } + + /** + * Extract domains from URL allowlist patterns + * Handles regex patterns, wildcards, and plain URLs/domains + */ + extractDomainsFromAllowlist(allowlist) { + if (!Array.isArray(allowlist) || allowlist.length === 0) { + return []; + } + + const domains = []; + + for (const pattern of allowlist) { + if (!pattern || typeof pattern !== 'string') continue; + + try { + let domain = null; + + // Remove regex anchors and escaping + let cleaned = pattern.trim() + .replace(/^\^/, '') // Remove leading ^ + .replace(/\$$/, '') // Remove trailing $ + .replace(/\\/g, ''); // Remove escape characters + + // Try to extract domain from URL pattern + // Pattern formats: + // - https://example.com/... + // - ^https://example\.com$ + // - *.example.com + // - example.com + + // Extract hostname from URL-like patterns + const urlMatch = cleaned.match(/^(?:https?:\/\/)?([a-zA-Z0-9][\w\-\.]*[a-zA-Z0-9])/); + if (urlMatch) { + domain = urlMatch[1]; + + // Remove wildcards + domain = domain.replace(/^\*\./, ''); + + // Remove path and query string indicators + domain = domain.split('/')[0].split('?')[0].split('#')[0]; + + // Remove regex patterns like (.*)? + domain = domain.replace(/\(.*?\)/g, ''); + + // Remove trailing dots or special chars, only allow valid domain characters + domain = domain.replace(/[^a-zA-Z0-9\-\.]/g, '').replace(/\.$/, ''); + + // Validate it looks like a domain + if (domain && domain.includes('.') && domain.length > 3) { + domains.push(domain.toLowerCase()); + } + } + } catch (error) { + logger.debug(`Could not extract domain from pattern: ${pattern}`, error); + } + } + + // Remove duplicates + return [...new Set(domains)]; + } + + /** + * Initialize with configuration + */ + async initialize(config, urlAllowlist = []) { + try { + if (config.domain_squatting) { + this.enabled = config.domain_squatting.enabled !== false; + this.protectedDomains = config.domain_squatting.protected_domains || []; + this.deviationThreshold = config.domain_squatting.deviation_threshold || 2; + + if (config.domain_squatting.algorithms) { + this.algorithms = { ...this.algorithms, ...config.domain_squatting.algorithms }; + } + } + + // Extract domains from URL allowlist patterns + const allowlistDomains = this.extractDomainsFromAllowlist(urlAllowlist); + if (allowlistDomains.length > 0) { + // Merge with protected domains from rules (avoid duplicates) + const allDomains = [...new Set([...this.protectedDomains, ...allowlistDomains])]; + this.protectedDomains = allDomains; + logger.log(`Added ${allowlistDomains.length} domains from URL allowlist`); + } + + logger.log('DomainSquattingDetector initialized:', { + enabled: this.enabled, + protectedDomains: this.protectedDomains.length, + fromRules: config.domain_squatting?.protected_domains?.length || 0, + fromAllowlist: allowlistDomains.length, + deviationThreshold: this.deviationThreshold + }); + } catch (error) { + logger.error('Failed to initialize DomainSquattingDetector:', error); + } + } + + /** + * Update configuration + */ + updateConfig(config) { + if (config.enabled !== undefined) { + this.enabled = config.enabled; + } + if (config.protected_domains) { + this.protectedDomains = config.protected_domains; + } + if (config.deviation_threshold !== undefined) { + this.deviationThreshold = config.deviation_threshold; + } + if (config.algorithms) { + this.algorithms = { ...this.algorithms, ...config.algorithms }; + } + } + + /** + * Check if a domain is attempting to squat on protected domains + * @param {string} testDomain - Domain to test + * @returns {Object|null} Detection result or null if no squatting detected + */ + checkDomain(testDomain) { + if (!this.enabled || !testDomain) { + return null; + } + + // Extract domain without subdomain and TLD for comparison + const testBase = this.extractBaseDomain(testDomain); + + for (const protectedDomain of this.protectedDomains) { + const protectedBase = this.extractBaseDomain(protectedDomain); + + // Skip if domains are identical + if (testBase === protectedBase) { + continue; + } + + // Run detection algorithms + const detections = []; + + if (this.algorithms.levenshtein) { + const levenshteinResult = this.detectLevenshtein(testBase, protectedBase); + if (levenshteinResult) { + detections.push(levenshteinResult); + } + } + + if (this.algorithms.homoglyph) { + const homoglyphResult = this.detectHomoglyph(testBase, protectedBase); + if (homoglyphResult) { + detections.push(homoglyphResult); + } + } + + if (this.algorithms.typosquat) { + const typosquatResult = this.detectTyposquat(testBase, protectedBase); + if (typosquatResult) { + detections.push(typosquatResult); + } + } + + if (this.algorithms.combosquat) { + const combosquatResult = this.detectCombosquat(testBase, protectedBase); + if (combosquatResult) { + detections.push(combosquatResult); + } + } + + // If any detection triggered, return result + if (detections.length > 0) { + return { + detected: true, + testDomain: testDomain, + protectedDomain: protectedDomain, + techniques: detections, + severity: this.calculateSeverity(detections), + confidence: this.calculateConfidence(detections) + }; + } + } + + return null; + } + + /** + * Extract base domain without subdomain and TLD + */ + extractBaseDomain(domain) { + if (!domain) return ''; + + // Remove protocol + domain = domain.replace(/^https?:\/\//, ''); + + // Remove path + domain = domain.split('/')[0]; + + // Remove port + domain = domain.split(':')[0]; + + // Split by dots + const parts = domain.split('.'); + + // Get the main domain part (second-to-last typically) + if (parts.length >= 2) { + // Handle common two-part TLDs like .co.uk, .com.au + if (parts.length >= 3 && ['co', 'com', 'net', 'org', 'gov', 'edu'].includes(parts[parts.length - 2])) { + return parts[parts.length - 3]; + } + return parts[parts.length - 2]; + } + + return parts[0] || ''; + } + + /** + * Calculate Levenshtein distance between two strings + */ + levenshteinDistance(str1, str2) { + const matrix = []; + + for (let i = 0; i <= str2.length; i++) { + matrix[i] = [i]; + } + + for (let j = 0; j <= str1.length; j++) { + matrix[0][j] = j; + } + + for (let i = 1; i <= str2.length; i++) { + for (let j = 1; j <= str1.length; j++) { + if (str2.charAt(i - 1) === str1.charAt(j - 1)) { + matrix[i][j] = matrix[i - 1][j - 1]; + } else { + matrix[i][j] = Math.min( + matrix[i - 1][j - 1] + 1, // substitution + matrix[i][j - 1] + 1, // insertion + matrix[i - 1][j] + 1 // deletion + ); + } + } + } + + return matrix[str2.length][str1.length]; + } + + /** + * Detect domain squatting using Levenshtein distance + */ + detectLevenshtein(testDomain, protectedDomain) { + const distance = this.levenshteinDistance(testDomain, protectedDomain); + + if (distance > 0 && distance <= this.deviationThreshold) { + return { + technique: 'levenshtein', + description: `Domain differs by ${distance} character(s) from protected domain`, + distance: distance, + confidence: 1 - (distance / this.deviationThreshold) + }; + } + + return null; + } + + /** + * Normalize domain by replacing homoglyphs with standard characters + */ + normalizeHomoglyphs(domain) { + let normalized = ''; + for (const char of domain) { + normalized += this.homoglyphReverse[char] || char; + } + return normalized; + } + + /** + * Detect homoglyph substitution + */ + detectHomoglyph(testDomain, protectedDomain) { + const normalized = this.normalizeHomoglyphs(testDomain); + + if (normalized !== testDomain && normalized === protectedDomain) { + return { + technique: 'homoglyph', + description: 'Domain uses confusable characters (homoglyphs) to mimic protected domain', + original: testDomain, + normalized: normalized, + confidence: 0.95 + }; + } + + // Also check if normalized version is close to protected domain + const distance = this.levenshteinDistance(normalized, protectedDomain); + if (normalized !== testDomain && distance > 0 && distance <= this.deviationThreshold) { + return { + technique: 'homoglyph', + description: 'Domain uses confusable characters and differs slightly from protected domain', + original: testDomain, + normalized: normalized, + distance: distance, + confidence: 0.85 - (distance * 0.1) + }; + } + + return null; + } + + /** + * Detect common typosquatting patterns + */ + detectTyposquat(testDomain, protectedDomain) { + // Check for character swaps (transposition) + for (let i = 0; i < protectedDomain.length - 1; i++) { + const swapped = protectedDomain.substring(0, i) + + protectedDomain.charAt(i + 1) + + protectedDomain.charAt(i) + + protectedDomain.substring(i + 2); + + if (swapped === testDomain) { + return { + technique: 'typosquat', + description: 'Domain has swapped adjacent characters', + pattern: 'character_swap', + position: i, + confidence: 0.9 + }; + } + } + + // Check for missing character + for (let i = 0; i < protectedDomain.length; i++) { + const missing = protectedDomain.substring(0, i) + protectedDomain.substring(i + 1); + + if (missing === testDomain) { + return { + technique: 'typosquat', + description: 'Domain is missing a character', + pattern: 'character_omission', + position: i, + confidence: 0.85 + }; + } + } + + // Check for duplicate character + for (let i = 0; i < protectedDomain.length; i++) { + const duplicated = protectedDomain.substring(0, i + 1) + + protectedDomain.charAt(i) + + protectedDomain.substring(i + 1); + + if (duplicated === testDomain) { + return { + technique: 'typosquat', + description: 'Domain has a duplicated character', + pattern: 'character_duplication', + position: i, + confidence: 0.85 + }; + } + } + + // Check for adjacent key substitution (common keyboard mistakes) + const keyboardAdjacent = { + 'q': ['w', 'a'], + 'w': ['q', 'e', 's', 'a'], + 'e': ['w', 'r', 'd', 's'], + 'r': ['e', 't', 'f', 'd'], + 't': ['r', 'y', 'g', 'f'], + 'y': ['t', 'u', 'h', 'g'], + 'u': ['y', 'i', 'j', 'h'], + 'i': ['u', 'o', 'k', 'j'], + 'o': ['i', 'p', 'l', 'k'], + 'p': ['o', 'l'], + 'a': ['q', 'w', 's', 'z'], + 's': ['a', 'w', 'd', 'x', 'z'], + 'd': ['s', 'e', 'f', 'c', 'x'], + 'f': ['d', 'r', 'g', 'v', 'c'], + 'g': ['f', 't', 'h', 'b', 'v'], + 'h': ['g', 'y', 'j', 'n', 'b'], + 'j': ['h', 'u', 'k', 'm', 'n'], + 'k': ['j', 'i', 'l', 'm'], + 'l': ['k', 'o', 'p'], + 'z': ['a', 's', 'x'], + 'x': ['z', 's', 'd', 'c'], + 'c': ['x', 'd', 'f', 'v'], + 'v': ['c', 'f', 'g', 'b'], + 'b': ['v', 'g', 'h', 'n'], + 'n': ['b', 'h', 'j', 'm'], + 'm': ['n', 'j', 'k'] + }; + + for (let i = 0; i < protectedDomain.length; i++) { + const char = protectedDomain.charAt(i); + const adjacentKeys = keyboardAdjacent[char] || []; + + for (const adjacentKey of adjacentKeys) { + const substituted = protectedDomain.substring(0, i) + + adjacentKey + + protectedDomain.substring(i + 1); + + if (substituted === testDomain) { + return { + technique: 'typosquat', + description: 'Domain has keyboard-adjacent character substitution', + pattern: 'adjacent_key_substitution', + position: i, + original: char, + substituted: adjacentKey, + confidence: 0.8 + }; + } + } + } + + return null; + } + + /** + * Detect combosquatting (adding prefixes/suffixes) + */ + detectCombosquat(testDomain, protectedDomain) { + // Check if protected domain is contained in test domain + if (testDomain.includes(protectedDomain) && testDomain !== protectedDomain) { + const prefix = testDomain.substring(0, testDomain.indexOf(protectedDomain)); + const suffix = testDomain.substring(testDomain.indexOf(protectedDomain) + protectedDomain.length); + + // Common combosquatting prefixes and suffixes + const commonCombos = [ + 'secure', 'login', 'account', 'verify', 'support', 'help', 'my', + 'auth', 'sso', 'signin', 'app', 'portal', 'online', 'web', + 'mobile', 'service', 'official', 'verified', 'safe' + ]; + + const hasCommonCombo = commonCombos.some(combo => + prefix.includes(combo) || suffix.includes(combo) + ); + + if (hasCommonCombo) { + return { + technique: 'combosquat', + description: 'Domain adds suspicious prefix/suffix to protected domain', + pattern: 'common_combo', + prefix: prefix, + suffix: suffix, + confidence: 0.9 + }; + } + + // Any prefix/suffix is suspicious but lower confidence + if (prefix || suffix) { + return { + technique: 'combosquat', + description: 'Domain adds prefix/suffix to protected domain', + pattern: 'generic_combo', + prefix: prefix, + suffix: suffix, + confidence: 0.7 + }; + } + } + + // Check if test domain contains protected domain with separator + const separators = ['-', '_', '']; + for (const sep of separators) { + if (testDomain.startsWith(protectedDomain + sep) || testDomain.endsWith(sep + protectedDomain)) { + return { + technique: 'combosquat', + description: `Domain adds text with separator '${sep || '(none)'}' to protected domain`, + pattern: 'separator_combo', + separator: sep, + confidence: 0.75 + }; + } + } + + return null; + } + + /** + * Calculate overall severity based on detections + */ + calculateSeverity(detections) { + const maxConfidence = Math.max(...detections.map(d => d.confidence)); + + if (maxConfidence >= 0.9) return 'critical'; + if (maxConfidence >= 0.8) return 'high'; + if (maxConfidence >= 0.6) return 'medium'; + return 'low'; + } + + /** + * Calculate overall confidence based on detections + */ + calculateConfidence(detections) { + if (detections.length === 0) return 0; + + // If multiple techniques detected, increase confidence + const avgConfidence = detections.reduce((sum, d) => sum + d.confidence, 0) / detections.length; + const multiTechniqueBonus = detections.length > 1 ? 0.1 : 0; + + return Math.min(0.99, avgConfidence + multiTechniqueBonus); + } +} + +export default DomainSquattingDetector;