alert2action 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +203 -0
- package/alert2action.cmd +2 -0
- package/bin/alert2action.js +77 -0
- package/examples/brute-force-alert.json +33 -0
- package/examples/credential-dump-alert.json +32 -0
- package/examples/lateral-movement-alert.json +29 -0
- package/examples/malware-alert-2.json +30 -0
- package/examples/malware-alert.json +35 -0
- package/examples/phishing-alert.json +28 -0
- package/examples/privesc-alert.json +112 -0
- package/examples/soc-test-alert.json +80 -0
- package/package.json +48 -0
- package/src/formatter.js +267 -0
- package/src/guide-generator.js +478 -0
- package/src/index.js +28 -0
- package/src/mitre.js +837 -0
- package/src/parser.js +309 -0
package/src/parser.js
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alert Parser Module
|
|
3
|
+
* Normalizes different alert formats into a standard structure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parse and normalize an alert from various formats
|
|
8
|
+
* Supports: Generic, Splunk, Microsoft Sentinel, Elastic, CrowdStrike, etc.
|
|
9
|
+
*/
|
|
10
|
+
function parseAlert(rawAlert) {
|
|
11
|
+
const normalized = {
|
|
12
|
+
// Basic info
|
|
13
|
+
id: null,
|
|
14
|
+
title: null,
|
|
15
|
+
description: null,
|
|
16
|
+
severity: 'medium',
|
|
17
|
+
timestamp: null,
|
|
18
|
+
source: null,
|
|
19
|
+
|
|
20
|
+
// Network indicators
|
|
21
|
+
sourceIp: null,
|
|
22
|
+
destIp: null,
|
|
23
|
+
sourcePort: null,
|
|
24
|
+
destPort: null,
|
|
25
|
+
protocol: null,
|
|
26
|
+
|
|
27
|
+
// Host indicators
|
|
28
|
+
hostname: null,
|
|
29
|
+
username: null,
|
|
30
|
+
domain: null,
|
|
31
|
+
|
|
32
|
+
// Process indicators
|
|
33
|
+
processName: null,
|
|
34
|
+
processPath: null,
|
|
35
|
+
processCommandLine: null,
|
|
36
|
+
parentProcess: null,
|
|
37
|
+
processId: null,
|
|
38
|
+
|
|
39
|
+
// File indicators
|
|
40
|
+
filePath: null,
|
|
41
|
+
fileHash: null,
|
|
42
|
+
fileName: null,
|
|
43
|
+
|
|
44
|
+
// Additional context
|
|
45
|
+
category: null,
|
|
46
|
+
action: null,
|
|
47
|
+
status: null,
|
|
48
|
+
eventType: null,
|
|
49
|
+
rawData: rawAlert,
|
|
50
|
+
|
|
51
|
+
// Extracted indicators for MITRE mapping
|
|
52
|
+
indicators: [],
|
|
53
|
+
keywords: []
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Try to extract from common field patterns
|
|
57
|
+
normalized.id = extractField(rawAlert, ['id', 'alert_id', 'event_id', 'alertId', 'eventId', '_id', 'uuid', 'host_id']);
|
|
58
|
+
normalized.title = extractField(rawAlert, ['title', 'name', 'alert_name', 'alertName', 'rule_name', 'ruleName', 'signature', 'event_type', 'eventType', 'rule_name', 'summary']);
|
|
59
|
+
normalized.description = extractField(rawAlert, ['description', 'message', 'msg', 'details', 'summary', 'reason', 'technique_details']);
|
|
60
|
+
normalized.severity = normalizeSeverity(extractField(rawAlert, ['severity', 'event_severity', 'priority', 'risk_level', 'riskLevel', 'threat_level', 'urgency', 'criticality']));
|
|
61
|
+
normalized.timestamp = extractField(rawAlert, ['timestamp', 'time', 'created_at', 'createdAt', 'detection_time', '@timestamp', 'event_time', 'eventTime', 'ingested_time']);
|
|
62
|
+
normalized.source = extractField(rawAlert, ['source', 'product', 'vendor', 'tool', 'detector', 'data_source', 'log_source']);
|
|
63
|
+
|
|
64
|
+
// Network
|
|
65
|
+
normalized.sourceIp = extractField(rawAlert, ['source_ip', 'sourceIp', 'src_ip', 'srcIp', 'src', 'attacker_ip', 'remote_ip', 'client_ip', 'ip_address']);
|
|
66
|
+
normalized.destIp = extractField(rawAlert, ['dest_ip', 'destIp', 'dst_ip', 'dstIp', 'dst', 'destination_ip', 'target_ip', 'server_ip', 'local_ip']);
|
|
67
|
+
normalized.sourcePort = extractField(rawAlert, ['source_port', 'sourcePort', 'src_port', 'srcPort']);
|
|
68
|
+
normalized.destPort = extractField(rawAlert, ['dest_port', 'destPort', 'dst_port', 'dstPort', 'port']);
|
|
69
|
+
normalized.protocol = extractField(rawAlert, ['protocol', 'proto', 'network_protocol']);
|
|
70
|
+
|
|
71
|
+
// Host
|
|
72
|
+
normalized.hostname = extractField(rawAlert, ['hostname', 'host', 'computer_name', 'computerName', 'machine', 'device_name', 'endpoint']);
|
|
73
|
+
normalized.username = extractField(rawAlert, ['username', 'user', 'user_name', 'userName', 'account', 'account_name', 'actor', 'original_user', 'escalated_user']);
|
|
74
|
+
normalized.domain = extractField(rawAlert, ['domain', 'domain_name', 'ad_domain']);
|
|
75
|
+
|
|
76
|
+
// Process
|
|
77
|
+
normalized.processName = extractField(rawAlert, ['process_name', 'processName', 'process', 'image', 'exe', 'executable', 'suspicious_binary']);
|
|
78
|
+
normalized.processPath = extractField(rawAlert, ['process_path', 'processPath', 'image_path', 'file_path', 'exe_path', 'service_binary']);
|
|
79
|
+
normalized.processCommandLine = extractField(rawAlert, ['command_line', 'commandLine', 'cmdline', 'cmd', 'process_command_line', 'command']);
|
|
80
|
+
normalized.parentProcess = extractField(rawAlert, ['parent_process', 'parentProcess', 'parent_image', 'parent']);
|
|
81
|
+
normalized.processId = extractField(rawAlert, ['process_id', 'processId', 'pid']);
|
|
82
|
+
|
|
83
|
+
// File
|
|
84
|
+
normalized.filePath = extractField(rawAlert, ['file_path', 'filePath', 'path', 'target_path', 'service_binary']);
|
|
85
|
+
normalized.fileHash = extractField(rawAlert, ['file_hash', 'fileHash', 'hash', 'md5', 'sha256', 'sha1']);
|
|
86
|
+
normalized.fileName = extractField(rawAlert, ['file_name', 'fileName', 'filename', 'file', 'suspicious_binary']);
|
|
87
|
+
|
|
88
|
+
// Context
|
|
89
|
+
normalized.category = extractField(rawAlert, ['category', 'type', 'alert_type', 'alertType', 'tactic', 'technique', 'method', 'vector']);
|
|
90
|
+
normalized.action = extractField(rawAlert, ['action', 'event_action', 'result', 'outcome', 'success']);
|
|
91
|
+
normalized.status = extractField(rawAlert, ['status', 'state', 'resolution', 'event_status', 'isolation_status']);
|
|
92
|
+
normalized.eventType = extractField(rawAlert, ['event_type', 'eventType', 'type', 'activity_type']);
|
|
93
|
+
|
|
94
|
+
// Extract keywords for MITRE mapping
|
|
95
|
+
normalized.keywords = extractKeywords(normalized);
|
|
96
|
+
normalized.indicators = extractIndicators(normalized);
|
|
97
|
+
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Extract a field value from multiple possible field names
|
|
103
|
+
* Searches top-level, common nested paths, and deeply nested objects
|
|
104
|
+
*/
|
|
105
|
+
function extractField(obj, fieldNames) {
|
|
106
|
+
for (const field of fieldNames) {
|
|
107
|
+
// Check top level
|
|
108
|
+
if (obj[field] !== undefined && obj[field] !== null && obj[field] !== '') {
|
|
109
|
+
// If it's an object (like host: {hostname: ...}), skip it
|
|
110
|
+
if (typeof obj[field] !== 'object') {
|
|
111
|
+
return obj[field];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check nested common paths (expanded for more formats)
|
|
116
|
+
const nestedPaths = [
|
|
117
|
+
'data', 'event', 'alert', 'result', 'fields', 'source',
|
|
118
|
+
'host', 'user', 'process', 'file', 'network', 'destination',
|
|
119
|
+
'file_activity', 'persistence', 'detection', 'exploitation',
|
|
120
|
+
'privilege_change', 'ioc', 'analyst_notes', 'hashes'
|
|
121
|
+
];
|
|
122
|
+
for (const path of nestedPaths) {
|
|
123
|
+
if (obj[path] && typeof obj[path] === 'object') {
|
|
124
|
+
if (obj[path][field] !== undefined && obj[path][field] !== null && obj[path][field] !== '') {
|
|
125
|
+
if (typeof obj[path][field] !== 'object') {
|
|
126
|
+
return obj[path][field];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Check one level deeper (e.g., host.os.name)
|
|
130
|
+
for (const subPath of Object.keys(obj[path])) {
|
|
131
|
+
if (typeof obj[path][subPath] === 'object' && obj[path][subPath]) {
|
|
132
|
+
if (obj[path][subPath][field] !== undefined) {
|
|
133
|
+
return obj[path][subPath][field];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Normalize severity levels to standard values
|
|
145
|
+
*/
|
|
146
|
+
function normalizeSeverity(severity) {
|
|
147
|
+
if (!severity) return 'medium';
|
|
148
|
+
|
|
149
|
+
const severityStr = String(severity).toLowerCase();
|
|
150
|
+
|
|
151
|
+
// Map various severity representations
|
|
152
|
+
const severityMap = {
|
|
153
|
+
'critical': 'critical',
|
|
154
|
+
'crit': 'critical',
|
|
155
|
+
'5': 'critical',
|
|
156
|
+
'very high': 'critical',
|
|
157
|
+
'emergency': 'critical',
|
|
158
|
+
|
|
159
|
+
'high': 'high',
|
|
160
|
+
'4': 'high',
|
|
161
|
+
'major': 'high',
|
|
162
|
+
'severe': 'high',
|
|
163
|
+
|
|
164
|
+
'medium': 'medium',
|
|
165
|
+
'med': 'medium',
|
|
166
|
+
'3': 'medium',
|
|
167
|
+
'moderate': 'medium',
|
|
168
|
+
'warning': 'medium',
|
|
169
|
+
|
|
170
|
+
'low': 'low',
|
|
171
|
+
'2': 'low',
|
|
172
|
+
'minor': 'low',
|
|
173
|
+
|
|
174
|
+
'informational': 'informational',
|
|
175
|
+
'info': 'informational',
|
|
176
|
+
'1': 'informational',
|
|
177
|
+
'notice': 'informational'
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return severityMap[severityStr] || 'medium';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Extract keywords from alert for MITRE technique mapping
|
|
185
|
+
*/
|
|
186
|
+
function extractKeywords(alert) {
|
|
187
|
+
const keywords = new Set();
|
|
188
|
+
|
|
189
|
+
// Extract from title and description
|
|
190
|
+
const textFields = [alert.title, alert.description, alert.category, alert.eventType, alert.action];
|
|
191
|
+
const keywordPatterns = [
|
|
192
|
+
// Authentication & Brute Force
|
|
193
|
+
'brute force', 'login', 'authentication', 'credential', 'password', 'logon', 'failed login',
|
|
194
|
+
'password spray', 'credential stuffing', 'account lockout',
|
|
195
|
+
|
|
196
|
+
// Execution
|
|
197
|
+
'powershell', 'cmd', 'script', 'execution', 'wscript', 'cscript', 'macro', 'command line',
|
|
198
|
+
'living off the land', 'lolbin', 'mshta', 'regsvr32', 'rundll32', 'certutil',
|
|
199
|
+
|
|
200
|
+
// Persistence
|
|
201
|
+
'registry', 'scheduled task', 'service', 'startup', 'autorun', 'persistence',
|
|
202
|
+
'cron', 'boot', 'wmi subscription', 'service creation',
|
|
203
|
+
|
|
204
|
+
// Privilege Escalation
|
|
205
|
+
'privilege', 'escalation', 'admin', 'root', 'sudo', 'elevation', 'uac bypass',
|
|
206
|
+
'token manipulation', 'impersonation', 'system', 'kernel exploit', 'setuid',
|
|
207
|
+
|
|
208
|
+
// Defense Evasion
|
|
209
|
+
'obfuscation', 'encoded', 'base64', 'hidden', 'masquerading', 'disable', 'bypass',
|
|
210
|
+
'process injection', 'dll injection', 'hollowing', 'av evasion', 'edr tampering',
|
|
211
|
+
'log clearing', 'timestomp', 'signed binary', 'code signing',
|
|
212
|
+
|
|
213
|
+
// Credential Access
|
|
214
|
+
'mimikatz', 'lsass', 'credential dump', 'hash', 'kerberos', 'ntlm', 'keylogger',
|
|
215
|
+
'password store', 'browser credential', 'sam', 'ntds', 'dcSync',
|
|
216
|
+
|
|
217
|
+
// Discovery
|
|
218
|
+
'enumeration', 'reconnaissance', 'scan', 'discovery', 'query', 'whoami',
|
|
219
|
+
'network scan', 'port scan', 'service enumeration', 'account discovery',
|
|
220
|
+
'domain enumeration', 'process discovery', 'system information',
|
|
221
|
+
|
|
222
|
+
// Lateral Movement
|
|
223
|
+
'lateral', 'psexec', 'wmi', 'remote', 'smb', 'rdp', 'ssh', 'winrm',
|
|
224
|
+
'pass the hash', 'pass the ticket', 'remote service', 'dcom',
|
|
225
|
+
|
|
226
|
+
// Collection
|
|
227
|
+
'data collection', 'keylogger', 'screenshot', 'clipboard', 'archive', 'staging',
|
|
228
|
+
|
|
229
|
+
// Command and Control
|
|
230
|
+
'c2', 'command and control', 'beacon', 'callback', 'beaconing',
|
|
231
|
+
'dns tunneling', 'encrypted channel', 'proxy', 'covert channel',
|
|
232
|
+
|
|
233
|
+
// Exfiltration
|
|
234
|
+
'exfiltration', 'data transfer', 'upload', 'data theft', 'large transfer',
|
|
235
|
+
'cloud exfil', 'ftp', 'dns exfil',
|
|
236
|
+
|
|
237
|
+
// Impact
|
|
238
|
+
'ransomware', 'encrypt', 'wipe', 'destruct', 'delete', 'defacement',
|
|
239
|
+
'resource hijacking', 'cryptomining', 'dos', 'denial of service',
|
|
240
|
+
|
|
241
|
+
// Malware
|
|
242
|
+
'malware', 'virus', 'trojan', 'worm', 'backdoor', 'rat', 'rootkit',
|
|
243
|
+
'dropper', 'loader', 'implant',
|
|
244
|
+
|
|
245
|
+
// Network & Initial Access
|
|
246
|
+
'phishing', 'spam', 'suspicious', 'anomaly', 'unusual', 'dns', 'http', 'https',
|
|
247
|
+
'drive-by', 'exploit', 'vulnerability', 'cve', 'web shell',
|
|
248
|
+
|
|
249
|
+
// File & Payload
|
|
250
|
+
'suspicious file', 'malicious', 'dropper', 'payload', 'attachment',
|
|
251
|
+
|
|
252
|
+
// Identity & Compliance
|
|
253
|
+
'impossible travel', 'privileged login', 'service account', 'oauth',
|
|
254
|
+
'policy violation', 'audit', 'compliance', 'configuration drift',
|
|
255
|
+
|
|
256
|
+
// Threat Intel
|
|
257
|
+
'ioc', 'indicator', 'threat intel', 'behavior anomaly', 'threat hunt'
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
for (const text of textFields) {
|
|
261
|
+
if (!text) continue;
|
|
262
|
+
const lowerText = String(text).toLowerCase();
|
|
263
|
+
|
|
264
|
+
for (const pattern of keywordPatterns) {
|
|
265
|
+
if (lowerText.includes(pattern)) {
|
|
266
|
+
keywords.add(pattern);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Check command line for suspicious patterns
|
|
272
|
+
if (alert.processCommandLine) {
|
|
273
|
+
const cmd = alert.processCommandLine.toLowerCase();
|
|
274
|
+
if (cmd.includes('-enc') || cmd.includes('-encoded')) keywords.add('encoded');
|
|
275
|
+
if (cmd.includes('downloadstring') || cmd.includes('invoke-webrequest')) keywords.add('download');
|
|
276
|
+
if (cmd.includes('bypass') || cmd.includes('-ep bypass')) keywords.add('bypass');
|
|
277
|
+
if (cmd.includes('hidden') || cmd.includes('-w hidden')) keywords.add('hidden');
|
|
278
|
+
if (cmd.includes('invoke-mimikatz')) keywords.add('mimikatz');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Check process names
|
|
282
|
+
if (alert.processName) {
|
|
283
|
+
const proc = alert.processName.toLowerCase();
|
|
284
|
+
const suspiciousProcs = ['powershell', 'cmd', 'wscript', 'cscript', 'mshta', 'regsvr32', 'rundll32', 'certutil', 'bitsadmin'];
|
|
285
|
+
for (const sp of suspiciousProcs) {
|
|
286
|
+
if (proc.includes(sp)) keywords.add(sp);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return Array.from(keywords);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Extract IOC indicators from alert
|
|
295
|
+
*/
|
|
296
|
+
function extractIndicators(alert) {
|
|
297
|
+
const indicators = [];
|
|
298
|
+
|
|
299
|
+
if (alert.sourceIp) indicators.push({ type: 'ip', value: alert.sourceIp, context: 'source' });
|
|
300
|
+
if (alert.destIp) indicators.push({ type: 'ip', value: alert.destIp, context: 'destination' });
|
|
301
|
+
if (alert.fileHash) indicators.push({ type: 'hash', value: alert.fileHash, context: 'file' });
|
|
302
|
+
if (alert.hostname) indicators.push({ type: 'hostname', value: alert.hostname, context: 'endpoint' });
|
|
303
|
+
if (alert.username) indicators.push({ type: 'username', value: alert.username, context: 'actor' });
|
|
304
|
+
if (alert.processName) indicators.push({ type: 'process', value: alert.processName, context: 'execution' });
|
|
305
|
+
|
|
306
|
+
return indicators;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
module.exports = { parseAlert };
|