@ordojs/security
Version:
Security package for OrdoJS with XSS, CSRF, and injection protection
205 lines (175 loc) • 5.89 kB
text/typescript
/**
* SQL Injection Prevention
* Provides comprehensive protection against SQL injection attacks
*/
import type { SqlInjectionPattern } from './types';
export class SqlInjectionPrevention {
private static readonly SQL_INJECTION_PATTERNS: SqlInjectionPattern[] = [
// Union-based injection patterns
{
pattern: /(\bunion\b.*\bselect\b)|(\bselect\b.*\bunion\b)/gi,
severity: 'critical',
description: 'UNION-based SQL injection attempt detected'
},
// Boolean-based blind injection
{
pattern: /(\b(and|or)\b\s*\d+\s*[=<>]\s*\d+)|(\b(and|or)\b\s*['"]?\w+['"]?\s*[=<>]\s*['"]?\w+['"]?)/gi,
severity: 'high',
description: 'Boolean-based blind SQL injection attempt detected'
},
// Time-based blind injection
{
pattern: /\b(sleep|waitfor|delay|benchmark)\s*\(/gi,
severity: 'critical',
description: 'Time-based blind SQL injection attempt detected'
},
// Error-based injection
{
pattern: /\b(extractvalue|updatexml|exp|floor|rand)\s*\(/gi,
severity: 'high',
description: 'Error-based SQL injection attempt detected'
},
// Stacked queries
{
pattern: /;\s*(drop|delete|insert|update|create|alter|exec|execute)\b/gi,
severity: 'critical',
description: 'Stacked query SQL injection attempt detected'
},
// Comment-based evasion
{
pattern: /(\/\*.*?\*\/)|(--.*)|(#.*)/g,
severity: 'medium',
description: 'SQL comment-based evasion attempt detected'
},
// Information schema access
{
pattern: /\binformation_schema\b/gi,
severity: 'high',
description: 'Information schema access attempt detected'
},
// System function calls
{
pattern: /\b(xp_cmdshell|sp_oacreate|sp_oamethod|openrowset|opendatasource)\b/gi,
severity: 'critical',
description: 'System function SQL injection attempt detected'
},
// Hex encoding evasion
{
pattern: /0x[0-9a-f]+/gi,
severity: 'medium',
description: 'Hex-encoded SQL injection attempt detected'
},
// Concatenation-based evasion
{
pattern: /\bconcat\s*\(|(\|\|)|(\+\s*['"])/gi,
severity: 'medium',
description: 'Concatenation-based SQL injection attempt detected'
}
];
/**
* Validates input for SQL injection patterns
*/
public static validateInput(input: string): { isValid: boolean; threats: SqlInjectionPattern[] } {
if (typeof input !== 'string') {
return { isValid: true, threats: [] };
}
const detectedThreats: SqlInjectionPattern[] = [];
for (const pattern of this.SQL_INJECTION_PATTERNS) {
if (pattern.pattern.test(input)) {
detectedThreats.push(pattern);
}
}
return {
isValid: detectedThreats.length === 0,
threats: detectedThreats
};
}
/**
* Sanitizes input by removing or escaping SQL injection patterns
*/
public static sanitizeInput(input: string, options: { strict?: boolean } = {}): string {
if (typeof input !== 'string') {
return String(input);
}
let sanitized = input;
if (options.strict) {
// In strict mode, remove any detected patterns entirely
for (const pattern of this.SQL_INJECTION_PATTERNS) {
sanitized = sanitized.replace(pattern.pattern, '');
}
} else {
// In normal mode, escape dangerous characters
sanitized = sanitized
.replace(/'/g, "''") // Escape single quotes
.replace(/"/g, '""') // Escape double quotes
.replace(/\\/g, '\\\\') // Escape backslashes
.replace(/;/g, '\\;') // Escape semicolons
.replace(/--/g, '\\-\\-') // Escape SQL comments
.replace(/\/\*/g, '\\/\\*') // Escape block comments
.replace(/\*\//g, '\\*\\/'); // Escape block comments
}
return sanitized.trim();
}
/**
* Creates a parameterized query-safe version of input
*/
public static createSafeParameter(input: any): any {
if (typeof input === 'string') {
return this.sanitizeInput(input, { strict: true });
}
if (typeof input === 'number' || typeof input === 'boolean') {
return input;
}
if (input === null || input === undefined) {
return null;
}
if (Array.isArray(input)) {
return input.map(item => this.createSafeParameter(item));
}
if (typeof input === 'object') {
const safeObject: Record<string, any> = {};
for (const [key, value] of Object.entries(input)) {
safeObject[this.sanitizeInput(key)] = this.createSafeParameter(value);
}
return safeObject;
}
return String(input);
}
/**
* Validates and sanitizes multiple inputs
*/
public static validateAndSanitizeInputs(inputs: Record<string, any>): {
isValid: boolean;
threats: Array<{ field: string; threats: SqlInjectionPattern[] }>;
sanitizedInputs: Record<string, any>;
} {
const allThreats: Array<{ field: string; threats: SqlInjectionPattern[] }> = [];
const sanitizedInputs: Record<string, any> = {};
for (const [field, value] of Object.entries(inputs)) {
try {
const sanitized = this.createSafeParameter(value);
sanitizedInputs[field] = sanitized;
if (typeof value === 'string') {
const validation = this.validateInput(value);
if (!validation.isValid) {
allThreats.push({ field, threats: validation.threats });
}
}
} catch (error) {
allThreats.push({
field,
threats: [{
pattern: /.*/,
severity: 'critical',
description: error instanceof Error ? error.message : 'Unknown SQL injection threat'
}]
});
}
}
return {
isValid: allThreats.length === 0,
threats: allThreats,
sanitizedInputs
};
}
}