Created
November 4, 2024 23:30
-
-
Save greenido/cab41285ac0284c4ff7c89b6faa4ad71 to your computer and use it in GitHub Desktop.
Chrome extension example: suspicious redirects, listen to URL event and block ones who exhibit rapid switching and suspicious URL or DNS patterns
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// manifest.json | |
{ | |
"manifest_version": 3, | |
"name": "Redirect Detective", | |
"version": "1.0", | |
"description": "Detects and analyzes potentially malicious redirects", | |
"permissions": [ | |
"webNavigation", | |
"webRequest", | |
"storage", | |
"tabs", | |
"scripting" | |
], | |
"host_permissions": [ | |
"<all_urls>" | |
], | |
"background": { | |
"service_worker": "background.js" | |
}, | |
"action": { | |
"default_popup": "popup.html" | |
}, | |
"content_scripts": [ | |
{ | |
"matches": ["<all_urls>"], | |
"js": ["content.js"], | |
"run_at": "document_start" | |
} | |
] | |
} | |
// background.js | |
const SUSPICIOUS_PATTERNS = { | |
domains: [ | |
/^(?:\d{1,3}\.){3}\d{1,3}$/, // IP addresses | |
/^[a-z0-9-]{1,63}\.[a-z0-9-]{1,63}$/, // Short domains | |
/[0-9]{5,}/, // Domains with many numbers | |
/[a-z0-9]{16,}/ // Very long strings | |
], | |
urlPatterns: [ | |
/\.(php|cgi)\?/i, | |
/redirect|forward|goto|click/i, | |
/track|stats|metrics/i, | |
/ad\.|ads\.|adserv/i | |
], | |
parameters: [ | |
'redirect_to', | |
'return_url', | |
'goto', | |
'next', | |
'target' | |
] | |
}; | |
class RedirectAnalyzer { | |
constructor() { | |
this.redirectChains = new Map(); | |
this.initializeListeners(); | |
} | |
initializeListeners() { | |
// Monitor web navigation events | |
chrome.webNavigation.onBeforeNavigate.addListener( | |
details => this.handleNavigation(details) | |
); | |
chrome.webNavigation.onCompleted.addListener( | |
details => this.analyzeRedirectChain(details) | |
); | |
// Monitor web requests | |
chrome.webRequest.onBeforeRedirect.addListener( | |
details => this.handleRedirect(details), | |
{ urls: ["<all_urls>"] } | |
); | |
} | |
handleNavigation(details) { | |
if (details.frameId === 0) { // Main frame only | |
if (!this.redirectChains.has(details.tabId)) { | |
this.redirectChains.set(details.tabId, []); | |
} | |
this.redirectChains.get(details.tabId).push({ | |
url: details.url, | |
timestamp: Date.now(), | |
type: 'navigation' | |
}); | |
} | |
} | |
handleRedirect(details) { | |
if (details.frameId === 0) { | |
const chain = this.redirectChains.get(details.tabId) || []; | |
chain.push({ | |
from: details.url, | |
to: details.redirectUrl, | |
timestamp: Date.now(), | |
type: 'redirect' | |
}); | |
this.redirectChains.set(details.tabId, chain); | |
} | |
} | |
analyzeRedirectChain(details) { | |
const chain = this.redirectChains.get(details.tabId); | |
if (!chain) return; | |
const analysis = { | |
chainLength: chain.length, | |
suspiciousPatterns: [], | |
riskScore: 0, | |
timestamps: [] | |
}; | |
// Analyze each redirect in the chain | |
chain.forEach((redirect, index) => { | |
const url = redirect.url || redirect.to; | |
// Check for suspicious patterns | |
this.checkSuspiciousPatterns(url, analysis); | |
// Check redirect timing | |
if (index > 0) { | |
const timeDiff = redirect.timestamp - chain[index - 1].timestamp; | |
if (timeDiff < 100) { // Suspicious if redirects happen too quickly | |
analysis.suspiciousPatterns.push('rapid_redirect'); | |
analysis.riskScore += 2; | |
} | |
} | |
}); | |
// Alert if suspicious activity detected | |
if (analysis.riskScore > 5) { | |
this.alertSuspiciousRedirect(details.tabId, analysis, chain); | |
} | |
// Clear the chain | |
this.redirectChains.delete(details.tabId); | |
} | |
checkSuspiciousPatterns(url, analysis) { | |
try { | |
const urlObj = new URL(url); | |
// Check domain patterns | |
SUSPICIOUS_PATTERNS.domains.forEach(pattern => { | |
if (pattern.test(urlObj.hostname)) { | |
analysis.suspiciousPatterns.push(`suspicious_domain:${pattern}`); | |
analysis.riskScore += 2; | |
} | |
}); | |
// Check URL patterns | |
SUSPICIOUS_PATTERNS.urlPatterns.forEach(pattern => { | |
if (pattern.test(url)) { | |
analysis.suspiciousPatterns.push(`suspicious_url_pattern:${pattern}`); | |
analysis.riskScore += 1; | |
} | |
}); | |
// Check for suspicious parameters | |
SUSPICIOUS_PATTERNS.parameters.forEach(param => { | |
if (urlObj.searchParams.has(param)) { | |
analysis.suspiciousPatterns.push(`suspicious_parameter:${param}`); | |
analysis.riskScore += 1; | |
} | |
}); | |
// Additional checks | |
if (urlObj.protocol === 'http:') { | |
analysis.suspiciousPatterns.push('non_https'); | |
analysis.riskScore += 1; | |
} | |
if (urlObj.username || urlObj.password) { | |
analysis.suspiciousPatterns.push('credentials_in_url'); | |
analysis.riskScore += 3; | |
} | |
} catch (e) { | |
analysis.suspiciousPatterns.push('invalid_url'); | |
analysis.riskScore += 2; | |
} | |
} | |
alertSuspiciousRedirect(tabId, analysis, chain) { | |
// Store the analysis | |
chrome.storage.local.get('redirectAlerts', (data) => { | |
const alerts = data.redirectAlerts || []; | |
alerts.push({ | |
timestamp: Date.now(), | |
analysis, | |
chain, | |
tabId | |
}); | |
chrome.storage.local.set({ redirectAlerts: alerts }); | |
}); | |
// Update extension badge | |
chrome.action.setBadgeText({ | |
text: '!', | |
tabId | |
}); | |
chrome.action.setBadgeBackgroundColor({ | |
color: '#FF0000', | |
tabId | |
}); | |
} | |
} | |
// Initialize the analyzer | |
const analyzer = new RedirectAnalyzer(); | |
// popup.html | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<style> | |
body { | |
width: 400px; | |
padding: 10px; | |
font-family: Arial, sans-serif; | |
} | |
.alert { | |
border: 1px solid #ccc; | |
margin: 10px 0; | |
padding: 10px; | |
background: #f9f9f9; | |
} | |
.high-risk { | |
background: #ffe6e6; | |
} | |
.details { | |
margin-top: 5px; | |
font-size: 0.9em; | |
} | |
</style> | |
</head> | |
<body> | |
<h2>Redirect Detective</h2> | |
<div id="alerts"></div> | |
<script src="popup.js"></script> | |
</body> | |
</html> | |
// popup.js | |
document.addEventListener('DOMContentLoaded', () => { | |
chrome.storage.local.get('redirectAlerts', (data) => { | |
const alerts = data.redirectAlerts || []; | |
const alertsDiv = document.getElementById('alerts'); | |
if (alerts.length === 0) { | |
alertsDiv.innerHTML = '<p>No suspicious redirects detected.</p>'; | |
return; | |
} | |
alerts.reverse().forEach(alert => { | |
const alertElement = document.createElement('div'); | |
alertElement.className = `alert ${alert.analysis.riskScore > 7 ? 'high-risk' : ''}`; | |
alertElement.innerHTML = ` | |
<strong>Risk Score: ${alert.analysis.riskScore}</strong> | |
<div class="details"> | |
<p>Suspicious Patterns:</p> | |
<ul> | |
${alert.analysis.suspiciousPatterns.map(p => `<li>${p}</li>`).join('')} | |
</ul> | |
<p>Redirect Chain:</p> | |
<ul> | |
${alert.chain.map(r => `<li>${r.url || r.from + ' → ' + r.to}</li>`).join('')} | |
</ul> | |
<small>Detected: ${new Date(alert.timestamp).toLocaleString()}</small> | |
</div> | |
`; | |
alertsDiv.appendChild(alertElement); | |
}); | |
}); | |
}); | |
// content.js | |
// Monitor and report client-side redirects | |
(function() { | |
const originalAssign = window.location.assign; | |
const originalReplace = window.location.replace; | |
const originalHref = Object.getOwnPropertyDescriptor(window.location, 'href'); | |
// Monitor location.assign | |
window.location.assign = function(url) { | |
chrome.runtime.sendMessage({ | |
type: 'client_redirect', | |
from: window.location.href, | |
to: url, | |
method: 'assign' | |
}); | |
return originalAssign.apply(this, arguments); | |
}; | |
// Monitor location.replace | |
window.location.replace = function(url) { | |
chrome.runtime.sendMessage({ | |
type: 'client_redirect', | |
from: window.location.href, | |
to: url, | |
method: 'replace' | |
}); | |
return originalReplace.apply(this, arguments); | |
}; | |
// Monitor location.href | |
Object.defineProperty(window.location, 'href', { | |
set: function(url) { | |
chrome.runtime.sendMessage({ | |
type: 'client_redirect', | |
from: window.location.href, | |
to: url, | |
method: 'href' | |
}); | |
return originalHref.set.call(this, url); | |
}, | |
get: originalHref.get | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment