UNPKG

mcp-sanitizer

Version:

Comprehensive security sanitization library for Model Context Protocol (MCP) servers with trusted security libraries

398 lines (343 loc) 10.3 kB
/** * SQL Injection Pattern Detection Module * * Detects patterns commonly used in SQL injection attacks, including * SQL keywords, union selects, comment attacks, and blind injection techniques. * * Based on security best practices from OWASP SQL Injection Prevention, * common SQL injection vectors, and database-specific attack patterns. */ const SEVERITY_LEVELS = { CRITICAL: 'critical', HIGH: 'high', MEDIUM: 'medium', LOW: 'low' }; /** * SQL keywords that are commonly used in injection attacks */ const SQL_KEYWORDS = [ // Data manipulation /\b(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER|TRUNCATE)\b/gi, // Data definition /\b(DATABASE|TABLE|INDEX|VIEW|PROCEDURE|FUNCTION|TRIGGER)\b/gi, // Access control /\b(GRANT|REVOKE|DENY)\b/gi, // System commands /\b(EXEC|EXECUTE|SHUTDOWN|BACKUP|RESTORE)\b/gi ]; /** * SQL injection attack patterns */ const INJECTION_PATTERNS = [ // Union-based attacks /\bUNION\s+(ALL\s+)?SELECT\b/gi, /\bUNION\s+.*\bFROM\b/gi, // Boolean-based blind injection /\b(AND|OR)\s+\d+\s*[=<>!]+\s*\d+/gi, /\b(AND|OR)\s+['"]?\w+['"]?\s*[=<>!]+\s*['"]?\w+['"]?/gi, /\b(AND|OR)\s+\d+\s*BETWEEN\s+\d+\s+AND\s+\d+/gi, // Time-based blind injection /\bWAITFOR\s+DELAY\s+['"]\d+:\d+:\d+['"]/gi, /\bSLEEP\s*\(\s*\d+\s*\)/gi, /\bBENCHMARK\s*\(\s*\d+\s*,/gi, /\bpg_sleep\s*\(\s*\d+\s*\)/gi, // Error-based injection /\bCONVERT\s*\(\s*int\s*,/gi, /\bCAST\s*\(\s*\w+\s+AS\s+int\s*\)/gi, /\bEXTRACTVALUE\s*\(/gi, /\bUPDATEXML\s*\(/gi, // Stacked queries /;\s*(INSERT|UPDATE|DELETE|DROP|CREATE|ALTER)/gi, /;\s*--/, /;\s*\/\*/ ]; /** * SQL comment patterns used to bypass filters */ const COMMENT_PATTERNS = [ /--\s*.*$/gm, // SQL line comments /\/\*[\s\S]*?\*\//g, // SQL block comments /\/\*.*$/gm, // Unclosed block comments /#.*$/gm, // MySQL comments /--\+.*$/gm, // Oracle hints /\/\*!\d+.*?\*\//g // MySQL version-specific comments ]; /** * Database-specific injection patterns */ const DATABASE_SPECIFIC_PATTERNS = { mysql: [ /\bINTO\s+OUTFILE\b/gi, // File operations /\bLOAD_FILE\s*\(/gi, /\bINTO\s+DUMPFILE\b/gi, /\bINFORMATION_SCHEMA\b/gi, // Schema enumeration /\bMYSQL\b.*\bUSER\b/gi, /\bVERSION\s*\(\s*\)/gi, /\bDATABASE\s*\(\s*\)/gi, /\bUSER\s*\(\s*\)/gi ], postgresql: [ /\bCOPY\s+.*\bTO\s+PROGRAM\b/gi, // Command execution /\bpg_read_file\s*\(/gi, // File operations /\bpg_ls_dir\s*\(/gi, /\bCURRENT_DATABASE\s*\(\s*\)/gi, // Schema enumeration /\bCURRENT_USER\s*\(\s*\)/gi, /\bVERSION\s*\(\s*\)/gi, /\bpg_sleep\s*\(/gi, // Time delays /\$\$[^$]*\$\$/g, // Dollar quoting ($$...$$) /\$([a-zA-Z_][a-zA-Z0-9_]*)\$[^$]*\$\1\$/g // Tagged dollar quotes ($tag$...$tag$) ], mssql: [ /\bxp_cmdshell\b/gi, // Command execution /\bsp_OACreate\b/gi, /\bsp_OAMethod\b/gi, /\bOPENROWSET\s*\(/gi, // Linked servers /\bOPENDATASOURCE\s*\(/gi, /\bSYS\.DATABASES\b/gi, // Schema enumeration /\bSYS\.TABLES\b/gi, /\bSYSTEM_USER\b/gi, /\bSUSER_SNAME\s*\(\s*\)/gi ], oracle: [ /\bUTL_FILE\b/gi, // File operations /\bUTL_HTTP\b/gi, /\bDBMS_XMLQUERY\b/gi, // XML operations /\bDBMS_XMLGEN\b/gi, /\bALL_TABLES\b/gi, // Schema enumeration /\bALL_TAB_COLUMNS\b/gi, /\bUSER_TABLES\b/gi, /\bSYS\.USER_OBJECTS\b/gi ], sqlite: [ /\bsqlite_master\b/gi, // Schema enumeration /\bsqlite_temp_master\b/gi, /\bPRAGMA\s+table_info\s*\(/gi, /\bPRAGMA\s+database_list\b/gi, /\bATTACH\s+DATABASE\b/gi // Database manipulation ] }; /** * Encoded SQL injection patterns */ const ENCODED_PATTERNS = [ /0x[0-9a-f]+/gi, // Hex encoding /CHAR\s*\(\s*\d+\s*\)/gi, // CHAR function encoding /CHR\s*\(\s*\d+\s*\)/gi, // CHR function encoding /ASCII\s*\(\s*\d+\s*\)/gi, // ASCII function /CONCAT\s*\(/gi, // String concatenation /\|\|/g, // String concatenation (Oracle/PostgreSQL) /\+.*\+/g // String concatenation (SQL Server) ]; /** * Bypass techniques */ const BYPASS_PATTERNS = [ /\s+/g, // Multiple spaces /\/\*.*?\*\//g, // Inline comments /\bunion\s*\/\*.*?\*\//gi, // Comment-separated keywords /\bselect\s*\/\*.*?\*\//gi, /['"]\s*\+\s*['"]/g, // Quote concatenation /['"]\s*\|\|\s*['"]/g, // Quote concatenation (Oracle/PostgreSQL) /\b\w+\s*\(\s*\)/g // Function calls without parameters ]; /** * Main detection function for SQL injection patterns * @param {string} input - The input string to analyze * @param {Object} options - Detection options * @returns {Object} Detection result with severity and details */ function detectSQLInjection (input, options = {}) { if (typeof input !== 'string') { return { detected: false, severity: null, patterns: [] }; } const detectedPatterns = []; let maxSeverity = null; // Check SQL keywords const keywordResult = checkSQLKeywords(input); if (keywordResult.detected) { detectedPatterns.push(...keywordResult.patterns); maxSeverity = getHigherSeverity(maxSeverity, keywordResult.severity); } // Check injection patterns const injectionResult = checkInjectionPatterns(input); if (injectionResult.detected) { detectedPatterns.push(...injectionResult.patterns); maxSeverity = getHigherSeverity(maxSeverity, injectionResult.severity); } // Check comment patterns const commentResult = checkCommentPatterns(input); if (commentResult.detected) { detectedPatterns.push(...commentResult.patterns); maxSeverity = getHigherSeverity(maxSeverity, commentResult.severity); } // Check database-specific patterns const dbSpecificResult = checkDatabaseSpecificPatterns(input); if (dbSpecificResult.detected) { detectedPatterns.push(...dbSpecificResult.patterns); maxSeverity = getHigherSeverity(maxSeverity, dbSpecificResult.severity); } // Check encoded patterns const encodedResult = checkEncodedPatterns(input); if (encodedResult.detected) { detectedPatterns.push(...encodedResult.patterns); maxSeverity = getHigherSeverity(maxSeverity, encodedResult.severity); } // Check bypass patterns const bypassResult = checkBypassPatterns(input); if (bypassResult.detected) { detectedPatterns.push(...bypassResult.patterns); maxSeverity = getHigherSeverity(maxSeverity, bypassResult.severity); } return { detected: detectedPatterns.length > 0, severity: maxSeverity, patterns: detectedPatterns, message: detectedPatterns.length > 0 ? `SQL injection patterns detected: ${detectedPatterns.join(', ')}` : null }; } /** * Check for dangerous SQL keywords */ function checkSQLKeywords (input) { const detected = []; for (const pattern of SQL_KEYWORDS) { if (pattern.test(input)) { detected.push(`sql_keyword:${pattern.source}`); } } return { detected: detected.length > 0, severity: detected.length > 0 ? SEVERITY_LEVELS.HIGH : null, patterns: detected }; } /** * Check for SQL injection attack patterns */ function checkInjectionPatterns (input) { const detected = []; for (const pattern of INJECTION_PATTERNS) { if (pattern.test(input)) { detected.push(`injection_pattern:${pattern.source}`); } } return { detected: detected.length > 0, severity: detected.length > 0 ? SEVERITY_LEVELS.CRITICAL : null, patterns: detected }; } /** * Check for SQL comment patterns */ function checkCommentPatterns (input) { const detected = []; for (const pattern of COMMENT_PATTERNS) { if (pattern.test(input)) { detected.push(`comment_pattern:${pattern.source}`); } } return { detected: detected.length > 0, severity: detected.length > 0 ? SEVERITY_LEVELS.MEDIUM : null, patterns: detected }; } /** * Check for database-specific patterns */ function checkDatabaseSpecificPatterns (input) { const detected = []; for (const [db, patterns] of Object.entries(DATABASE_SPECIFIC_PATTERNS)) { for (const pattern of patterns) { if (pattern.test(input)) { detected.push(`${db}_specific:${pattern.source}`); } } } return { detected: detected.length > 0, severity: detected.length > 0 ? SEVERITY_LEVELS.HIGH : null, patterns: detected }; } /** * Check for encoded SQL patterns */ function checkEncodedPatterns (input) { const detected = []; for (const pattern of ENCODED_PATTERNS) { if (pattern.test(input)) { detected.push(`encoded_pattern:${pattern.source}`); } } return { detected: detected.length > 0, severity: detected.length > 0 ? SEVERITY_LEVELS.MEDIUM : null, patterns: detected }; } /** * Check for bypass attempt patterns */ function checkBypassPatterns (input) { const detected = []; for (const pattern of BYPASS_PATTERNS) { if (pattern.test(input)) { detected.push(`bypass_pattern:${pattern.source}`); } } return { detected: detected.length > 0, severity: detected.length > 0 ? SEVERITY_LEVELS.MEDIUM : null, patterns: detected }; } /** * Get the higher severity between two severity levels */ function getHigherSeverity (current, newSeverity) { if (!current) return newSeverity; if (!newSeverity) return current; const severityOrder = [ SEVERITY_LEVELS.LOW, SEVERITY_LEVELS.MEDIUM, SEVERITY_LEVELS.HIGH, SEVERITY_LEVELS.CRITICAL ]; const currentIndex = severityOrder.indexOf(current); const newIndex = severityOrder.indexOf(newSeverity); return newIndex > currentIndex ? newSeverity : current; } /** * Simple boolean check for SQL injection * @param {string} input - The input string to check * @returns {boolean} True if SQL injection patterns are detected */ function isSQLInjection (input) { return detectSQLInjection(input).detected; } module.exports = { // Main detection functions detectSQLInjection, isSQLInjection, // Individual checkers checkSQLKeywords, checkInjectionPatterns, checkCommentPatterns, checkDatabaseSpecificPatterns, checkEncodedPatterns, checkBypassPatterns, // Pattern exports for reuse SQL_KEYWORDS, INJECTION_PATTERNS, COMMENT_PATTERNS, DATABASE_SPECIFIC_PATTERNS, ENCODED_PATTERNS, BYPASS_PATTERNS, // Constants SEVERITY_LEVELS };