first commit
This commit is contained in:
commit
7988853b57
43 changed files with 8415 additions and 0 deletions
298
src/blocks/sigma_details_block.js
Normal file
298
src/blocks/sigma_details_block.js
Normal file
|
@ -0,0 +1,298 @@
|
|||
/**
|
||||
* sigma_details_block.js
|
||||
*
|
||||
* Creates Slack Block Kit blocks for displaying Sigma rule explanations
|
||||
*
|
||||
*/
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const { getFileName } = require('../utils/file_utils');
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
||||
/**
|
||||
* Create Slack block kit blocks for rule explanation
|
||||
*
|
||||
* @param {Object} details - The rule details object containing all rule metadata
|
||||
* @returns {Array} Formatted Slack blocks ready for display
|
||||
*/
|
||||
function getRuleExplanationBlocks(details) {
|
||||
logger.debug(`${FILE_NAME}: Creating rule explanation blocks for rule: ${details?.id || 'unknown'}`);
|
||||
|
||||
if (!details) {
|
||||
logger.error('Failed to create explanation blocks: No details object provided');
|
||||
return [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: 'Error: No explanation data provided'
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Map severity levels to emojis for visual representation
|
||||
const severityConfig = {
|
||||
'critical': { emoji: '🔴', text: 'Critical' },
|
||||
'high': { emoji: '🟠', text: 'High' },
|
||||
'medium': { emoji: '🟡', text: 'Medium' },
|
||||
'low': { emoji: '🟢', text: 'Low' },
|
||||
'informational': { emoji: '🔵', text: 'Info' }
|
||||
};
|
||||
|
||||
// Normalize severity to lowercase for matching
|
||||
const normalizedSeverity = (details.severity || '').toLowerCase();
|
||||
const severityInfo = severityConfig[normalizedSeverity] || { emoji: '⚪', text: details.severity || 'Unknown' };
|
||||
|
||||
// Create a formatted severity indicator
|
||||
const severityDisplay = `${severityInfo.emoji} *${severityInfo.text}*`;
|
||||
|
||||
logger.debug(`Rule severity: ${normalizedSeverity} (${severityDisplay})`);
|
||||
|
||||
/**
|
||||
* Format tags with MITRE links where applicable
|
||||
*
|
||||
* @param {Array} tags - Array of tag strings to format
|
||||
* @returns {Array} Array of formatted tags with links where appropriate
|
||||
*/
|
||||
const formatTags = (tags = []) => {
|
||||
if (!tags || tags.length === 0 || (tags.length === 2 && tags.includes('error'))) {
|
||||
logger.debug('No valid tags to format');
|
||||
return [];
|
||||
}
|
||||
|
||||
logger.debug(`Formatting ${tags.length} tags`);
|
||||
|
||||
return tags.map(tag => {
|
||||
let formattedTag = tag.trim();
|
||||
let url = '';
|
||||
let displayText = formattedTag;
|
||||
|
||||
// Handle MITRE ATT&CK Technique IDs
|
||||
if (/^T\d{4}(\.\d{3})?$/.test(formattedTag)) {
|
||||
// Technique ID (e.g., T1234 or T1234.001)
|
||||
url = `https://attack.mitre.org/techniques/${formattedTag}/`;
|
||||
logger.debug(`Formatted MITRE technique: ${formattedTag}`);
|
||||
}
|
||||
// Handle MITRE ATT&CK Tactic IDs
|
||||
else if (/^TA\d{4}$/.test(formattedTag)) {
|
||||
// Tactic ID (e.g., TA0001)
|
||||
url = `https://attack.mitre.org/tactics/${formattedTag}/`;
|
||||
logger.debug(`Formatted MITRE tactic: ${formattedTag}`);
|
||||
}
|
||||
// Handle CWE IDs
|
||||
else if (/^S\d{4}$/.test(formattedTag)) {
|
||||
// CWE ID
|
||||
url = `https://cwe.mitre.org/data/definitions/${formattedTag.substring(1)}.html`;
|
||||
logger.debug(`Formatted CWE: ${formattedTag}`);
|
||||
}
|
||||
// Handle attack.* tactics
|
||||
else if (formattedTag.startsWith('attack.')) {
|
||||
const tacticName = formattedTag.substring(7); // Remove 'attack.' prefix
|
||||
|
||||
// Handle specific techniques with T#### format
|
||||
if (/^t\d{4}(\.\d{3})?$/.test(tacticName)) {
|
||||
const techniqueId = tacticName.toUpperCase();
|
||||
url = `https://attack.mitre.org/techniques/${techniqueId}/`;
|
||||
displayText = techniqueId;
|
||||
logger.debug(`Formatted MITRE technique from attack. format: ${techniqueId}`);
|
||||
}
|
||||
// Handle regular tactics
|
||||
else {
|
||||
// Map common tactics to their MITRE ATT&CK IDs
|
||||
const tacticMappings = {
|
||||
'reconnaissance': 'TA0043',
|
||||
'resourcedevelopment': 'TA0042',
|
||||
'initialaccess': 'TA0001',
|
||||
'execution': 'TA0002',
|
||||
'persistence': 'TA0003',
|
||||
'privilegeescalation': 'TA0004',
|
||||
'defenseevasion': 'TA0005',
|
||||
'credentialaccess': 'TA0006',
|
||||
'discovery': 'TA0007',
|
||||
'lateralmovement': 'TA0008',
|
||||
'collection': 'TA0009',
|
||||
'command-and-control': 'TA0011',
|
||||
'exfiltration': 'TA0010',
|
||||
'impact': 'TA0040'
|
||||
};
|
||||
|
||||
// Remove hyphens and convert to lowercase for matching
|
||||
const normalizedTactic = tacticName.toLowerCase().replace(/-/g, '');
|
||||
|
||||
if (tacticMappings[normalizedTactic]) {
|
||||
url = `https://attack.mitre.org/tactics/${tacticMappings[normalizedTactic]}/`;
|
||||
logger.debug(`Mapped tactic ${tacticName} to ${tacticMappings[normalizedTactic]}`);
|
||||
} else {
|
||||
// If we don't have a specific mapping, try a search
|
||||
url = `https://attack.mitre.org/search/?q=${encodeURIComponent(tacticName)}`;
|
||||
logger.debug(`Created search URL for unmapped tactic: ${tacticName}`);
|
||||
}
|
||||
|
||||
// Format the display text with proper capitalization
|
||||
displayText = tacticName.replace(/-/g, ' ')
|
||||
.split(' ')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
}
|
||||
// Handle CVE IDs
|
||||
else if (/^CVE-\d{4}-\d{4,}$/i.test(formattedTag)) {
|
||||
url = `https://nvd.nist.gov/vuln/detail/${formattedTag.toUpperCase()}`;
|
||||
displayText = formattedTag.toUpperCase();
|
||||
logger.debug(`Formatted CVE: ${displayText}`);
|
||||
}
|
||||
|
||||
if (url) {
|
||||
return `<${url}|${displayText}>`;
|
||||
} else {
|
||||
return displayText;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Define header based on title - check if it contains error messages
|
||||
const isErrorMessage = details.title.toLowerCase().includes('error') ||
|
||||
details.title.toLowerCase().includes('missing');
|
||||
|
||||
if (isErrorMessage) {
|
||||
logger.warn(`Rule appears to have errors: ${details.title}`);
|
||||
}
|
||||
|
||||
// Start with header block
|
||||
const blocks = [
|
||||
{
|
||||
type: 'header',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: details.title || 'Rule Explanation',
|
||||
emoji: true
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'context',
|
||||
elements: [
|
||||
{
|
||||
type: 'mrkdwn',
|
||||
text: `ID: ${details.id || 'Unknown'}`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'section',
|
||||
fields: [
|
||||
{
|
||||
type: 'mrkdwn',
|
||||
text: `*Severity:* ${severityDisplay}`
|
||||
},
|
||||
{
|
||||
type: 'mrkdwn',
|
||||
text: `*Author:* ${details.author || 'Unknown'}`
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Add divider for visual separation
|
||||
blocks.push({ type: 'divider' });
|
||||
|
||||
// Add description section
|
||||
blocks.push({
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `*Description:*\n${details.description || 'No description available'}`
|
||||
}
|
||||
});
|
||||
|
||||
// Detection explanation section - only add if not an error case or has useful detection info
|
||||
if (!isErrorMessage || details.detectionExplanation !== 'Content missing') {
|
||||
blocks.push({
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `*What This Rule Detects:*\n${details.detectionExplanation || 'No detection information available'}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// False positives section - only add if not an error case with N/A values
|
||||
if (details.falsePositives && !details.falsePositives.includes('N/A - Content missing')) {
|
||||
const fpItems = Array.isArray(details.falsePositives)
|
||||
? details.falsePositives.map(item => `• ${item}`).join('\n')
|
||||
: `• ${details.falsePositives}`;
|
||||
|
||||
blocks.push({
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `*Possible False Positives:*\n${fpItems || 'None specified'}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add tags if they exist and are formatted
|
||||
const formattedTags = formatTags(details.tags);
|
||||
if (formattedTags.length > 0) {
|
||||
logger.debug(`Added ${formattedTags.length} formatted tags to the block`);
|
||||
blocks.push({
|
||||
type: 'context',
|
||||
elements: [
|
||||
{
|
||||
type: 'mrkdwn',
|
||||
text: `*Tags:* ${formattedTags.join(' | ')}`
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// If this is an error message, add a troubleshooting section
|
||||
if (isErrorMessage) {
|
||||
blocks.push({
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: ':warning: *Troubleshooting:*\nThis rule appears to have issues in the database. You may want to check the rule import process or run a database maintenance task to fix this issue.'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add action buttons for better interactivity
|
||||
blocks.push({
|
||||
type: 'actions',
|
||||
elements: [
|
||||
{
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'View YAML',
|
||||
emoji: true
|
||||
},
|
||||
action_id: 'view_yaml',
|
||||
value: `view_yaml_${details.id}`
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'Convert to SIEM Rule',
|
||||
emoji: true
|
||||
},
|
||||
action_id: 'convert_rule_to_siem',
|
||||
value: `convert_rule_to_siem_${details.id}`
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
// Add a divider at the end
|
||||
blocks.push({
|
||||
type: 'divider'
|
||||
});
|
||||
|
||||
logger.debug(`Created ${blocks.length} blocks for rule explanation`);
|
||||
return blocks;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getRuleExplanationBlocks
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue