854 lines
26 KiB
JavaScript
854 lines
26 KiB
JavaScript
|
|
/**
|
||
|
|
* Security Intelligence & Pattern Analysis
|
||
|
|
* Algorithm-driven surveillance system for automatic pattern detection
|
||
|
|
* Includes anomaly detection, threat intelligence, and predictive analysis
|
||
|
|
* Enhanced with configurable thresholds and risk signatures (CWE-778)
|
||
|
|
*/
|
||
|
|
|
||
|
|
const logger = require('./logger');
|
||
|
|
const logAggregator = require('./logAggregator');
|
||
|
|
const { db } = require('../database/db');
|
||
|
|
const thresholdManager = require('./thresholdManager');
|
||
|
|
const riskSignatureManager = require('./riskSignatureManager');
|
||
|
|
|
||
|
|
class SecurityIntelligence {
|
||
|
|
constructor() {
|
||
|
|
this.patterns = new Map();
|
||
|
|
this.anomalies = [];
|
||
|
|
this.threatScore = 0;
|
||
|
|
this.analysisInterval = 60000; // 1 minute
|
||
|
|
this.initialize();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Initialize security intelligence system
|
||
|
|
*/
|
||
|
|
async initialize() {
|
||
|
|
await this.createAnomaliesTable();
|
||
|
|
await this.createThreatIntelligenceTable();
|
||
|
|
|
||
|
|
// Start continuous monitoring
|
||
|
|
setInterval(() => this.analyze(), this.analysisInterval);
|
||
|
|
|
||
|
|
logger.info('[SecurityIntelligence] Initialized - Active monitoring enabled');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create anomalies table
|
||
|
|
*/
|
||
|
|
async createAnomaliesTable() {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.run(`
|
||
|
|
CREATE TABLE IF NOT EXISTS security_anomalies (
|
||
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
|
|
anomaly_id TEXT UNIQUE NOT NULL,
|
||
|
|
type TEXT NOT NULL,
|
||
|
|
severity TEXT NOT NULL,
|
||
|
|
description TEXT NOT NULL,
|
||
|
|
confidence REAL NOT NULL,
|
||
|
|
affected_user_id INTEGER,
|
||
|
|
affected_ip TEXT,
|
||
|
|
pattern_data TEXT,
|
||
|
|
related_logs TEXT,
|
||
|
|
status TEXT DEFAULT 'open',
|
||
|
|
resolved_at DATETIME,
|
||
|
|
resolved_by INTEGER,
|
||
|
|
resolution_notes TEXT,
|
||
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
|
|
)
|
||
|
|
`, (err) => {
|
||
|
|
if (err) reject(err);
|
||
|
|
else {
|
||
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_anomalies_type ON security_anomalies(type, created_at DESC)`);
|
||
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_anomalies_severity ON security_anomalies(severity, created_at DESC)`);
|
||
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_anomalies_status ON security_anomalies(status, created_at DESC)`);
|
||
|
|
resolve();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create threat intelligence table
|
||
|
|
*/
|
||
|
|
async createThreatIntelligenceTable() {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.run(`
|
||
|
|
CREATE TABLE IF NOT EXISTS threat_intelligence (
|
||
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
|
|
indicator TEXT NOT NULL,
|
||
|
|
indicator_type TEXT NOT NULL,
|
||
|
|
threat_level TEXT NOT NULL,
|
||
|
|
description TEXT,
|
||
|
|
source TEXT,
|
||
|
|
confidence REAL NOT NULL,
|
||
|
|
first_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
|
|
last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
|
|
occurrence_count INTEGER DEFAULT 1,
|
||
|
|
metadata TEXT
|
||
|
|
)
|
||
|
|
`, (err) => {
|
||
|
|
if (err) reject(err);
|
||
|
|
else {
|
||
|
|
db.run(`CREATE UNIQUE INDEX IF NOT EXISTS idx_threat_indicator ON threat_intelligence(indicator, indicator_type)`);
|
||
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_threat_level ON threat_intelligence(threat_level, last_seen DESC)`);
|
||
|
|
resolve();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Main analysis loop - runs continuously
|
||
|
|
*/
|
||
|
|
async analyze() {
|
||
|
|
try {
|
||
|
|
logger.debug('[SecurityIntelligence] Running analysis cycle');
|
||
|
|
|
||
|
|
// Run all detection algorithms in parallel
|
||
|
|
await Promise.all([
|
||
|
|
this.detectBruteForceAttacks(),
|
||
|
|
this.detectAccountEnumeration(),
|
||
|
|
this.detectPrivilegeEscalation(),
|
||
|
|
this.detectAnomalousAccess(),
|
||
|
|
this.detectSuspiciousIPs(),
|
||
|
|
this.detectDataExfiltration(),
|
||
|
|
this.detectSessionAnomalies(),
|
||
|
|
this.detectRateLimitAbuse()
|
||
|
|
]);
|
||
|
|
|
||
|
|
// Calculate overall threat score
|
||
|
|
await this.calculateThreatScore();
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
logger.error('[SecurityIntelligence] Analysis cycle failed:', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Detect brute force authentication attacks
|
||
|
|
* Enhanced with configurable thresholds
|
||
|
|
*/
|
||
|
|
async detectBruteForceAttacks() {
|
||
|
|
// Get configured threshold or use default
|
||
|
|
const thresholdConfig = await thresholdManager.getThresholds({ patternType: 'brute_force_attack' });
|
||
|
|
const timeWindow = thresholdConfig[0]?.time_window_minutes || 10;
|
||
|
|
const threshold = thresholdConfig[0]?.threshold_value || 10;
|
||
|
|
const startTime = new Date(Date.now() - timeWindow * 60 * 1000).toISOString();
|
||
|
|
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.all(
|
||
|
|
`SELECT ip_address, COUNT(*) as attempt_count,
|
||
|
|
MAX(timestamp) as last_attempt,
|
||
|
|
GROUP_CONCAT(log_id) as log_ids
|
||
|
|
FROM aggregated_logs
|
||
|
|
WHERE category = 'authentication'
|
||
|
|
AND level IN ('warn', 'error')
|
||
|
|
AND message LIKE '%failed%'
|
||
|
|
AND timestamp >= ?
|
||
|
|
AND ip_address IS NOT NULL
|
||
|
|
GROUP BY ip_address
|
||
|
|
HAVING attempt_count >= ?`,
|
||
|
|
[startTime, threshold],
|
||
|
|
async (err, rows) => {
|
||
|
|
if (err) {
|
||
|
|
reject(err);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const row of rows) {
|
||
|
|
// Evaluate threshold
|
||
|
|
const thresholdResult = await thresholdManager.evaluateThreshold(
|
||
|
|
'brute_force_attack',
|
||
|
|
'failed_login_count',
|
||
|
|
row.attempt_count,
|
||
|
|
{ ip_address: row.ip_address, timeWindow }
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!thresholdResult.exceeded) continue;
|
||
|
|
|
||
|
|
const configuredThreshold = thresholdResult.thresholds[0];
|
||
|
|
const severity = configuredThreshold?.severity || (row.attempt_count > 20 ? 'critical' : 'high');
|
||
|
|
|
||
|
|
await this.createAnomaly({
|
||
|
|
type: 'brute_force_attack',
|
||
|
|
severity: severity,
|
||
|
|
description: `Brute force attack detected from IP ${row.ip_address}: ${row.attempt_count} failed login attempts in ${timeWindow} minutes (threshold: ${threshold})`,
|
||
|
|
confidence: Math.min(row.attempt_count / threshold, 1.0),
|
||
|
|
affected_ip: row.ip_address,
|
||
|
|
pattern_data: JSON.stringify({
|
||
|
|
attemptCount: row.attempt_count,
|
||
|
|
timeWindow: `${timeWindow} minutes`,
|
||
|
|
threshold: threshold,
|
||
|
|
thresholdExceeded: thresholdResult.exceeded,
|
||
|
|
lastAttempt: row.last_attempt
|
||
|
|
}),
|
||
|
|
related_logs: row.log_ids
|
||
|
|
});
|
||
|
|
|
||
|
|
// Add to threat intelligence
|
||
|
|
await this.addThreatIndicator(row.ip_address, 'ip', severity === 'critical' ? 'critical' : 'high', 'Brute force attack source');
|
||
|
|
}
|
||
|
|
|
||
|
|
resolve(rows.length);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Detect account enumeration attempts
|
||
|
|
* Enhanced with configurable thresholds
|
||
|
|
*/
|
||
|
|
async detectAccountEnumeration() {
|
||
|
|
// Get configured threshold or use default
|
||
|
|
const thresholdConfig = await thresholdManager.getThresholds({ patternType: 'credential_stuffing' });
|
||
|
|
const timeWindow = thresholdConfig[0]?.time_window_minutes || 5;
|
||
|
|
const threshold = thresholdConfig[0]?.threshold_value || 5;
|
||
|
|
const startTime = new Date(Date.now() - timeWindow * 60 * 1000).toISOString();
|
||
|
|
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.all(
|
||
|
|
`SELECT ip_address,
|
||
|
|
COUNT(DISTINCT json_extract(metadata, '$.username')) as unique_usernames,
|
||
|
|
COUNT(*) as total_attempts,
|
||
|
|
GROUP_CONCAT(log_id) as log_ids
|
||
|
|
FROM aggregated_logs
|
||
|
|
WHERE category = 'authentication'
|
||
|
|
AND level = 'warn'
|
||
|
|
AND timestamp >= ?
|
||
|
|
AND ip_address IS NOT NULL
|
||
|
|
AND metadata LIKE '%username%'
|
||
|
|
GROUP BY ip_address
|
||
|
|
HAVING unique_usernames >= ?`,
|
||
|
|
[startTime, threshold],
|
||
|
|
async (err, rows) => {
|
||
|
|
if (err) {
|
||
|
|
reject(err);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const row of rows) {
|
||
|
|
await this.createAnomaly({
|
||
|
|
type: 'account_enumeration',
|
||
|
|
severity: 'medium',
|
||
|
|
description: `Account enumeration detected from IP ${row.ip_address}: ${row.unique_usernames} different usernames tried in ${timeWindow} minutes`,
|
||
|
|
confidence: Math.min(row.unique_usernames / (threshold * 2), 1.0),
|
||
|
|
affected_ip: row.ip_address,
|
||
|
|
pattern_data: JSON.stringify({
|
||
|
|
uniqueUsernames: row.unique_usernames,
|
||
|
|
totalAttempts: row.total_attempts,
|
||
|
|
timeWindow: `${timeWindow} minutes`
|
||
|
|
}),
|
||
|
|
related_logs: row.log_ids
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
resolve(rows.length);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Detect privilege escalation attempts
|
||
|
|
*/
|
||
|
|
async detectPrivilegeEscalation() {
|
||
|
|
const timeWindow = 30; // minutes
|
||
|
|
const startTime = new Date(Date.now() - timeWindow * 60 * 1000).toISOString();
|
||
|
|
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.all(
|
||
|
|
`SELECT user_id, ip_address,
|
||
|
|
COUNT(*) as escalation_attempts,
|
||
|
|
GROUP_CONCAT(log_id) as log_ids
|
||
|
|
FROM aggregated_logs
|
||
|
|
WHERE category = 'authorization'
|
||
|
|
AND (message LIKE '%denied%' OR message LIKE '%unauthorized%')
|
||
|
|
AND timestamp >= ?
|
||
|
|
AND user_id IS NOT NULL
|
||
|
|
GROUP BY user_id, ip_address
|
||
|
|
HAVING escalation_attempts >= 3`,
|
||
|
|
[startTime],
|
||
|
|
async (err, rows) => {
|
||
|
|
if (err) {
|
||
|
|
reject(err);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const row of rows) {
|
||
|
|
await this.createAnomaly({
|
||
|
|
type: 'privilege_escalation',
|
||
|
|
severity: 'critical',
|
||
|
|
description: `Privilege escalation attempt detected: User ${row.user_id} attempted ${row.escalation_attempts} unauthorized actions`,
|
||
|
|
confidence: 0.85,
|
||
|
|
affected_user_id: row.user_id,
|
||
|
|
affected_ip: row.ip_address,
|
||
|
|
pattern_data: JSON.stringify({
|
||
|
|
escalationAttempts: row.escalation_attempts,
|
||
|
|
timeWindow: `${timeWindow} minutes`
|
||
|
|
}),
|
||
|
|
related_logs: row.log_ids
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
resolve(rows.length);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Detect anomalous access patterns
|
||
|
|
*/
|
||
|
|
async detectAnomalousAccess() {
|
||
|
|
const timeWindow = 60; // minutes
|
||
|
|
const startTime = new Date(Date.now() - timeWindow * 60 * 1000).toISOString();
|
||
|
|
|
||
|
|
// Detect access from unusual hours (2 AM - 5 AM)
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.all(
|
||
|
|
`SELECT user_id, ip_address,
|
||
|
|
COUNT(*) as access_count,
|
||
|
|
GROUP_CONCAT(log_id) as log_ids
|
||
|
|
FROM aggregated_logs
|
||
|
|
WHERE category IN ('access', 'security_audit')
|
||
|
|
AND timestamp >= ?
|
||
|
|
AND CAST(strftime('%H', timestamp) AS INTEGER) BETWEEN 2 AND 5
|
||
|
|
AND user_id IS NOT NULL
|
||
|
|
GROUP BY user_id, ip_address
|
||
|
|
HAVING access_count >= 3`,
|
||
|
|
[startTime],
|
||
|
|
async (err, rows) => {
|
||
|
|
if (err) {
|
||
|
|
reject(err);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const row of rows) {
|
||
|
|
await this.createAnomaly({
|
||
|
|
type: 'anomalous_access',
|
||
|
|
severity: 'medium',
|
||
|
|
description: `Unusual access pattern: User ${row.user_id} accessed system during off-hours (${row.access_count} times)`,
|
||
|
|
confidence: 0.7,
|
||
|
|
affected_user_id: row.user_id,
|
||
|
|
affected_ip: row.ip_address,
|
||
|
|
pattern_data: JSON.stringify({
|
||
|
|
accessCount: row.access_count,
|
||
|
|
timeRange: '2 AM - 5 AM'
|
||
|
|
}),
|
||
|
|
related_logs: row.log_ids
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
resolve(rows.length);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Detect suspicious IP addresses
|
||
|
|
*/
|
||
|
|
async detectSuspiciousIPs() {
|
||
|
|
const timeWindow = 60; // minutes
|
||
|
|
const threshold = 100; // requests
|
||
|
|
const startTime = new Date(Date.now() - timeWindow * 60 * 1000).toISOString();
|
||
|
|
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.all(
|
||
|
|
`SELECT ip_address,
|
||
|
|
COUNT(*) as request_count,
|
||
|
|
COUNT(DISTINCT user_id) as unique_users,
|
||
|
|
COUNT(CASE WHEN level = 'error' THEN 1 END) as error_count,
|
||
|
|
GROUP_CONCAT(DISTINCT source) as sources
|
||
|
|
FROM aggregated_logs
|
||
|
|
WHERE timestamp >= ?
|
||
|
|
AND ip_address IS NOT NULL
|
||
|
|
GROUP BY ip_address
|
||
|
|
HAVING request_count >= ? OR unique_users >= 10`,
|
||
|
|
[startTime, threshold],
|
||
|
|
async (err, rows) => {
|
||
|
|
if (err) {
|
||
|
|
reject(err);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const row of rows) {
|
||
|
|
let severity = 'low';
|
||
|
|
let reason = [];
|
||
|
|
|
||
|
|
if (row.request_count >= threshold * 2) {
|
||
|
|
severity = 'high';
|
||
|
|
reason.push(`excessive requests (${row.request_count})`);
|
||
|
|
} else if (row.request_count >= threshold) {
|
||
|
|
severity = 'medium';
|
||
|
|
reason.push(`high request volume (${row.request_count})`);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (row.unique_users >= 10) {
|
||
|
|
severity = 'high';
|
||
|
|
reason.push(`multiple user accounts (${row.unique_users})`);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (row.error_count > row.request_count * 0.3) {
|
||
|
|
severity = 'high';
|
||
|
|
reason.push(`high error rate (${row.error_count})`);
|
||
|
|
}
|
||
|
|
|
||
|
|
await this.createAnomaly({
|
||
|
|
type: 'suspicious_ip',
|
||
|
|
severity,
|
||
|
|
description: `Suspicious IP activity from ${row.ip_address}: ${reason.join(', ')}`,
|
||
|
|
confidence: 0.75,
|
||
|
|
affected_ip: row.ip_address,
|
||
|
|
pattern_data: JSON.stringify({
|
||
|
|
requestCount: row.request_count,
|
||
|
|
uniqueUsers: row.unique_users,
|
||
|
|
errorCount: row.error_count,
|
||
|
|
sources: row.sources
|
||
|
|
}),
|
||
|
|
related_logs: null
|
||
|
|
});
|
||
|
|
|
||
|
|
if (severity === 'high') {
|
||
|
|
await this.addThreatIndicator(row.ip_address, 'ip', severity, reason.join(', '));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
resolve(rows.length);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Detect potential data exfiltration
|
||
|
|
*/
|
||
|
|
async detectDataExfiltration() {
|
||
|
|
const timeWindow = 30; // minutes
|
||
|
|
const downloadThreshold = 5; // Large downloads
|
||
|
|
const startTime = new Date(Date.now() - timeWindow * 60 * 1000).toISOString();
|
||
|
|
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.all(
|
||
|
|
`SELECT user_id, ip_address,
|
||
|
|
COUNT(*) as download_count,
|
||
|
|
GROUP_CONCAT(log_id) as log_ids
|
||
|
|
FROM aggregated_logs
|
||
|
|
WHERE category = 'access'
|
||
|
|
AND (message LIKE '%download%' OR message LIKE '%export%' OR message LIKE '%backup%')
|
||
|
|
AND timestamp >= ?
|
||
|
|
AND user_id IS NOT NULL
|
||
|
|
GROUP BY user_id, ip_address
|
||
|
|
HAVING download_count >= ?`,
|
||
|
|
[startTime, downloadThreshold],
|
||
|
|
async (err, rows) => {
|
||
|
|
if (err) {
|
||
|
|
reject(err);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const row of rows) {
|
||
|
|
await this.createAnomaly({
|
||
|
|
type: 'data_exfiltration',
|
||
|
|
severity: 'high',
|
||
|
|
description: `Potential data exfiltration: User ${row.user_id} performed ${row.download_count} download/export operations in ${timeWindow} minutes`,
|
||
|
|
confidence: 0.8,
|
||
|
|
affected_user_id: row.user_id,
|
||
|
|
affected_ip: row.ip_address,
|
||
|
|
pattern_data: JSON.stringify({
|
||
|
|
downloadCount: row.download_count,
|
||
|
|
timeWindow: `${timeWindow} minutes`
|
||
|
|
}),
|
||
|
|
related_logs: row.log_ids
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
resolve(rows.length);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Detect session anomalies
|
||
|
|
*/
|
||
|
|
async detectSessionAnomalies() {
|
||
|
|
const timeWindow = 24; // hours
|
||
|
|
const startTime = new Date(Date.now() - timeWindow * 60 * 60 * 1000).toISOString();
|
||
|
|
|
||
|
|
// Detect impossible travel (same user, different locations in short time)
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.all(
|
||
|
|
`SELECT user_id,
|
||
|
|
COUNT(DISTINCT ip_address) as unique_ips,
|
||
|
|
GROUP_CONCAT(DISTINCT ip_address) as ips,
|
||
|
|
COUNT(*) as session_count
|
||
|
|
FROM aggregated_logs
|
||
|
|
WHERE category = 'authentication'
|
||
|
|
AND message LIKE '%login%success%'
|
||
|
|
AND timestamp >= ?
|
||
|
|
AND user_id IS NOT NULL
|
||
|
|
GROUP BY user_id
|
||
|
|
HAVING unique_ips >= 5`,
|
||
|
|
[startTime],
|
||
|
|
async (err, rows) => {
|
||
|
|
if (err) {
|
||
|
|
reject(err);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const row of rows) {
|
||
|
|
await this.createAnomaly({
|
||
|
|
type: 'session_anomaly',
|
||
|
|
severity: 'medium',
|
||
|
|
description: `Session anomaly: User ${row.user_id} logged in from ${row.unique_ips} different IP addresses in ${timeWindow} hours`,
|
||
|
|
confidence: 0.7,
|
||
|
|
affected_user_id: row.user_id,
|
||
|
|
pattern_data: JSON.stringify({
|
||
|
|
uniqueIPs: row.unique_ips,
|
||
|
|
ipAddresses: row.ips.split(','),
|
||
|
|
sessionCount: row.session_count,
|
||
|
|
timeWindow: `${timeWindow} hours`
|
||
|
|
}),
|
||
|
|
related_logs: null
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
resolve(rows.length);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Detect rate limit abuse
|
||
|
|
*/
|
||
|
|
async detectRateLimitAbuse() {
|
||
|
|
const timeWindow = 15; // minutes
|
||
|
|
const startTime = new Date(Date.now() - timeWindow * 60 * 1000).toISOString();
|
||
|
|
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.all(
|
||
|
|
`SELECT ip_address,
|
||
|
|
COUNT(*) as blocked_count,
|
||
|
|
GROUP_CONCAT(log_id) as log_ids
|
||
|
|
FROM aggregated_logs
|
||
|
|
WHERE category = 'system'
|
||
|
|
AND message LIKE '%rate limit%'
|
||
|
|
AND timestamp >= ?
|
||
|
|
AND ip_address IS NOT NULL
|
||
|
|
GROUP BY ip_address
|
||
|
|
HAVING blocked_count >= 5`,
|
||
|
|
[startTime],
|
||
|
|
async (err, rows) => {
|
||
|
|
if (err) {
|
||
|
|
reject(err);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const row of rows) {
|
||
|
|
await this.createAnomaly({
|
||
|
|
type: 'rate_limit_abuse',
|
||
|
|
severity: 'medium',
|
||
|
|
description: `Rate limit abuse: IP ${row.ip_address} was rate-limited ${row.blocked_count} times in ${timeWindow} minutes`,
|
||
|
|
confidence: 0.9,
|
||
|
|
affected_ip: row.ip_address,
|
||
|
|
pattern_data: JSON.stringify({
|
||
|
|
blockedCount: row.blocked_count,
|
||
|
|
timeWindow: `${timeWindow} minutes`
|
||
|
|
}),
|
||
|
|
related_logs: row.log_ids
|
||
|
|
});
|
||
|
|
|
||
|
|
await this.addThreatIndicator(row.ip_address, 'ip', 'medium', 'Rate limit abuse');
|
||
|
|
}
|
||
|
|
|
||
|
|
resolve(rows.length);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create anomaly record
|
||
|
|
*/
|
||
|
|
async createAnomaly(details) {
|
||
|
|
const anomalyId = `ANOM-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||
|
|
|
||
|
|
// Check if similar anomaly exists (deduplication)
|
||
|
|
const existing = await this.findSimilarAnomaly(details);
|
||
|
|
if (existing) {
|
||
|
|
logger.debug(`[SecurityIntelligence] Similar anomaly exists: ${existing.anomaly_id}`);
|
||
|
|
return existing.anomaly_id;
|
||
|
|
}
|
||
|
|
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.run(
|
||
|
|
`INSERT INTO security_anomalies
|
||
|
|
(anomaly_id, type, severity, description, confidence, affected_user_id, affected_ip, pattern_data, related_logs)
|
||
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
|
|
[
|
||
|
|
anomalyId,
|
||
|
|
details.type,
|
||
|
|
details.severity,
|
||
|
|
details.description,
|
||
|
|
details.confidence,
|
||
|
|
details.affected_user_id || null,
|
||
|
|
details.affected_ip || null,
|
||
|
|
details.pattern_data,
|
||
|
|
details.related_logs
|
||
|
|
],
|
||
|
|
(err) => {
|
||
|
|
if (err) {
|
||
|
|
reject(err);
|
||
|
|
} else {
|
||
|
|
logger.warn(`[SecurityIntelligence] Anomaly detected: ${details.type} - ${details.severity} - ${details.description}`);
|
||
|
|
|
||
|
|
// Log to aggregated logs as well
|
||
|
|
logAggregator.aggregate('security_intelligence', 'warn', 'security', details.description, {
|
||
|
|
anomalyId,
|
||
|
|
type: details.type,
|
||
|
|
severity: details.severity,
|
||
|
|
confidence: details.confidence
|
||
|
|
});
|
||
|
|
|
||
|
|
resolve(anomalyId);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Find similar anomaly (deduplication)
|
||
|
|
*/
|
||
|
|
async findSimilarAnomaly(details) {
|
||
|
|
const recentTime = new Date(Date.now() - 10 * 60 * 1000).toISOString(); // Last 10 minutes
|
||
|
|
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.get(
|
||
|
|
`SELECT * FROM security_anomalies
|
||
|
|
WHERE type = ?
|
||
|
|
AND severity = ?
|
||
|
|
AND (affected_user_id = ? OR affected_ip = ?)
|
||
|
|
AND status = 'open'
|
||
|
|
AND created_at >= ?
|
||
|
|
ORDER BY created_at DESC
|
||
|
|
LIMIT 1`,
|
||
|
|
[details.type, details.severity, details.affected_user_id, details.affected_ip, recentTime],
|
||
|
|
(err, row) => {
|
||
|
|
if (err) reject(err);
|
||
|
|
else resolve(row);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add threat indicator to intelligence database
|
||
|
|
*/
|
||
|
|
async addThreatIndicator(indicator, type, level, description) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.run(
|
||
|
|
`INSERT INTO threat_intelligence (indicator, indicator_type, threat_level, description, confidence, source)
|
||
|
|
VALUES (?, ?, ?, ?, 0.8, 'internal_detection')
|
||
|
|
ON CONFLICT(indicator, indicator_type) DO UPDATE SET
|
||
|
|
last_seen = CURRENT_TIMESTAMP,
|
||
|
|
occurrence_count = occurrence_count + 1,
|
||
|
|
threat_level = ?,
|
||
|
|
description = ?`,
|
||
|
|
[indicator, type, level, description, level, description],
|
||
|
|
(err) => {
|
||
|
|
if (err) reject(err);
|
||
|
|
else {
|
||
|
|
logger.info(`[SecurityIntelligence] Threat indicator added: ${type}=${indicator} (${level})`);
|
||
|
|
resolve();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Calculate overall threat score (0-100)
|
||
|
|
*/
|
||
|
|
async calculateThreatScore() {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.get(
|
||
|
|
`SELECT
|
||
|
|
COUNT(CASE WHEN severity = 'critical' AND status = 'open' THEN 1 END) as critical_count,
|
||
|
|
COUNT(CASE WHEN severity = 'high' AND status = 'open' THEN 1 END) as high_count,
|
||
|
|
COUNT(CASE WHEN severity = 'medium' AND status = 'open' THEN 1 END) as medium_count,
|
||
|
|
COUNT(CASE WHEN severity = 'low' AND status = 'open' THEN 1 END) as low_count
|
||
|
|
FROM security_anomalies
|
||
|
|
WHERE created_at >= datetime('now', '-24 hours')`,
|
||
|
|
[],
|
||
|
|
(err, row) => {
|
||
|
|
if (err) {
|
||
|
|
reject(err);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Weight severity levels
|
||
|
|
const score = Math.min(
|
||
|
|
(row.critical_count * 40) +
|
||
|
|
(row.high_count * 20) +
|
||
|
|
(row.medium_count * 10) +
|
||
|
|
(row.low_count * 5),
|
||
|
|
100
|
||
|
|
);
|
||
|
|
|
||
|
|
this.threatScore = score;
|
||
|
|
|
||
|
|
if (score >= 80) {
|
||
|
|
logger.error(`[SecurityIntelligence] CRITICAL THREAT LEVEL: ${score}/100`);
|
||
|
|
} else if (score >= 50) {
|
||
|
|
logger.warn(`[SecurityIntelligence] HIGH THREAT LEVEL: ${score}/100`);
|
||
|
|
} else if (score >= 20) {
|
||
|
|
logger.info(`[SecurityIntelligence] MEDIUM THREAT LEVEL: ${score}/100`);
|
||
|
|
} else {
|
||
|
|
logger.debug(`[SecurityIntelligence] LOW THREAT LEVEL: ${score}/100`);
|
||
|
|
}
|
||
|
|
|
||
|
|
resolve(score);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get active anomalies
|
||
|
|
*/
|
||
|
|
async getAnomalies(filters = {}) {
|
||
|
|
const {
|
||
|
|
status = 'open',
|
||
|
|
severity,
|
||
|
|
type,
|
||
|
|
limit = 100,
|
||
|
|
offset = 0
|
||
|
|
} = filters;
|
||
|
|
|
||
|
|
let whereClause = ['status = ?'];
|
||
|
|
let params = [status];
|
||
|
|
|
||
|
|
if (severity) {
|
||
|
|
whereClause.push('severity = ?');
|
||
|
|
params.push(severity);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (type) {
|
||
|
|
whereClause.push('type = ?');
|
||
|
|
params.push(type);
|
||
|
|
}
|
||
|
|
|
||
|
|
params.push(limit, offset);
|
||
|
|
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.all(
|
||
|
|
`SELECT * FROM security_anomalies
|
||
|
|
WHERE ${whereClause.join(' AND ')}
|
||
|
|
ORDER BY created_at DESC
|
||
|
|
LIMIT ? OFFSET ?`,
|
||
|
|
params,
|
||
|
|
(err, rows) => {
|
||
|
|
if (err) reject(err);
|
||
|
|
else resolve(rows);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Resolve anomaly
|
||
|
|
*/
|
||
|
|
async resolveAnomaly(anomalyId, resolvedBy, notes) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.run(
|
||
|
|
`UPDATE security_anomalies
|
||
|
|
SET status = 'resolved',
|
||
|
|
resolved_at = CURRENT_TIMESTAMP,
|
||
|
|
resolved_by = ?,
|
||
|
|
resolution_notes = ?
|
||
|
|
WHERE anomaly_id = ?`,
|
||
|
|
[resolvedBy, notes, anomalyId],
|
||
|
|
(err) => {
|
||
|
|
if (err) reject(err);
|
||
|
|
else {
|
||
|
|
logger.info(`[SecurityIntelligence] Anomaly resolved: ${anomalyId} by user ${resolvedBy}`);
|
||
|
|
resolve();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get threat intelligence
|
||
|
|
*/
|
||
|
|
async getThreatIntelligence(filters = {}) {
|
||
|
|
const { level, type, limit = 100 } = filters;
|
||
|
|
|
||
|
|
let whereClause = [];
|
||
|
|
let params = [];
|
||
|
|
|
||
|
|
if (level) {
|
||
|
|
whereClause.push('threat_level = ?');
|
||
|
|
params.push(level);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (type) {
|
||
|
|
whereClause.push('indicator_type = ?');
|
||
|
|
params.push(type);
|
||
|
|
}
|
||
|
|
|
||
|
|
const where = whereClause.length > 0 ? `WHERE ${whereClause.join(' AND ')}` : '';
|
||
|
|
params.push(limit);
|
||
|
|
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
db.all(
|
||
|
|
`SELECT * FROM threat_intelligence ${where}
|
||
|
|
ORDER BY last_seen DESC, occurrence_count DESC
|
||
|
|
LIMIT ?`,
|
||
|
|
params,
|
||
|
|
(err, rows) => {
|
||
|
|
if (err) reject(err);
|
||
|
|
else resolve(rows);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get security intelligence dashboard data
|
||
|
|
*/
|
||
|
|
async getDashboardData() {
|
||
|
|
const [anomalies, threats, score] = await Promise.all([
|
||
|
|
this.getAnomalies({ status: 'open', limit: 50 }),
|
||
|
|
this.getThreatIntelligence({ limit: 20 }),
|
||
|
|
this.calculateThreatScore()
|
||
|
|
]);
|
||
|
|
|
||
|
|
const anomalyStats = {
|
||
|
|
critical: anomalies.filter(a => a.severity === 'critical').length,
|
||
|
|
high: anomalies.filter(a => a.severity === 'high').length,
|
||
|
|
medium: anomalies.filter(a => a.severity === 'medium').length,
|
||
|
|
low: anomalies.filter(a => a.severity === 'low').length
|
||
|
|
};
|
||
|
|
|
||
|
|
return {
|
||
|
|
threatScore: score,
|
||
|
|
anomalies: anomalies.slice(0, 10),
|
||
|
|
anomalyStats,
|
||
|
|
threats: threats.slice(0, 10),
|
||
|
|
timestamp: new Date().toISOString()
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create singleton instance
|
||
|
|
const securityIntelligence = new SecurityIntelligence();
|
||
|
|
|
||
|
|
module.exports = securityIntelligence;
|