← back to Watches
security/intrusion-detection.js
420 lines
/**
* INTRUSION DETECTION SYSTEM (IDS)
* Real-time threat detection and automated response
*
* Features:
* - Behavioral analysis
* - Pattern recognition
* - Automated blocking
* - Alert generation
* - Forensic logging
*/
import { securityLogger } from '../middleware/security.js';
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
class IntrusionDetectionSystem {
constructor() {
this.suspiciousIPs = new Map();
this.blockedIPs = new Set();
this.attackPatterns = this.initializeAttackPatterns();
this.thresholds = {
maxFailedAuth: 5,
maxRequestsPerMinute: 60,
maxValidationErrors: 10,
suspiciousScore: 50
};
this.alertCallbacks = [];
}
/**
* Initialize known attack patterns
*/
initializeAttackPatterns() {
return {
sqlInjection: [
/(\bunion\b.*\bselect\b)/i,
/(\bdrop\b.*\btable\b)/i,
/(\bselect\b.*\bfrom\b.*\bwhere\b)/i,
/(--|;|\/\*|\*\/)/,
/(\bexec\b|\bexecute\b)/i,
/(\binsert\b.*\binto\b)/i,
/(\bdelete\b.*\bfrom\b)/i
],
xss: [
/<script\b[^>]*>/i,
/javascript:/i,
/on\w+\s*=/i,
/<iframe\b[^>]*>/i,
/onerror\s*=/i,
/onload\s*=/i
],
pathTraversal: [
/\.\.\//,
/\.\.\\\/,
/%2e%2e%2f/i,
/%252e%252e%252f/i
],
commandInjection: [
/[;&|`$()]/,
/\$\{.*\}/,
/\$\(.*\)/
],
enumeration: [
/\/admin/i,
/\/backup/i,
/\/config/i,
/\.env/i,
/\/\.git/i,
/\/\.svn/i
]
};
}
/**
* Analyze incoming request for threats
*/
analyzeRequest(req) {
const ip = this.getClientIP(req);
const suspiciousScore = this.calculateSuspiciousScore(req);
// Track IP behavior
this.trackIPBehavior(ip, req);
// Check if already blocked
if (this.blockedIPs.has(ip)) {
this.logThreat('BLOCKED_IP_ATTEMPT', ip, req, 'CRITICAL');
return { blocked: true, reason: 'IP is blocked' };
}
// Check suspicious score
if (suspiciousScore >= this.thresholds.suspiciousScore) {
this.handleSuspiciousActivity(ip, req, suspiciousScore);
return { blocked: true, reason: 'Suspicious activity detected' };
}
return { blocked: false, score: suspiciousScore };
}
/**
* Calculate suspicious score for request
*/
calculateSuspiciousScore(req) {
let score = 0;
// Check for attack patterns in URL
const fullUrl = req.originalUrl || req.url;
score += this.checkAttackPatterns(fullUrl) * 20;
// Check for attack patterns in query parameters
if (req.query) {
Object.values(req.query).forEach(value => {
if (typeof value === 'string') {
score += this.checkAttackPatterns(value) * 15;
}
});
}
// Check for attack patterns in body
if (req.body) {
const bodyStr = JSON.stringify(req.body);
score += this.checkAttackPatterns(bodyStr) * 15;
}
// Check user agent
const userAgent = req.get('user-agent') || '';
if (this.isSuspiciousUserAgent(userAgent)) {
score += 10;
}
// Check for missing common headers
if (!req.get('accept') || !req.get('accept-language')) {
score += 5;
}
// Check request method patterns
if (['TRACE', 'TRACK', 'OPTIONS'].includes(req.method)) {
score += 5;
}
return Math.min(score, 100);
}
/**
* Check text against attack patterns
*/
checkAttackPatterns(text) {
let matches = 0;
Object.values(this.attackPatterns).forEach(patterns => {
patterns.forEach(pattern => {
if (pattern.test(text)) {
matches++;
}
});
});
return matches;
}
/**
* Check if user agent is suspicious
*/
isSuspiciousUserAgent(userAgent) {
const suspiciousPatterns = [
/bot|crawler|spider|scraper/i,
/nikto|nmap|sqlmap|havij/i,
/masscan|zap|burp/i,
/python-requests|curl|wget/i
];
return suspiciousPatterns.some(pattern => pattern.test(userAgent));
}
/**
* Track IP behavior over time
*/
trackIPBehavior(ip, req) {
if (!this.suspiciousIPs.has(ip)) {
this.suspiciousIPs.set(ip, {
requests: [],
failedAuth: 0,
validationErrors: 0,
firstSeen: Date.now(),
score: 0
});
}
const ipData = this.suspiciousIPs.get(ip);
const now = Date.now();
// Track request
ipData.requests.push(now);
// Clean old requests (older than 1 minute)
ipData.requests = ipData.requests.filter(time => now - time < 60000);
// Check request rate
if (ipData.requests.length > this.thresholds.maxRequestsPerMinute) {
ipData.score += 20;
this.logThreat('RATE_ANOMALY', ip, req, 'WARNING');
}
// Clean up old IP data (older than 1 hour)
if (now - ipData.firstSeen > 3600000) {
this.suspiciousIPs.delete(ip);
}
}
/**
* Handle suspicious activity
*/
handleSuspiciousActivity(ip, req, score) {
const ipData = this.suspiciousIPs.get(ip);
if (ipData) {
ipData.score = score;
// Auto-block if score exceeds threshold
if (score >= 80) {
this.blockIP(ip, 'Automated block - high threat score', 3600000); // 1 hour
this.logThreat('AUTO_BLOCKED', ip, req, 'CRITICAL');
this.triggerAlert('CRITICAL', `IP ${ip} auto-blocked - threat score: ${score}`);
} else if (score >= 50) {
this.logThreat('SUSPICIOUS_ACTIVITY', ip, req, 'HIGH');
this.triggerAlert('HIGH', `Suspicious activity from IP ${ip} - score: ${score}`);
}
}
}
/**
* Block an IP address
*/
blockIP(ip, reason, duration = 3600000) {
this.blockedIPs.add(ip);
securityLogger.log({
type: 'IP_BLOCKED',
severity: 'CRITICAL',
ip: ip,
reason: reason,
duration: duration
});
// Auto-unblock after duration
if (duration > 0) {
setTimeout(() => {
this.unblockIP(ip);
}, duration);
}
}
/**
* Unblock an IP address
*/
unblockIP(ip) {
this.blockedIPs.delete(ip);
securityLogger.log({
type: 'IP_UNBLOCKED',
severity: 'INFO',
ip: ip
});
}
/**
* Record failed authentication attempt
*/
recordFailedAuth(ip, req) {
if (!this.suspiciousIPs.has(ip)) {
this.trackIPBehavior(ip, req);
}
const ipData = this.suspiciousIPs.get(ip);
ipData.failedAuth++;
ipData.score += 15;
if (ipData.failedAuth >= this.thresholds.maxFailedAuth) {
this.blockIP(ip, 'Too many failed authentication attempts', 900000); // 15 minutes
this.logThreat('BRUTE_FORCE_DETECTED', ip, req, 'CRITICAL');
this.triggerAlert('CRITICAL', `Brute force attack detected from IP ${ip}`);
}
}
/**
* Record validation error
*/
recordValidationError(ip, req) {
if (!this.suspiciousIPs.has(ip)) {
this.trackIPBehavior(ip, req);
}
const ipData = this.suspiciousIPs.get(ip);
ipData.validationErrors++;
ipData.score += 5;
if (ipData.validationErrors >= this.thresholds.maxValidationErrors) {
this.logThreat('VALIDATION_ATTACK', ip, req, 'HIGH');
}
}
/**
* Log security threat
*/
logThreat(type, ip, req, severity) {
securityLogger.log({
type: type,
severity: severity,
ip: ip,
path: req.path,
method: req.method,
userAgent: req.get('user-agent'),
timestamp: new Date().toISOString()
});
}
/**
* Get client IP address
*/
getClientIP(req) {
return req.ip ||
req.headers['x-forwarded-for']?.split(',')[0] ||
req.connection?.remoteAddress ||
'unknown';
}
/**
* Register alert callback
*/
onAlert(callback) {
this.alertCallbacks.push(callback);
}
/**
* Trigger alert
*/
triggerAlert(severity, message) {
this.alertCallbacks.forEach(callback => {
try {
callback({ severity, message, timestamp: new Date().toISOString() });
} catch (error) {
console.error('Alert callback error:', error);
}
});
}
/**
* Get system status
*/
getStatus() {
const now = Date.now();
const recentThreats = Array.from(this.suspiciousIPs.entries())
.filter(([ip, data]) => data.score > 30)
.map(([ip, data]) => ({
ip,
score: data.score,
requests: data.requests.length,
failedAuth: data.failedAuth,
validationErrors: data.validationErrors
}));
return {
blockedIPs: Array.from(this.blockedIPs),
monitoredIPs: this.suspiciousIPs.size,
recentThreats: recentThreats,
totalBlocked: this.blockedIPs.size,
timestamp: new Date().toISOString()
};
}
/**
* Export forensic data
*/
async exportForensicData() {
const data = {
timestamp: new Date().toISOString(),
blockedIPs: Array.from(this.blockedIPs),
suspiciousActivity: Array.from(this.suspiciousIPs.entries()).map(([ip, data]) => ({
ip,
...data,
requests: data.requests.length
})),
securityLogs: securityLogger.getRecentEvents(1000)
};
const filename = `forensic-${Date.now()}.json`;
const filepath = path.join(__dirname, '..', 'logs', filename);
await fs.mkdir(path.join(__dirname, '..', 'logs'), { recursive: true });
await fs.writeFile(filepath, JSON.stringify(data, null, 2));
return filepath;
}
}
// Express middleware
export const idsMiddleware = (ids) => {
return (req, res, next) => {
const analysis = ids.analyzeRequest(req);
if (analysis.blocked) {
return res.status(403).json({
error: 'Forbidden',
message: 'Request blocked by intrusion detection system',
reason: analysis.reason
});
}
// Attach IDS analysis to request for logging
req.idsAnalysis = analysis;
next();
};
};
export default IntrusionDetectionSystem;