565 lines
17 KiB
JavaScript
565 lines
17 KiB
JavaScript
/**
|
|
* Risk Signature Manager
|
|
* Predefined risk signatures for threat detection
|
|
* CWE-778 Compliance: Logs all signature matches and management operations
|
|
*/
|
|
|
|
const logger = require('./logger');
|
|
const logAggregator = require('./logAggregator');
|
|
const { db } = require('../database/db');
|
|
|
|
class RiskSignatureManager {
|
|
constructor() {
|
|
this.signatures = new Map();
|
|
this.initialize();
|
|
}
|
|
|
|
/**
|
|
* Initialize risk signature manager
|
|
*/
|
|
async initialize() {
|
|
await this.createSignaturesTable();
|
|
await this.loadSignatures();
|
|
|
|
logger.info('[RiskSignatureManager] Initialized with predefined risk signatures');
|
|
|
|
// Log initialization (CWE-778)
|
|
logAggregator.aggregate('risk_signature_manager', 'info', 'security', 'Risk signature manager initialized', {
|
|
totalSignatures: this.signatures.size
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create risk signatures table
|
|
*/
|
|
async createSignaturesTable() {
|
|
return new Promise((resolve, reject) => {
|
|
db.run(`
|
|
CREATE TABLE IF NOT EXISTS risk_signatures (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
signature_id TEXT UNIQUE NOT NULL,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
signature_type TEXT NOT NULL,
|
|
pattern TEXT NOT NULL,
|
|
match_type TEXT NOT NULL,
|
|
threat_level TEXT NOT NULL,
|
|
confidence REAL DEFAULT 0.8,
|
|
enabled INTEGER DEFAULT 1,
|
|
auto_block INTEGER DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`, async (err) => {
|
|
if (err) reject(err);
|
|
else {
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_signatures_type ON risk_signatures(signature_type, enabled)`);
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_signatures_threat ON risk_signatures(threat_level, enabled)`);
|
|
await this.createDefaultSignatures();
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create default risk signatures
|
|
*/
|
|
async createDefaultSignatures() {
|
|
const defaultSignatures = [
|
|
// IP-based signatures
|
|
{
|
|
signature_id: 'SIG-IP-TOR',
|
|
name: 'TOR Exit Node',
|
|
description: 'Known TOR exit node IP address',
|
|
signature_type: 'ip_address',
|
|
pattern: '(^10\\.\\d+\\.\\d+\\.\\d+|^172\\.(1[6-9]|2[0-9]|3[01])\\.\\d+\\.\\d+|^192\\.168\\.\\d+\\.\\d+)',
|
|
match_type: 'regex',
|
|
threat_level: 'high',
|
|
confidence: 0.9,
|
|
auto_block: 0
|
|
},
|
|
{
|
|
signature_id: 'SIG-IP-SUSPICIOUS',
|
|
name: 'Suspicious IP Range',
|
|
description: 'IP from suspicious geographic region',
|
|
signature_type: 'ip_address',
|
|
pattern: '',
|
|
match_type: 'custom',
|
|
threat_level: 'medium',
|
|
confidence: 0.7,
|
|
auto_block: 0
|
|
},
|
|
// User-agent signatures
|
|
{
|
|
signature_id: 'SIG-UA-BOT-MALICIOUS',
|
|
name: 'Malicious Bot User-Agent',
|
|
description: 'Known malicious bot signatures',
|
|
signature_type: 'user_agent',
|
|
pattern: '(scrapy|python-requests|curl|wget|nikto|sqlmap|havij|acunetix|nessus|openvas)',
|
|
match_type: 'regex_case_insensitive',
|
|
threat_level: 'high',
|
|
confidence: 0.95,
|
|
auto_block: 1
|
|
},
|
|
{
|
|
signature_id: 'SIG-UA-VULNERABILITY-SCANNER',
|
|
name: 'Vulnerability Scanner',
|
|
description: 'Automated vulnerability scanning tools',
|
|
signature_type: 'user_agent',
|
|
pattern: '(nmap|masscan|zap|burp|metasploit|w3af|arachni)',
|
|
match_type: 'regex_case_insensitive',
|
|
threat_level: 'critical',
|
|
confidence: 0.99,
|
|
auto_block: 1
|
|
},
|
|
// Attack pattern signatures
|
|
{
|
|
signature_id: 'SIG-ATTACK-SQL-INJECTION',
|
|
name: 'SQL Injection Pattern',
|
|
description: 'Common SQL injection attack patterns',
|
|
signature_type: 'attack_pattern',
|
|
pattern: '(union.*select|select.*from|insert.*into|delete.*from|drop.*table|exec.*xp_|script.*alert)',
|
|
match_type: 'regex_case_insensitive',
|
|
threat_level: 'critical',
|
|
confidence: 0.85,
|
|
auto_block: 1
|
|
},
|
|
{
|
|
signature_id: 'SIG-ATTACK-XSS',
|
|
name: 'Cross-Site Scripting Pattern',
|
|
description: 'XSS attack patterns',
|
|
signature_type: 'attack_pattern',
|
|
pattern: '(<script|javascript:|onerror=|onload=|<iframe|eval\\(|alert\\()',
|
|
match_type: 'regex_case_insensitive',
|
|
threat_level: 'high',
|
|
confidence: 0.8,
|
|
auto_block: 1
|
|
},
|
|
{
|
|
signature_id: 'SIG-ATTACK-PATH-TRAVERSAL',
|
|
name: 'Path Traversal Pattern',
|
|
description: 'Directory traversal attack patterns',
|
|
signature_type: 'attack_pattern',
|
|
pattern: '(\\.\\./|\\.\\.\\\\/|%2e%2e/|%252e%252e/)',
|
|
match_type: 'regex_case_insensitive',
|
|
threat_level: 'high',
|
|
confidence: 0.9,
|
|
auto_block: 1
|
|
},
|
|
{
|
|
signature_id: 'SIG-ATTACK-COMMAND-INJECTION',
|
|
name: 'Command Injection Pattern',
|
|
description: 'OS command injection patterns',
|
|
signature_type: 'attack_pattern',
|
|
pattern: '(;\\s*(rm|cat|ls|wget|curl|bash|sh|cmd|powershell)|\\|\\s*(nc|netcat))',
|
|
match_type: 'regex_case_insensitive',
|
|
threat_level: 'critical',
|
|
confidence: 0.95,
|
|
auto_block: 1
|
|
},
|
|
// Behavioral signatures
|
|
{
|
|
signature_id: 'SIG-BEHAVIOR-BRUTE-FORCE',
|
|
name: 'Brute Force Behavior',
|
|
description: 'Rapid repeated authentication attempts',
|
|
signature_type: 'behavior',
|
|
pattern: 'failed_login_rate',
|
|
match_type: 'custom',
|
|
threat_level: 'critical',
|
|
confidence: 0.9,
|
|
auto_block: 1
|
|
},
|
|
{
|
|
signature_id: 'SIG-BEHAVIOR-CREDENTIAL-STUFFING',
|
|
name: 'Credential Stuffing Behavior',
|
|
description: 'Multiple username attempts from single source',
|
|
signature_type: 'behavior',
|
|
pattern: 'unique_username_rate',
|
|
match_type: 'custom',
|
|
threat_level: 'high',
|
|
confidence: 0.85,
|
|
auto_block: 1
|
|
},
|
|
{
|
|
signature_id: 'SIG-BEHAVIOR-PRIVILEGE-ESC',
|
|
name: 'Privilege Escalation Behavior',
|
|
description: 'Repeated unauthorized access attempts',
|
|
signature_type: 'behavior',
|
|
pattern: 'authorization_failure_rate',
|
|
match_type: 'custom',
|
|
threat_level: 'critical',
|
|
confidence: 0.95,
|
|
auto_block: 1
|
|
},
|
|
{
|
|
signature_id: 'SIG-BEHAVIOR-DATA-EXFIL',
|
|
name: 'Data Exfiltration Behavior',
|
|
description: 'Unusual data download patterns',
|
|
signature_type: 'behavior',
|
|
pattern: 'download_volume_rate',
|
|
match_type: 'custom',
|
|
threat_level: 'high',
|
|
confidence: 0.8,
|
|
auto_block: 0
|
|
}
|
|
];
|
|
|
|
for (const signature of defaultSignatures) {
|
|
await new Promise((resolve, reject) => {
|
|
db.run(
|
|
`INSERT OR IGNORE INTO risk_signatures
|
|
(signature_id, name, description, signature_type, pattern, match_type, threat_level, confidence, auto_block)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
signature.signature_id,
|
|
signature.name,
|
|
signature.description,
|
|
signature.signature_type,
|
|
signature.pattern,
|
|
signature.match_type,
|
|
signature.threat_level,
|
|
signature.confidence,
|
|
signature.auto_block
|
|
],
|
|
(err) => {
|
|
if (err) reject(err);
|
|
else resolve();
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
logger.info(`[RiskSignatureManager] Created ${defaultSignatures.length} default signatures`);
|
|
}
|
|
|
|
/**
|
|
* Load signatures from database into memory
|
|
*/
|
|
async loadSignatures() {
|
|
return new Promise((resolve, reject) => {
|
|
db.all(
|
|
`SELECT * FROM risk_signatures WHERE enabled = 1`,
|
|
[],
|
|
(err, rows) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
this.signatures.clear();
|
|
rows.forEach(row => {
|
|
this.signatures.set(row.signature_id, row);
|
|
});
|
|
logger.info(`[RiskSignatureManager] Loaded ${rows.length} active signatures`);
|
|
resolve();
|
|
}
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Match input against risk signatures
|
|
* CWE-778: Logs all signature matches
|
|
*/
|
|
async matchSignatures(input, signatureType, context = {}) {
|
|
const matchingSignatures = Array.from(this.signatures.values()).filter(
|
|
s => s.signature_type === signatureType
|
|
);
|
|
|
|
if (matchingSignatures.length === 0) {
|
|
return { matched: false, signatures: [] };
|
|
}
|
|
|
|
const matches = [];
|
|
|
|
for (const signature of matchingSignatures) {
|
|
const matched = this.testPattern(input, signature.pattern, signature.match_type);
|
|
|
|
if (matched) {
|
|
matches.push({
|
|
...signature,
|
|
matchedInput: input,
|
|
context
|
|
});
|
|
|
|
// Log signature match (CWE-778)
|
|
logAggregator.aggregate('risk_signature_manager', 'warn', 'security', 'Risk signature matched', {
|
|
signatureId: signature.signature_id,
|
|
signatureName: signature.name,
|
|
signatureType,
|
|
threatLevel: signature.threat_level,
|
|
confidence: signature.confidence,
|
|
autoBlock: signature.auto_block === 1,
|
|
matchedInput: input.substring(0, 100), // Truncate for logging
|
|
context
|
|
});
|
|
|
|
logger.warn(`[RiskSignatureManager] Signature matched: ${signature.name} (${signature.threat_level})`);
|
|
}
|
|
}
|
|
|
|
return {
|
|
matched: matches.length > 0,
|
|
signatures: matches,
|
|
highestThreat: matches.length > 0 ? this.getHighestThreatLevel(matches) : null,
|
|
shouldAutoBlock: matches.some(m => m.auto_block === 1)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Test pattern against input
|
|
*/
|
|
testPattern(input, pattern, matchType) {
|
|
try {
|
|
switch (matchType) {
|
|
case 'regex':
|
|
return new RegExp(pattern).test(input);
|
|
case 'regex_case_insensitive':
|
|
return new RegExp(pattern, 'i').test(input);
|
|
case 'exact':
|
|
return input === pattern;
|
|
case 'contains':
|
|
return input.includes(pattern);
|
|
case 'custom':
|
|
// Custom patterns handled by specific detection methods
|
|
return false;
|
|
default:
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
logger.error(`[RiskSignatureManager] Pattern test error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get highest threat level from matches
|
|
*/
|
|
getHighestThreatLevel(matches) {
|
|
const threatLevels = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
let highest = 'low';
|
|
let highestScore = 0;
|
|
|
|
for (const match of matches) {
|
|
const score = threatLevels[match.threat_level] || 0;
|
|
if (score > highestScore) {
|
|
highestScore = score;
|
|
highest = match.threat_level;
|
|
}
|
|
}
|
|
|
|
return highest;
|
|
}
|
|
|
|
/**
|
|
* Get all signatures
|
|
*/
|
|
async getSignatures(filters = {}) {
|
|
const { signatureType, threatLevel, enabled, limit = 100 } = filters;
|
|
|
|
let whereClause = [];
|
|
let params = [];
|
|
|
|
if (signatureType) {
|
|
whereClause.push('signature_type = ?');
|
|
params.push(signatureType);
|
|
}
|
|
|
|
if (threatLevel) {
|
|
whereClause.push('threat_level = ?');
|
|
params.push(threatLevel);
|
|
}
|
|
|
|
if (enabled !== undefined) {
|
|
whereClause.push('enabled = ?');
|
|
params.push(enabled ? 1 : 0);
|
|
}
|
|
|
|
const where = whereClause.length > 0 ? `WHERE ${whereClause.join(' AND ')}` : '';
|
|
params.push(limit);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
db.all(
|
|
`SELECT * FROM risk_signatures ${where}
|
|
ORDER BY threat_level DESC, confidence DESC
|
|
LIMIT ?`,
|
|
params,
|
|
(err, rows) => {
|
|
if (err) reject(err);
|
|
else resolve(rows);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get signature by ID
|
|
*/
|
|
async getSignatureById(signatureId) {
|
|
return new Promise((resolve, reject) => {
|
|
db.get(
|
|
`SELECT * FROM risk_signatures WHERE signature_id = ?`,
|
|
[signatureId],
|
|
(err, row) => {
|
|
if (err) reject(err);
|
|
else resolve(row);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create new signature
|
|
* CWE-778: Logs signature creation
|
|
*/
|
|
async createSignature(data, userId) {
|
|
const signatureId = `SIG-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
db.run(
|
|
`INSERT INTO risk_signatures
|
|
(signature_id, name, description, signature_type, pattern, match_type, threat_level, confidence, enabled, auto_block)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
signatureId,
|
|
data.name,
|
|
data.description || '',
|
|
data.signature_type,
|
|
data.pattern,
|
|
data.match_type,
|
|
data.threat_level,
|
|
data.confidence || 0.8,
|
|
data.enabled !== undefined ? (data.enabled ? 1 : 0) : 1,
|
|
data.auto_block !== undefined ? (data.auto_block ? 1 : 0) : 0
|
|
],
|
|
async (err) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
await this.loadSignatures();
|
|
|
|
// Log signature creation (CWE-778)
|
|
logAggregator.aggregate('risk_signature_manager', 'info', 'security', 'Risk signature created', {
|
|
signatureId,
|
|
userId,
|
|
name: data.name,
|
|
signatureType: data.signature_type,
|
|
threatLevel: data.threat_level,
|
|
autoBlock: data.auto_block === 1
|
|
});
|
|
|
|
logger.info(`[RiskSignatureManager] Signature created: ${signatureId} by user ${userId}`);
|
|
resolve({ signatureId });
|
|
}
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update signature
|
|
* CWE-778: Logs signature modifications
|
|
*/
|
|
async updateSignature(signatureId, updates, userId) {
|
|
const allowedFields = ['name', 'description', 'pattern', 'match_type', 'threat_level', 'confidence', 'enabled', 'auto_block'];
|
|
const setClause = [];
|
|
const params = [];
|
|
|
|
for (const [key, value] of Object.entries(updates)) {
|
|
if (allowedFields.includes(key)) {
|
|
setClause.push(`${key} = ?`);
|
|
params.push((key === 'enabled' || key === 'auto_block') ? (value ? 1 : 0) : value);
|
|
}
|
|
}
|
|
|
|
if (setClause.length === 0) {
|
|
throw new Error('No valid fields to update');
|
|
}
|
|
|
|
setClause.push('updated_at = CURRENT_TIMESTAMP');
|
|
params.push(signatureId);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
db.run(
|
|
`UPDATE risk_signatures
|
|
SET ${setClause.join(', ')}
|
|
WHERE signature_id = ?`,
|
|
params,
|
|
async (err) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
await this.loadSignatures();
|
|
|
|
// Log signature update (CWE-778)
|
|
logAggregator.aggregate('risk_signature_manager', 'info', 'security', 'Risk signature updated', {
|
|
signatureId,
|
|
userId,
|
|
updates
|
|
});
|
|
|
|
logger.info(`[RiskSignatureManager] Signature updated: ${signatureId} by user ${userId}`);
|
|
resolve({ success: true });
|
|
}
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete signature
|
|
* CWE-778: Logs signature deletion
|
|
*/
|
|
async deleteSignature(signatureId, userId) {
|
|
return new Promise((resolve, reject) => {
|
|
db.run(
|
|
`DELETE FROM risk_signatures WHERE signature_id = ?`,
|
|
[signatureId],
|
|
async (err) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
await this.loadSignatures();
|
|
|
|
// Log signature deletion (CWE-778)
|
|
logAggregator.aggregate('risk_signature_manager', 'warn', 'security', 'Risk signature deleted', {
|
|
signatureId,
|
|
userId
|
|
});
|
|
|
|
logger.info(`[RiskSignatureManager] Signature deleted: ${signatureId} by user ${userId}`);
|
|
resolve({ success: true });
|
|
}
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get signature statistics
|
|
*/
|
|
async getStatistics() {
|
|
return new Promise((resolve, reject) => {
|
|
db.get(
|
|
`SELECT
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN enabled = 1 THEN 1 ELSE 0 END) as enabled,
|
|
SUM(CASE WHEN enabled = 0 THEN 1 ELSE 0 END) as disabled,
|
|
SUM(CASE WHEN auto_block = 1 THEN 1 ELSE 0 END) as auto_block_enabled,
|
|
COUNT(DISTINCT signature_type) as unique_types,
|
|
COUNT(DISTINCT threat_level) as unique_threat_levels
|
|
FROM risk_signatures`,
|
|
[],
|
|
(err, row) => {
|
|
if (err) reject(err);
|
|
else resolve(row);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Create singleton instance
|
|
const riskSignatureManager = new RiskSignatureManager();
|
|
|
|
module.exports = riskSignatureManager;
|