|
|
|
|
|
|
|
|
|
|
|
interface SecurityConfig { |
|
enableCSP: boolean; |
|
enableXSSProtection: boolean; |
|
enableRateLimiting: boolean; |
|
enableSecurityHeaders: boolean; |
|
enableIntegrityChecks: boolean; |
|
} |
|
|
|
interface SecurityThreat { |
|
type: string; |
|
severity: 'low' | 'medium' | 'high' | 'critical'; |
|
details: any; |
|
timestamp: number; |
|
mitigated: boolean; |
|
} |
|
|
|
export class SecurityEnhanced { |
|
private static instance: SecurityEnhanced; |
|
private config: SecurityConfig; |
|
private threats: SecurityThreat[] = []; |
|
private isEnabled = true; |
|
|
|
private constructor() { |
|
this.config = { |
|
enableCSP: true, |
|
enableXSSProtection: true, |
|
enableRateLimiting: true, |
|
enableSecurityHeaders: true, |
|
enableIntegrityChecks: true |
|
}; |
|
} |
|
|
|
static getInstance(): SecurityEnhanced { |
|
if (!SecurityEnhanced.instance) { |
|
SecurityEnhanced.instance = new SecurityEnhanced(); |
|
} |
|
return SecurityEnhanced.instance; |
|
} |
|
|
|
|
|
|
|
|
|
initialize(): void { |
|
if (!this.isEnabled) return; |
|
|
|
console.log('🛡️ Inicializando sistema de segurança avançado...'); |
|
|
|
this.setupContentSecurityPolicy(); |
|
this.setupSecurityHeaders(); |
|
this.setupXSSProtection(); |
|
this.setupIntegrityChecks(); |
|
this.setupThreatMonitoring(); |
|
this.setupSecureStorage(); |
|
} |
|
|
|
private setupContentSecurityPolicy(): void { |
|
if (!this.config.enableCSP) return; |
|
|
|
const csp = { |
|
'default-src': ["'self'"], |
|
'script-src': [ |
|
"'self'", |
|
"'unsafe-inline'", |
|
"'unsafe-eval'", |
|
'https://servicebus2.caixa.gov.br', |
|
'https://brasilapi.com.br' |
|
], |
|
'style-src': [ |
|
"'self'", |
|
"'unsafe-inline'", |
|
'https://fonts.googleapis.com' |
|
], |
|
'font-src': [ |
|
"'self'", |
|
'https://fonts.gstatic.com' |
|
], |
|
'connect-src': [ |
|
"'self'", |
|
'https://servicebus2.caixa.gov.br', |
|
'https://brasilapi.com.br', |
|
'https://api.loterias-caixa.com' |
|
], |
|
'img-src': [ |
|
"'self'", |
|
'data:', |
|
'https:' |
|
], |
|
'frame-ancestors': ["'none'"], |
|
'base-uri': ["'self'"], |
|
'form-action': ["'self'"] |
|
}; |
|
|
|
const cspString = Object.entries(csp) |
|
.map(([directive, sources]) => `${directive} ${sources.join(' ')}`) |
|
.join('; '); |
|
|
|
|
|
if (process.env.NODE_ENV === 'development') { |
|
console.log('🛡️ CSP configurado:', cspString); |
|
} |
|
} |
|
|
|
private setupSecurityHeaders(): void { |
|
if (!this.config.enableSecurityHeaders) return; |
|
|
|
|
|
const securityHeaders = { |
|
'X-Content-Type-Options': 'nosniff', |
|
'X-Frame-Options': 'DENY', |
|
'X-XSS-Protection': '1; mode=block', |
|
'Referrer-Policy': 'strict-origin-when-cross-origin', |
|
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()', |
|
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains' |
|
}; |
|
|
|
console.log('🛡️ Security headers configurados:', securityHeaders); |
|
} |
|
|
|
private setupXSSProtection(): void { |
|
if (!this.config.enableXSSProtection) return; |
|
|
|
|
|
this.monitorDOMChanges(); |
|
this.validateUserInputs(); |
|
this.sanitizeURLParameters(); |
|
} |
|
|
|
private monitorDOMChanges(): void { |
|
if (typeof window === 'undefined') return; |
|
|
|
const observer = new MutationObserver((mutations) => { |
|
mutations.forEach((mutation) => { |
|
mutation.addedNodes.forEach((node) => { |
|
if (node.nodeType === Node.ELEMENT_NODE) { |
|
const element = node as Element; |
|
|
|
|
|
if (element.tagName === 'SCRIPT') { |
|
const src = element.getAttribute('src'); |
|
const inline = element.textContent; |
|
|
|
if (src && !this.isAllowedScriptSource(src)) { |
|
this.recordThreat('unauthorized-script', 'high', { src }); |
|
element.remove(); |
|
} |
|
|
|
if (inline && this.containsSuspiciousCode(inline)) { |
|
this.recordThreat('suspicious-inline-script', 'high', { code: inline.substring(0, 100) }); |
|
element.remove(); |
|
} |
|
} |
|
|
|
|
|
if (element.tagName === 'IFRAME') { |
|
const src = element.getAttribute('src'); |
|
if (src && !this.isAllowedFrameSource(src)) { |
|
this.recordThreat('unauthorized-iframe', 'high', { src }); |
|
element.remove(); |
|
} |
|
} |
|
} |
|
}); |
|
}); |
|
}); |
|
|
|
observer.observe(document.body, { |
|
childList: true, |
|
subtree: true |
|
}); |
|
|
|
console.log('🛡️ Monitoramento de DOM ativo para XSS'); |
|
} |
|
|
|
private validateUserInputs(): void { |
|
|
|
document.addEventListener('input', (event) => { |
|
const target = event.target as HTMLInputElement; |
|
if (target && target.value) { |
|
const sanitized = this.sanitizeInput(target.value); |
|
if (sanitized !== target.value) { |
|
this.recordThreat('xss-attempt', 'medium', { |
|
original: target.value.substring(0, 100), |
|
sanitized: sanitized.substring(0, 100) |
|
}); |
|
target.value = sanitized; |
|
} |
|
} |
|
}); |
|
|
|
console.log('🛡️ Validação de inputs ativa'); |
|
} |
|
|
|
private sanitizeURLParameters(): void { |
|
if (typeof window === 'undefined') return; |
|
|
|
const params = new URLSearchParams(window.location.search); |
|
let hasThreats = false; |
|
|
|
params.forEach((value, key) => { |
|
const sanitized = this.sanitizeInput(value); |
|
if (sanitized !== value) { |
|
this.recordThreat('url-xss-attempt', 'medium', { |
|
parameter: key, |
|
original: value.substring(0, 100) |
|
}); |
|
params.set(key, sanitized); |
|
hasThreats = true; |
|
} |
|
}); |
|
|
|
if (hasThreats) { |
|
const newUrl = `${window.location.pathname}?${params.toString()}`; |
|
window.history.replaceState({}, '', newUrl); |
|
console.log('🛡️ Parâmetros URL sanitizados'); |
|
} |
|
} |
|
|
|
private setupIntegrityChecks(): void { |
|
if (!this.config.enableIntegrityChecks) return; |
|
|
|
|
|
const externalScripts = document.querySelectorAll('script[src]'); |
|
const externalStyles = document.querySelectorAll('link[rel="stylesheet"][href]'); |
|
|
|
Array.from(externalScripts).concat(Array.from(externalStyles)).forEach(element => { |
|
if (!element.hasAttribute('integrity')) { |
|
const src = element.getAttribute('src') || element.getAttribute('href'); |
|
if (src && this.isExternalResource(src)) { |
|
this.recordThreat('missing-integrity', 'low', { src }); |
|
} |
|
} |
|
}); |
|
|
|
console.log('🛡️ Verificação de integridade ativa'); |
|
} |
|
|
|
private setupThreatMonitoring(): void { |
|
|
|
this.monitorConsoleAccess(); |
|
this.monitorDevToolsUsage(); |
|
this.monitorSuspiciousNetworkActivity(); |
|
} |
|
|
|
private monitorConsoleAccess(): void { |
|
|
|
if (process.env.NODE_ENV === 'production') { |
|
const originalConsole = { ...console }; |
|
|
|
['log', 'warn', 'error', 'debug'].forEach(method => { |
|
(console as any)[method] = (...args: any[]) => { |
|
|
|
if (args.some(arg => |
|
typeof arg === 'string' && |
|
(arg.includes('eval') || arg.includes('Function') || arg.includes('script')) |
|
)) { |
|
this.recordThreat('suspicious-console-access', 'low', { args: args.slice(0, 3) }); |
|
} |
|
|
|
return (originalConsole as any)[method](...args); |
|
}; |
|
}); |
|
} |
|
} |
|
|
|
private monitorDevToolsUsage(): void { |
|
|
|
let devtools = { open: false }; |
|
|
|
setInterval(() => { |
|
const start = performance.now(); |
|
debugger; |
|
const end = performance.now(); |
|
|
|
if (end - start > 100) { |
|
if (!devtools.open) { |
|
devtools.open = true; |
|
this.recordThreat('devtools-opened', 'low', { timestamp: Date.now() }); |
|
} |
|
} else { |
|
devtools.open = false; |
|
} |
|
}, 1000); |
|
} |
|
|
|
private monitorSuspiciousNetworkActivity(): void { |
|
|
|
const originalFetch = window.fetch; |
|
|
|
window.fetch = async (...args) => { |
|
const url = args[0].toString(); |
|
|
|
|
|
if (this.isSuspiciousURL(url)) { |
|
this.recordThreat('suspicious-network-request', 'medium', { url }); |
|
throw new Error('Requisição bloqueada por segurança'); |
|
} |
|
|
|
return originalFetch(...args); |
|
}; |
|
} |
|
|
|
private setupSecureStorage(): void { |
|
|
|
const originalSetItem = localStorage.setItem; |
|
const originalGetItem = localStorage.getItem; |
|
|
|
localStorage.setItem = (key: string, value: string) => { |
|
|
|
if (this.isSensitiveData(key)) { |
|
value = this.encryptData(value); |
|
} |
|
return originalSetItem.call(localStorage, key, value); |
|
}; |
|
|
|
localStorage.getItem = (key: string) => { |
|
const value = originalGetItem.call(localStorage, key); |
|
if (value && this.isSensitiveData(key)) { |
|
return this.decryptData(value); |
|
} |
|
return value; |
|
}; |
|
|
|
console.log('🛡️ Armazenamento seguro configurado'); |
|
} |
|
|
|
private sanitizeInput(input: string): string { |
|
return input |
|
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') |
|
.replace(/javascript:/gi, '') |
|
.replace(/on\w+=/gi, '') |
|
.replace(/eval\(/gi, '') |
|
.replace(/Function\(/gi, '') |
|
.replace(/setTimeout\(/gi, '') |
|
.replace(/setInterval\(/gi, ''); |
|
} |
|
|
|
private containsSuspiciousCode(code: string): boolean { |
|
const suspiciousPatterns = [ |
|
/eval\s*\(/, |
|
/Function\s*\(/, |
|
/document\.write/, |
|
/innerHTML\s*=/, |
|
/outerHTML\s*=/, |
|
/javascript:/, |
|
/data:text\/html/, |
|
/execScript/, |
|
/setInterval.*eval/, |
|
/setTimeout.*eval/ |
|
]; |
|
|
|
return suspiciousPatterns.some(pattern => pattern.test(code)); |
|
} |
|
|
|
private isAllowedScriptSource(src: string): boolean { |
|
const allowedDomains = [ |
|
window.location.origin, |
|
'https://servicebus2.caixa.gov.br', |
|
'https://brasilapi.com.br', |
|
'https://fonts.googleapis.com', |
|
'https://fonts.gstatic.com' |
|
]; |
|
|
|
return allowedDomains.some(domain => src.startsWith(domain)); |
|
} |
|
|
|
private isAllowedFrameSource(src: string): boolean { |
|
|
|
return src.startsWith(window.location.origin); |
|
} |
|
|
|
private isExternalResource(src: string): boolean { |
|
return !src.startsWith(window.location.origin) && |
|
(src.startsWith('http://') || src.startsWith('https://')); |
|
} |
|
|
|
private isSuspiciousURL(url: string): boolean { |
|
const suspiciousPatterns = [ |
|
/bit\.ly/, |
|
/tinyurl/, |
|
/malware/, |
|
/phishing/, |
|
/\.tk$/, |
|
/\.ml$/ |
|
]; |
|
|
|
return suspiciousPatterns.some(pattern => pattern.test(url)); |
|
} |
|
|
|
private isSensitiveData(key: string): boolean { |
|
const sensitiveKeys = ['token', 'password', 'secret', 'key', 'auth']; |
|
return sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive)); |
|
} |
|
|
|
private encryptData(data: string): string { |
|
|
|
return btoa(encodeURIComponent(data)); |
|
} |
|
|
|
private decryptData(data: string): string { |
|
try { |
|
return decodeURIComponent(atob(data)); |
|
} catch { |
|
return data; |
|
} |
|
} |
|
|
|
private recordThreat(type: string, severity: SecurityThreat['severity'], details: any): void { |
|
const threat: SecurityThreat = { |
|
type, |
|
severity, |
|
details, |
|
timestamp: Date.now(), |
|
mitigated: false |
|
}; |
|
|
|
this.threats.push(threat); |
|
|
|
|
|
const emoji = severity === 'critical' ? '🚨' : severity === 'high' ? '⚠️' : '⚡'; |
|
console.warn(`${emoji} Ameaça ${severity}: ${type}`, details); |
|
|
|
|
|
this.mitigateThreat(threat); |
|
} |
|
|
|
private mitigateThreat(threat: SecurityThreat): void { |
|
switch (threat.type) { |
|
case 'unauthorized-script': |
|
case 'unauthorized-iframe': |
|
|
|
threat.mitigated = true; |
|
break; |
|
|
|
case 'xss-attempt': |
|
case 'url-xss-attempt': |
|
|
|
threat.mitigated = true; |
|
break; |
|
|
|
default: |
|
|
|
break; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
getSecurityReport(): { |
|
threatsDetected: number; |
|
threatsMitigated: number; |
|
recentThreats: SecurityThreat[]; |
|
recommendations: string[]; |
|
} { |
|
const recent = this.threats.filter(t => Date.now() - t.timestamp < 24 * 60 * 60 * 1000); |
|
const mitigated = this.threats.filter(t => t.mitigated).length; |
|
|
|
return { |
|
threatsDetected: this.threats.length, |
|
threatsMitigated: mitigated, |
|
recentThreats: recent, |
|
recommendations: this.generateSecurityRecommendations() |
|
}; |
|
} |
|
|
|
private generateSecurityRecommendations(): string[] { |
|
const recommendations = []; |
|
|
|
if (this.threats.some(t => t.type.includes('xss'))) { |
|
recommendations.push('🛡️ Implementar sanitização mais rigorosa de inputs'); |
|
} |
|
|
|
if (this.threats.some(t => t.type.includes('script'))) { |
|
recommendations.push('🔒 Revisar Content Security Policy'); |
|
} |
|
|
|
if (this.threats.some(t => t.severity === 'high' || t.severity === 'critical')) { |
|
recommendations.push('🚨 Revisar logs de segurança imediatamente'); |
|
} |
|
|
|
if (recommendations.length === 0) { |
|
recommendations.push('✅ Sistema seguro - nenhuma ameaça significativa detectada'); |
|
} |
|
|
|
return recommendations; |
|
} |
|
|
|
|
|
|
|
|
|
cleanup(): void { |
|
this.threats = []; |
|
console.log('🧹 Dados de segurança limpos'); |
|
} |
|
|
|
|
|
|
|
|
|
setEnabled(enabled: boolean): void { |
|
this.isEnabled = enabled; |
|
if (enabled) { |
|
this.initialize(); |
|
} |
|
} |
|
} |
|
|
|
|
|
export const securityEnhanced = SecurityEnhanced.getInstance(); |
|
|