@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
JavaScript
;
/**
* 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