187 lines
5.7 KiB
JavaScript
187 lines
5.7 KiB
JavaScript
const config = require('config');
|
|
const logger = require('../utils/logger');
|
|
|
|
/**
|
|
* User-Agent Filtering Middleware - Equivalent to Kamailio UA_FILTER route
|
|
* Provides security protection by filtering SIP requests based on User-Agent header
|
|
*/
|
|
class UserAgentFilter {
|
|
constructor() {
|
|
this.blockedPatterns = config.get('security.blockedUserAgents');
|
|
this.allowedPatterns = config.get('security.allowedUserAgents');
|
|
this.enabled = config.get('security.enableUAFilter');
|
|
}
|
|
|
|
/**
|
|
* Extract User-Agent header from SIP request
|
|
*/
|
|
getUserAgent(req) {
|
|
return req.get('User-Agent') || '';
|
|
}
|
|
|
|
/**
|
|
* Block requests with no User-Agent header (equivalent to kamailio.cfg:425-429)
|
|
*/
|
|
checkEmptyUserAgent(userAgent, req, res) {
|
|
if (!userAgent || userAgent.trim() === '') {
|
|
logger.warn('[UA_FILTER] Blocked request with empty User-Agent from %s:%s | Method: %s | Call-ID: %s',
|
|
req.source_address, req.source_port, req.method, req.get('Call-ID'));
|
|
res.send(403, 'Forbidden - User-Agent header required');
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Block known malicious scanners (equivalent to kamailio.cfg:438-442)
|
|
*/
|
|
checkBlockedPatterns(userAgent, req, res) {
|
|
const userAgentLower = userAgent.toLowerCase();
|
|
|
|
for (const pattern of this.blockedPatterns) {
|
|
if (userAgentLower.includes(pattern.toLowerCase())) {
|
|
logger.warn('[UA_FILTER] Blocked malicious scanner User-Agent: \'%s\' from %s:%s | Method: %s | Call-ID: %s',
|
|
userAgent, req.source_address, req.source_port, req.method, req.get('Call-ID'));
|
|
res.send(403, 'Forbidden - Access denied');
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Block User-Agents containing attack tool keywords (equivalent to kamailio.cfg:446-450)
|
|
*/
|
|
checkAttackToolPatterns(userAgent, req, res) {
|
|
const attackPatterns = [
|
|
'scanner', 'vicious', 'warvox', 'sipdic', 'sip-scan',
|
|
'brute', 'attack', 'exploit', 'flood', 'dos'
|
|
];
|
|
|
|
const userAgentLower = userAgent.toLowerCase();
|
|
|
|
for (const pattern of attackPatterns) {
|
|
if (userAgentLower.includes(pattern)) {
|
|
logger.warn('[UA_FILTER] Blocked attack tool User-Agent: \'%s\' from %s:%s | Method: %s | Call-ID: %s',
|
|
userAgent, req.source_address, req.source_port, req.method, req.get('Call-ID'));
|
|
res.send(403, 'Forbidden - Access denied');
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Block User-Agents with only numbers/special characters (equivalent to kamailio.cfg:461-465)
|
|
*/
|
|
checkNumericOnlyUserAgent(userAgent, req, res) {
|
|
if (userAgent.length > 1 && /^[0-9\-\._\s]+$/.test(userAgent)) {
|
|
logger.warn('[UA_FILTER] Blocked numeric/special-char-only User-Agent: \'%s\' from %s:%s | Method: %s | Call-ID: %s',
|
|
userAgent, req.source_address, req.source_port, req.method, req.get('Call-ID'));
|
|
res.send(403, 'Forbidden - Invalid User-Agent');
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check allowlist for legitimate SIP clients (equivalent to kamailio.cfg:469-472)
|
|
*/
|
|
checkAllowlist(userAgent, req, res) {
|
|
const userAgentLower = userAgent.toLowerCase();
|
|
|
|
for (const pattern of this.allowedPatterns) {
|
|
if (userAgentLower.includes(pattern.toLowerCase())) {
|
|
logger.info('[UA_FILTER] Allowed legitimate SIP client: \'%s\' from %s:%s | Method: %s | Call-ID: %s',
|
|
userAgent, req.source_address, req.source_port, req.method, req.get('Call-ID'));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check for carrier/provider infrastructure (equivalent to kamailio.cfg:477-480)
|
|
*/
|
|
checkCarrierPatterns(userAgent, req, res) {
|
|
const carrierPatterns = [
|
|
'carrier', 'provider', 'operator', 'core', 'gateway',
|
|
'trunk', 'pbx', 'sbc', 'session-border', 'voip'
|
|
];
|
|
|
|
const userAgentLower = userAgent.toLowerCase();
|
|
|
|
for (const pattern of carrierPatterns) {
|
|
if (userAgentLower.includes(pattern)) {
|
|
logger.info('[UA_FILTER] Allowed carrier/provider system: \'%s\' from %s:%s | Method: %s | Call-ID: %s',
|
|
userAgent, req.source_address, req.source_port, req.method, req.get('Call-ID'));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Log unknown but allowed User-Agents for monitoring (equivalent to kamailio.cfg:485)
|
|
*/
|
|
logUnknownUserAgent(userAgent, req) {
|
|
logger.info('[UA_FILTER] Unknown User-Agent allowed (for monitoring): \'%s\' from %s:%s | Method: %s | Call-ID: %s',
|
|
userAgent, req.source_address, req.source_port, req.method, req.get('Call-ID'));
|
|
}
|
|
|
|
/**
|
|
* Main filtering function that processes User-Agent checks
|
|
*/
|
|
filter(req, res, next) {
|
|
if (!this.enabled) {
|
|
return next();
|
|
}
|
|
|
|
const userAgent = this.getUserAgent(req);
|
|
|
|
// Check for empty User-Agent
|
|
if (this.checkEmptyUserAgent(userAgent, req, res)) {
|
|
return;
|
|
}
|
|
|
|
// Check blocked patterns
|
|
if (this.checkBlockedPatterns(userAgent, req, res)) {
|
|
return;
|
|
}
|
|
|
|
// Check attack tool patterns
|
|
if (this.checkAttackToolPatterns(userAgent, req, res)) {
|
|
return;
|
|
}
|
|
|
|
// Check numeric-only User-Agents
|
|
if (this.checkNumericOnlyUserAgent(userAgent, req, res)) {
|
|
return;
|
|
}
|
|
|
|
// Check allowlist
|
|
if (this.checkAllowlist(userAgent, req, res)) {
|
|
return next();
|
|
}
|
|
|
|
// Check carrier patterns
|
|
if (this.checkCarrierPatterns(userAgent, req, res)) {
|
|
return next();
|
|
}
|
|
|
|
// Unknown User-Agent - allow but log for monitoring
|
|
this.logUnknownUserAgent(userAgent, req);
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* Middleware function
|
|
*/
|
|
middleware() {
|
|
return (req, res, next) => {
|
|
this.filter(req, res, next);
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = UserAgentFilter; |