UNPKG

@iota-big3/sdk-security

Version:

Advanced security features including zero trust, quantum-safe crypto, and ML threat detection

929 lines (915 loc) 38.7 kB
"use strict"; /** * Log Forensics Analyzer * Analyzes logs for anomalies, suspicious patterns, and timeline reconstruction */ Object.defineProperty(exports, "__esModule", { value: true }); exports.LogAnalyzer = void 0; const tslib_1 = require("tslib"); const crypto = tslib_1.__importStar(require("crypto")); const events_1 = require("events"); const types_1 = require("../types"); class LogAnalyzer extends events_1.EventEmitter { constructor() { super(); this.type = types_1.ForensicAnalysisType.LOG; this.suspiciousPatterns = new Map([ ['failed_auth', /failed|denied|unauthorized|invalid.*password/i], ['privilege_escalation', /sudo|su\s|elevation|administrator/i], ['data_access', /SELECT.*FROM|INSERT.*INTO|UPDATE.*SET|DELETE.*FROM/i], ['command_injection', /;|&&|\|\||`|\$\(/], ['path_traversal', /\.\.[\/\\]|\.\.%2[Ff]/], ['suspicious_process', /nc\s|netcat|mimikatz|psexec|wmic/i], ['encryption', /encrypt|ransom|\.encrypted|aes256/i], ['lateral_movement', /rdp|ssh|telnet|winrm|psexec/i], ['log_clear', /log.*clear|clear.*log|rm.*\.log|del.*\.log/i], ['backdoor', /backdoor|reverse.*shell|bind.*shell|4444|4445/i] ]); this.anomalyDetectors = new Map([ ['volume_spike', this.detectVolumeSpike.bind(this)], ['time_gap', this.detectTimeGaps.bind(this)], ['unusual_source', this.detectUnusualSources.bind(this)], ['error_burst', this.detectErrorBursts.bind(this)], ['after_hours', this.detectAfterHoursActivity.bind(this)] ]); } /** * Analyze log files */ async analyze(evidence) { if (evidence.type !== types_1.ForensicAnalysisType.LOG) { throw new Error('Evidence is not a log file'); } this.emit('analysis:started', { evidenceId: evidence.id }); const findings = []; const artifacts = []; const iocs = []; const timeline = []; try { // Parse log data const logData = await this.parseLogFile(evidence); // Detect anomalies const anomalyFindings = await this.detectAnomalies(logData); findings.push(...anomalyFindings.findings); // Analyze patterns const patternFindings = await this.analyzePatterns(logData.patterns); findings.push(...patternFindings.findings); // Process timeline const timelineAnalysis = await this.analyzeTimeline(logData.timeline); findings.push(...timelineAnalysis.findings); timeline.push(...timelineAnalysis.timeline); iocs.push(...timelineAnalysis.iocs); // Analyze statistics const statsFindings = await this.analyzeStatistics(logData.statistics); findings.push(...statsFindings); // Extract suspicious log entries as artifacts const suspiciousLogs = await this.extractSuspiciousLogs(logData); artifacts.push(...suspiciousLogs); // Generate analysis result const result = { id: crypto.randomUUID(), timestamp: new Date(), analyst: 'LogAnalyzer', type: 'Log Analysis', tool: 'IOTA Log Forensics', findings, artifacts, iocs, timeline: timeline.slice(0, 500), // Limit timeline entries conclusion: this.generateConclusion(findings, logData) }; this.emit('analysis:completed', { evidenceId: evidence.id, findingsCount: findings.length, anomaliesCount: logData.anomalies.length }); return result; } catch (error) { this.emit('analysis:failed', { evidenceId: evidence.id, error: error.message }); throw error; } } /** * Validate log file */ async validate(evidence) { // Check if it's a text-based log file const validExtensions = ['.log', '.txt', '.syslog', '.json', '.xml', '.evt', '.evtx']; const hasValidExtension = validExtensions.some(ext => evidence.sourceName.toLowerCase().endsWith(ext)); // For now, accept any file with valid extension // In production, would check file format/structure return hasValidExtension; } /** * Extract artifacts from logs */ async extract(evidence, options) { const artifacts = []; // Extract suspicious entries if (options?.extractSuspicious) { const suspiciousArtifacts = await this.extractSuspiciousEntries(evidence); artifacts.push(...suspiciousArtifacts); } // Extract IOCs if (options?.extractIOCs) { const iocArtifacts = await this.extractIOCsFromLogs(evidence); artifacts.push(...iocArtifacts); } // Extract timeline if (options?.extractTimeline) { const timelineArtifact = await this.extractTimelineData(evidence); artifacts.push(timelineArtifact); } return artifacts; } /** * Generate report */ async generateReport(analysis) { const report = ` # Log Forensics Analysis Report **Analysis ID**: ${analysis.id} **Date**: ${analysis.timestamp.toISOString()} **Analyst**: ${analysis.analyst} **Tool**: ${analysis.tool} ## Executive Summary ${analysis.conclusion} ## Key Findings ${analysis.findings.map(f => ` ### ${f.title} (${f.severity}) - **Type**: ${f.type} - **Description**: ${f.description} - **Evidence**: ${f.evidence.map(e => ` - ${e}`).join('\n')} ${f.timestamp ? `- **Timestamp**: ${f.timestamp.toISOString()}` : ''} `).join('\n')} ## Anomalies Detected ${analysis.findings.filter(f => f.metadata?.anomaly).map(f => ` - **${f.title}** - Score: ${f.metadata.anomaly.score}/10 - Type: ${f.metadata.anomaly.type} `).join('\n')} ## Timeline Analysis Key events from logs: ${(analysis.timeline || []).slice(0, 30).map(t => ` - **${t.timestamp.toISOString()}**: ${t.action} - Source: ${t.source} ${t.actor ? `- User: ${t.actor}` : ''} ${t.details ? `- Details: ${t.details}` : ''} `).join('\n')} ## IOCs Extracted ${(analysis.iocs || []).map(ioc => ` - **${ioc.type}**: ${ioc.value} - Context: ${ioc.context || 'Found in logs'} `).join('\n')} ## Recommendations 1. Review all high-severity findings for immediate action items 2. Investigate anomalous time periods and unusual access patterns 3. Cross-reference IOCs with network and endpoint logs 4. Implement additional logging for identified blind spots 5. Create alerting rules based on discovered attack patterns `.trim(); return report; } /** * Export findings */ async exportFindings(analysis, format) { switch (format) { case 'json': return Buffer.from(JSON.stringify(analysis, null, 2)); case 'siem': return this.exportToSIEMFormat(analysis); case 'csv': return this.exportToCSV(analysis); default: throw new Error(`Unsupported export format: ${format}`); } } /** * Private helper methods */ async parseLogFile(evidence) { // In production, would parse actual log files // For demo, generate mock log analysis const events = this.generateMockLogEvents(); const patterns = this.identifyPatterns(events); const anomalies = this.runAnomalyDetection(events); const statistics = this.calculateStatistics(events); return { evidenceId: evidence.id, logInfo: { format: this.detectLogFormat(evidence.sourceName), timeRange: { start: events[0].timestamp, end: events[events.length - 1].timestamp }, entryCount: events.length, sources: Array.from(new Set(events.map(e => e.source))) }, anomalies, patterns, timeline: events, statistics }; } async detectAnomalies(logData) { const findings = []; // Convert anomalies to findings for (const anomaly of logData.anomalies) { findings.push({ id: crypto.randomUUID(), severity: anomaly.severity, type: this.mapAnomalyToFindingType(anomaly.type), title: anomaly.type, description: anomaly.description, evidence: anomaly.entries.slice(0, 5), timestamp: anomaly.timestamp, metadata: { anomaly } }); } return { findings }; } async analyzePatterns(patterns) { const findings = []; // Check each pattern against suspicious patterns for (const pattern of patterns) { if (pattern.suspicious) { // Identify which suspicious pattern it matches let matchedPattern = null; for (const [name, regex] of this.suspiciousPatterns) { if (regex.test(pattern.pattern)) { matchedPattern = name; break; } } if (matchedPattern) { findings.push({ id: crypto.randomUUID(), severity: this.getPatternSeverity(matchedPattern), type: this.getPatternFindingType(matchedPattern), title: `Suspicious Pattern: ${this.formatPatternName(matchedPattern)}`, description: `Found ${pattern.occurrences} occurrences of ${matchedPattern} pattern`, evidence: pattern.examples.slice(0, 3), metadata: { pattern, type: matchedPattern } }); } } } return { findings }; } async analyzeTimeline(events) { const findings = []; const timeline = []; const iocs = []; const ipRegex = /\b(?:\d{1,3}\.){3}\d{1,3}\b/g; const urlRegex = /https?:\/\/[^\s]+/g; for (const event of events) { // Convert to timeline entry timeline.push({ timestamp: event.timestamp, source: `Log: ${event.source}`, action: event.level === 'ERROR' ? 'Error' : 'Log Entry', actor: event.parsed?.user, target: event.parsed?.target, details: event.message }); // Extract IOCs from message const ipMatches = event.message.match(ipRegex) || []; for (const ip of ipMatches) { if (this.isPublicIP(ip)) { iocs.push({ type: types_1.IOCType.IP_ADDRESS, value: ip, context: `Found in ${event.source} logs`, firstSeen: event.timestamp, confidence: 70 }); } } const urlMatches = event.message.match(urlRegex) || []; for (const url of urlMatches) { iocs.push({ type: types_1.IOCType.URL, value: url, context: `Found in ${event.source} logs`, firstSeen: event.timestamp, confidence: 60 }); } // Check for specific suspicious activities if (event.parsed?.action === 'failed_login') { const failedLogins = events.filter(e => e.parsed?.action === 'failed_login' && e.parsed?.user === event.parsed?.user && Math.abs(e.timestamp.getTime() - event.timestamp.getTime()) < 5 * 60 * 1000); if (failedLogins.length > 5) { findings.push({ id: crypto.randomUUID(), severity: types_1.FindingSeverity.HIGH, type: types_1.FindingType.SUSPICIOUS_PROCESS, title: 'Brute Force Attack Detected', description: `Multiple failed login attempts for user ${event.parsed.user}`, evidence: [`${failedLogins.length} attempts in 5 minutes`], timestamp: event.timestamp }); } } } // Deduplicate IOCs const uniqueIOCs = Array.from(new Map(iocs.map(ioc => [`${ioc.type}-${ioc.value}`, ioc])).values()); return { findings, timeline, iocs: uniqueIOCs }; } async analyzeStatistics(stats) { const findings = []; // Check error rate const errorRate = (stats.errorCount / stats.totalEntries) * 100; if (errorRate > 10) { findings.push({ id: crypto.randomUUID(), severity: types_1.FindingSeverity.MEDIUM, type: types_1.FindingType.OTHER, title: 'High Error Rate', description: `${errorRate.toFixed(1)}% of log entries are errors`, evidence: stats.topErrors.slice(0, 3).map(e => `${e.message} (${e.count} times)`) }); } // Check for suspicious users const systemUsers = ['root', 'admin', 'administrator', 'system']; const suspiciousUsers = stats.topUsers.filter(u => !systemUsers.includes(u.user.toLowerCase())); if (suspiciousUsers.length > 0 && suspiciousUsers[0].count > 1000) { findings.push({ id: crypto.randomUUID(), severity: types_1.FindingSeverity.MEDIUM, type: types_1.FindingType.OTHER, title: 'Unusual User Activity', description: 'Non-system users with high activity levels', evidence: suspiciousUsers.slice(0, 3).map(u => `${u.user}: ${u.count} events`) }); } // Check hourly distribution for anomalies const afterHoursActivity = stats.hourlyDistribution .filter(h => h.hour < 6 || h.hour > 20) .reduce((sum, h) => sum + h.count, 0); const afterHoursPercentage = (afterHoursActivity / stats.totalEntries) * 100; if (afterHoursPercentage > 30) { findings.push({ id: crypto.randomUUID(), severity: types_1.FindingSeverity.MEDIUM, type: types_1.FindingType.OTHER, title: 'Significant After-Hours Activity', description: `${afterHoursPercentage.toFixed(1)}% of activity outside business hours`, evidence: [`${afterHoursActivity} events between 8 PM and 6 AM`] }); } return findings; } async extractSuspiciousLogs(logData) { const artifacts = []; // Extract logs related to findings const suspiciousEntries = []; // Add anomalous entries for (const anomaly of logData.anomalies) { suspiciousEntries.push(...anomaly.entries); } // Add examples from suspicious patterns for (const pattern of logData.patterns.filter(p => p.suspicious)) { suspiciousEntries.push(...pattern.examples); } if (suspiciousEntries.length > 0) { const content = suspiciousEntries.join('\n'); const hash = crypto.createHash('sha256').update(content).digest('hex'); artifacts.push({ id: crypto.randomUUID(), name: 'suspicious_log_entries.txt', type: types_1.ArtifactType.LOG_FILE, size: Buffer.byteLength(content), hash, extractedFrom: logData.evidenceId, extractedAt: new Date(), metadata: { entryCount: suspiciousEntries.length, sources: logData.logInfo.sources } }); } return artifacts; } // Anomaly detection methods runAnomalyDetection(events) { const anomalies = []; for (const [name, detector] of this.anomalyDetectors) { const detected = detector(events); anomalies.push(...detected); } return anomalies.sort((a, b) => b.score - a.score); } detectVolumeSpike(events) { const anomalies = []; const bucketSize = 5 * 60 * 1000; // 5 minutes const buckets = new Map(); // Group events into time buckets for (const event of events) { const bucket = Math.floor(event.timestamp.getTime() / bucketSize) * bucketSize; if (!buckets.has(bucket)) { buckets.set(bucket, []); } buckets.get(bucket).push(event); } // Calculate average and standard deviation const counts = Array.from(buckets.values()).map(b => b.length); const avg = counts.reduce((a, b) => a + b, 0) / counts.length; const stdDev = Math.sqrt(counts.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / counts.length); // Find spikes for (const [timestamp, bucketEvents] of buckets) { const count = bucketEvents.length; if (count > avg + (2 * stdDev)) { const score = Math.min(10, Math.round((count - avg) / stdDev)); anomalies.push({ timestamp: new Date(timestamp), type: 'Volume Spike', severity: score > 7 ? types_1.FindingSeverity.HIGH : types_1.FindingSeverity.MEDIUM, description: `Unusual log volume: ${count} events in 5 minutes (average: ${avg.toFixed(0)})`, entries: bucketEvents.slice(0, 5).map(e => e.message), score }); } } return anomalies; } detectTimeGaps(events) { const anomalies = []; const sorted = events.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); for (let i = 1; i < sorted.length; i++) { const gap = sorted[i].timestamp.getTime() - sorted[i - 1].timestamp.getTime(); const gapMinutes = gap / (60 * 1000); if (gapMinutes > 30) { anomalies.push({ timestamp: sorted[i - 1].timestamp, type: 'Time Gap', severity: gapMinutes > 120 ? types_1.FindingSeverity.HIGH : types_1.FindingSeverity.MEDIUM, description: `${gapMinutes.toFixed(0)} minute gap in logs`, entries: [ `Last event before gap: ${sorted[i - 1].message}`, `First event after gap: ${sorted[i].message}` ], score: Math.min(10, Math.round(gapMinutes / 30)) }); } } return anomalies; } detectUnusualSources(events) { const anomalies = []; const sourceCounts = new Map(); // Count events per source for (const event of events) { sourceCounts.set(event.source, (sourceCounts.get(event.source) || 0) + 1); } // Find sources with very few events (potential one-off malicious activity) for (const [source, count] of sourceCounts) { if (count < 5 && events.length > 1000) { const sourceEvents = events.filter(e => e.source === source); anomalies.push({ timestamp: sourceEvents[0].timestamp, type: 'Unusual Source', severity: types_1.FindingSeverity.LOW, description: `Rare log source: ${source} (only ${count} events)`, entries: sourceEvents.map(e => e.message), score: 4 }); } } return anomalies; } detectErrorBursts(events) { const anomalies = []; const errorEvents = events.filter(e => e.level === 'ERROR'); const windowSize = 60 * 1000; // 1 minute for (let i = 0; i < errorEvents.length; i++) { const windowStart = errorEvents[i].timestamp.getTime(); const windowEnd = windowStart + windowSize; const windowErrors = errorEvents.filter(e => e.timestamp.getTime() >= windowStart && e.timestamp.getTime() < windowEnd); if (windowErrors.length > 10) { anomalies.push({ timestamp: errorEvents[i].timestamp, type: 'Error Burst', severity: types_1.FindingSeverity.HIGH, description: `${windowErrors.length} errors in 1 minute`, entries: windowErrors.slice(0, 5).map(e => e.message), score: Math.min(10, windowErrors.length / 2) }); // Skip ahead to avoid duplicate detections i += windowErrors.length - 1; } } return anomalies; } detectAfterHoursActivity(events) { const anomalies = []; const afterHoursEvents = events.filter(e => { const hour = e.timestamp.getHours(); return hour < 6 || hour > 20; }); // Group by user const userActivity = new Map(); for (const event of afterHoursEvents) { const user = event.parsed?.user || 'unknown'; if (!userActivity.has(user)) { userActivity.set(user, []); } userActivity.get(user).push(event); } // Find suspicious after-hours activity for (const [user, userEvents] of userActivity) { if (userEvents.length > 20 && user !== 'system' && user !== 'unknown') { anomalies.push({ timestamp: userEvents[0].timestamp, type: 'After Hours Activity', severity: types_1.FindingSeverity.MEDIUM, description: `User ${user} active after hours (${userEvents.length} events)`, entries: userEvents.slice(0, 5).map(e => e.message), score: 6 }); } } return anomalies; } identifyPatterns(events) { const patterns = []; const patternMap = new Map(); // Group similar messages for (const event of events) { // Normalize message by removing timestamps, IPs, etc. const normalized = this.normalizeLogMessage(event.message); if (!patternMap.has(normalized)) { patternMap.set(normalized, []); } patternMap.get(normalized).push(event); } // Convert to patterns for (const [pattern, patternEvents] of patternMap) { if (patternEvents.length > 1) { const suspicious = this.isPatternSuspicious(pattern); patterns.push({ pattern, occurrences: patternEvents.length, firstSeen: patternEvents[0].timestamp, lastSeen: patternEvents[patternEvents.length - 1].timestamp, examples: patternEvents.slice(0, 3).map(e => e.message), suspicious }); } } return patterns.sort((a, b) => b.occurrences - a.occurrences); } calculateStatistics(events) { const stats = { totalEntries: events.length, errorCount: 0, warningCount: 0, uniqueUsers: new Set(), uniqueIps: new Set(), topErrors: [], topUsers: [], hourlyDistribution: [] }; const errorMap = new Map(); const userMap = new Map(); const hourMap = new Map(); const ipRegex = /\b(?:\d{1,3}\.){3}\d{1,3}\b/g; for (const event of events) { // Count by level if (event.level === 'ERROR') stats.errorCount++; if (event.level === 'WARNING') stats.warningCount++; // Extract users if (event.parsed?.user) { stats.uniqueUsers.add(event.parsed.user); userMap.set(event.parsed.user, (userMap.get(event.parsed.user) || 0) + 1); } // Extract IPs const ips = event.message.match(ipRegex) || []; ips.forEach(ip => stats.uniqueIps.add(ip)); // Track errors if (event.level === 'ERROR') { const key = this.normalizeLogMessage(event.message); errorMap.set(key, (errorMap.get(key) || 0) + 1); } // Hour distribution const hour = event.timestamp.getHours(); hourMap.set(hour, (hourMap.get(hour) || 0) + 1); } // Convert maps to arrays stats.topErrors = Array.from(errorMap.entries()) .map(([message, count]) => ({ message, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); stats.topUsers = Array.from(userMap.entries()) .map(([user, count]) => ({ user, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); // Fill hour distribution for (let hour = 0; hour < 24; hour++) { stats.hourlyDistribution.push({ hour, count: hourMap.get(hour) || 0 }); } stats.uniqueUsers = stats.uniqueUsers.size; stats.uniqueIps = stats.uniqueIps.size; return stats; } normalizeLogMessage(message) { return message .replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/g, 'TIMESTAMP') .replace(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g, 'IP') .replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, 'UUID') .replace(/\b\d+\b/g, 'NUM') .toLowerCase() .trim(); } isPatternSuspicious(pattern) { for (const [, regex] of this.suspiciousPatterns) { if (regex.test(pattern)) { return true; } } return false; } detectLogFormat(filename) { if (filename.includes('syslog')) return 'syslog'; if (filename.includes('apache') || filename.includes('access')) return 'apache'; if (filename.includes('nginx')) return 'nginx'; if (filename.endsWith('.json')) return 'json'; if (filename.endsWith('.xml')) return 'xml'; if (filename.endsWith('.evt') || filename.endsWith('.evtx')) return 'windows_event'; return 'unknown'; } mapAnomalyToFindingType(anomalyType) { const mapping = { 'Volume Spike': types_1.FindingType.OTHER, 'Time Gap': types_1.FindingType.LOG_TAMPERING, 'Unusual Source': types_1.FindingType.OTHER, 'Error Burst': types_1.FindingType.OTHER, 'After Hours Activity': types_1.FindingType.SUSPICIOUS_PROCESS }; return mapping[anomalyType] || types_1.FindingType.OTHER; } getPatternSeverity(patternType) { const severities = { 'failed_auth': types_1.FindingSeverity.HIGH, 'privilege_escalation': types_1.FindingSeverity.CRITICAL, 'data_access': types_1.FindingSeverity.MEDIUM, 'command_injection': types_1.FindingSeverity.CRITICAL, 'path_traversal': types_1.FindingSeverity.HIGH, 'suspicious_process': types_1.FindingSeverity.HIGH, 'encryption': types_1.FindingSeverity.CRITICAL, 'lateral_movement': types_1.FindingSeverity.HIGH, 'log_clear': types_1.FindingSeverity.CRITICAL, 'backdoor': types_1.FindingSeverity.CRITICAL }; return severities[patternType] || types_1.FindingSeverity.MEDIUM; } getPatternFindingType(patternType) { const types = { 'failed_auth': types_1.FindingType.SUSPICIOUS_PROCESS, 'privilege_escalation': types_1.FindingType.PRIVILEGE_ESCALATION, 'data_access': types_1.FindingType.DATA_EXFILTRATION, 'command_injection': types_1.FindingType.SUSPICIOUS_PROCESS, 'path_traversal': types_1.FindingType.SUSPICIOUS_PROCESS, 'suspicious_process': types_1.FindingType.SUSPICIOUS_PROCESS, 'encryption': types_1.FindingType.MALWARE, 'lateral_movement': types_1.FindingType.LATERAL_MOVEMENT, 'log_clear': types_1.FindingType.LOG_TAMPERING, 'backdoor': types_1.FindingType.PERSISTENCE }; return types[patternType] || types_1.FindingType.OTHER; } formatPatternName(pattern) { return pattern.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' '); } isPublicIP(ip) { const parts = ip.split('.').map(Number); // Check for private IP ranges if (parts[0] === 10) return false; if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return false; if (parts[0] === 192 && parts[1] === 168) return false; if (parts[0] === 127) return false; return true; } generateConclusion(findings, logData) { const criticalCount = findings.filter(f => f.severity === types_1.FindingSeverity.CRITICAL).length; const highCount = findings.filter(f => f.severity === types_1.FindingSeverity.HIGH).length; const anomalyCount = logData.anomalies.length; const timeRange = (logData.logInfo.timeRange.end.getTime() - logData.logInfo.timeRange.start.getTime()) / (1000 * 60 * 60); if (criticalCount > 0) { return `Critical security events detected in logs. Found ${criticalCount} critical findings including potential log tampering, privilege escalation, or malware activity. Analysis covered ${logData.logInfo.entryCount.toLocaleString()} log entries over ${timeRange.toFixed(1)} hours. Immediate investigation required.`; } else if (highCount > 0 || anomalyCount > 5) { return `Suspicious activity detected in logs. Found ${highCount} high-severity findings and ${anomalyCount} anomalies. The logs show signs of potential compromise or malicious activity that warrant further investigation.`; } else { return `Log analysis complete. Analyzed ${logData.logInfo.entryCount.toLocaleString()} entries from ${logData.logInfo.sources.length} sources. Found ${findings.length} findings of moderate or lower severity. Recommend implementing additional monitoring based on discovered patterns.`; } } exportToSIEMFormat(analysis) { // CEF (Common Event Format) export const cefEvents = []; for (const finding of analysis.findings) { const cef = [ 'CEF:0', 'IOTA Security', 'Log Forensics', '1.0', finding.type, finding.title, this.severityToNumber(finding.severity), `msg=${finding.description} cs1Label=Evidence cs1=${finding.evidence.join('; ')}` ].join('|'); cefEvents.push(cef); } return Buffer.from(cefEvents.join('\n')); } exportToCSV(analysis) { const lines = ['Timestamp,Severity,Type,Title,Description,Evidence']; for (const finding of analysis.findings) { lines.push([ finding.timestamp?.toISOString() || '', finding.severity, finding.type, `"${finding.title}"`, `"${finding.description}"`, `"${finding.evidence.join('; ')}"` ].join(',')); } return Buffer.from(lines.join('\n')); } severityToNumber(severity) { const mapping = { [types_1.FindingSeverity.CRITICAL]: 10, [types_1.FindingSeverity.HIGH]: 8, [types_1.FindingSeverity.MEDIUM]: 5, [types_1.FindingSeverity.LOW]: 3, [types_1.FindingSeverity.INFO]: 1 }; return mapping[severity]; } async extractSuspiciousEntries(evidence) { // Mock implementation return []; } async extractIOCsFromLogs(evidence) { // Mock implementation return []; } async extractTimelineData(evidence) { // Mock implementation return { id: crypto.randomUUID(), name: 'timeline.json', type: types_1.ArtifactType.OTHER, size: 0, hash: '', extractedFrom: evidence.id, extractedAt: new Date() }; } // Mock data generators generateMockLogEvents() { const events = []; const now = new Date(); const sources = ['auth.log', 'syslog', 'apache.log', 'application.log']; const levels = ['INFO', 'WARNING', 'ERROR', 'DEBUG']; const users = ['admin', 'user1', 'user2', 'www-data', 'root', 'system']; // Generate normal events for (let i = 0; i < 5000; i++) { const timestamp = new Date(now.getTime() - Math.random() * 24 * 60 * 60 * 1000); const source = sources[Math.floor(Math.random() * sources.length)]; const level = levels[Math.floor(Math.random() * levels.length)]; const user = users[Math.floor(Math.random() * users.length)]; events.push({ timestamp, source, level, message: this.generateLogMessage(source, level, user), parsed: this.parseLogMessage(source, level, user) }); } // Add suspicious events const suspiciousTimestamp = new Date(now.getTime() - 2 * 60 * 60 * 1000); // Failed login attempts for (let i = 0; i < 10; i++) { events.push({ timestamp: new Date(suspiciousTimestamp.getTime() + i * 30 * 1000), source: 'auth.log', level: 'WARNING', message: 'Failed password for admin from 185.220.101.45 port 22 ssh2', parsed: { user: 'admin', action: 'failed_login', sourceIp: '185.220.101.45' } }); } // Privilege escalation events.push({ timestamp: new Date(suspiciousTimestamp.getTime() + 15 * 60 * 1000), source: 'auth.log', level: 'INFO', message: 'user1 : TTY=pts/0 ; PWD=/home/user1 ; USER=root ; COMMAND=/bin/bash', parsed: { user: 'user1', action: 'privilege_escalation', target: 'root' } }); // Suspicious process events.push({ timestamp: new Date(suspiciousTimestamp.getTime() + 20 * 60 * 1000), source: 'syslog', level: 'WARNING', message: 'Process started: nc -lvnp 4444 -e /bin/bash', parsed: { action: 'process_start', target: 'nc -lvnp 4444' } }); // Log clearing attempt events.push({ timestamp: new Date(suspiciousTimestamp.getTime() + 30 * 60 * 1000), source: 'syslog', level: 'WARNING', message: 'Command executed: rm -rf /var/log/*.log', parsed: { user: 'user1', action: 'log_clear', target: '/var/log/*.log' } }); // Sort by timestamp return events.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); } generateLogMessage(source, level, user) { const messages = { 'auth.log': [ `Accepted password for ${user} from 192.168.1.100 port 22 ssh2`, `Failed password for ${user} from 192.168.1.100 port 22 ssh2`, `session opened for user ${user}`, `session closed for user ${user}`, `${user} : TTY=pts/0 ; PWD=/home/${user} ; USER=root ; COMMAND=/usr/bin/apt update` ], 'syslog': [ `systemd[1]: Started Session 123 of user ${user}`, `kernel: [12345.678] TCP: request_sock_TCP: Possible SYN flooding`, `CRON[1234]: (${user}) CMD (/usr/bin/php /var/www/cron.php)`, `systemd[1]: Stopping MySQL Community Server...`, `NetworkManager[567]: <info> device (eth0): state change: activated -> deactivating` ], 'apache.log': [ `192.168.1.100 - ${user} [${new Date().toISOString()}] "GET /index.php HTTP/1.1" 200 1234`, `192.168.1.100 - - [${new Date().toISOString()}] "POST /admin/login.php HTTP/1.1" 401 567`, `192.168.1.100 - ${user} [${new Date().toISOString()}] "GET /api/users HTTP/1.1" 200 4567`, `Error: File does not exist: /var/www/html/test.php`, `[${level}] mod_security: Access denied with code 403` ], 'application.log': [ `[${level}] User ${user} logged in successfully`, `[${level}] Database connection established`, `[${level}] Failed to connect to Redis server`, `[${level}] API request: GET /api/v1/users - 200 OK`, `[${level}] Exception: Division by zero in calculate.php line 42` ] }; const sourceMessages = messages[source] || messages['syslog']; return sourceMessages[Math.floor(Math.random() * sourceMessages.length)]; } parseLogMessage(source, level, user) { // Simple parsing logic for demo if (source === 'auth.log') { return { user, action: level === 'WARNING' ? 'failed_login' : 'login', sourceIp: '192.168.1.100' }; } return undefined; } } exports.LogAnalyzer = LogAnalyzer; //# sourceMappingURL=log-analyzer.js.map