Skip to content

Instantly share code, notes, and snippets.

@greenido
Created November 4, 2024 23:30
Show Gist options
  • Save greenido/cab41285ac0284c4ff7c89b6faa4ad71 to your computer and use it in GitHub Desktop.
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
// 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