Script Injection Prevention & Detection
What This Means
Script injection is a security vulnerability where attackers insert malicious JavaScript code into your website, either through cross-site scripting (XSS) attacks, compromised third-party scripts, or unauthorized modifications to your codebase. These injected scripts can steal user data, redirect traffic, inject ads, install malware, or compromise your entire website. Prevention and detection are critical for protecting your users and business.
Types of Script Injection
Cross-Site Scripting (XSS):
- Stored XSS: Malicious script saved in database (comments, user profiles)
- Reflected XSS: Script in URL parameters reflected back to user
- DOM-based XSS: Client-side JavaScript manipulates DOM unsafely
- Mutation XSS (mXSS): Browser parsing creates executable code
Third-Party Script Compromise:
- CDN compromise (supply chain attack)
- Compromised analytics/advertising tags
- Malicious browser extensions
- Magecart/formjacking attacks
- Cryptojacking scripts
Server Compromise:
- Database injection
- File system modification
- Admin panel breach
- Backdoor scripts
- Web shell injection
Impact on Your Business
Security Risks:
- User session hijacking
- Credential theft
- Payment card data theft (PCI compliance violation)
- Customer data breach (GDPR violation)
- Defacement and reputation damage
- Malware distribution
Business Consequences:
- Average data breach cost: $4.45M (IBM 2023)
- PCI fines: $5,000-$100,000/month
- GDPR fines: Up to €20M or 4% revenue
- Customer trust loss
- Revenue loss during incident
- Legal liability
- SEO penalties (malware warnings)
User Experience Impact:
- Redirects to phishing sites
- Unwanted ads and popups
- Slow page performance
- Browser security warnings
- Loss of user trust
How to Diagnose
Method 1: Content Security Policy (CSP) Violation Reports
Monitor CSP violations:
Check browser console:
Content Security Policy violation: Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'"Set up CSP reporting endpoint:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://trusted-cdn.com; report-uri /csp-report">// Server endpoint to receive reports app.post('/csp-report', (req, res) => { const report = req.body; console.log('CSP Violation:', report); // Log to security monitoring securityLogger.warn('CSP violation detected', { blockedURI: report['csp-report']['blocked-uri'], violatedDirective: report['csp-report']['violated-directive'], documentURI: report['csp-report']['document-uri'] }); res.sendStatus(204); });
What to Look For:
- Unexpected inline scripts
- Unauthorized third-party domains
- Eval() or Function() constructor usage
- Unknown script sources
Method 2: Subresource Integrity (SRI) Monitoring
Detect modified third-party scripts:
<!-- Script with SRI hash -->
<script src="https://cdn.example.com/library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
Monitor console for SRI failures:
Failed to find a valid digest in the 'integrity' attribute for resource
'https://cdn.example.com/library.js' with computed SHA-384 integrity...
What to Look For:
- SRI hash mismatch errors
- Modified third-party libraries
- Compromised CDN resources
- Man-in-the-middle attacks
Method 3: Regular Script Audits
Automated scanning:
# Scan for suspicious patterns in JavaScript
grep -r "eval(" ./public/js/
grep -r "document.write" ./public/js/
grep -r "innerHTML.*=" ./public/js/
grep -r "atob\|btoa" ./public/js/
grep -r "fromCharCode" ./public/js/
grep -r "createElement('script')" ./public/js/
Check for unauthorized scripts:
// List all script tags on page
const scripts = Array.from(document.querySelectorAll('script'));
scripts.forEach(script => {
console.log({
src: script.src || '(inline)',
integrity: script.integrity || '(none)',
async: script.async,
defer: script.defer,
content: script.src ? null : script.textContent.substring(0, 100)
});
});
What to Look For:
- Unknown script sources
- Obfuscated code
- Base64 encoded scripts
- Dynamic script injection
- Suspicious domains
Method 4: Browser DevTools Security Tab
- Open Chrome DevTools (
F12) - Navigate to "Security" tab
- Review security status
What to Look For:
- Mixed content warnings
- Certificate issues
- Insecure origins
- Security warnings
Method 5: Third-Party Security Scanners
Use automated tools:
Google Safe Browsing:
https://transparencyreport.google.com/safe-browsing/search?url=yoursite.comSucuri SiteCheck:
https://sitecheck.sucuri.net/VirusTotal:
https://www.virustotal.com/gui/url/yoursite.com
What to Look For:
- Malware detection
- Blacklist status
- Suspicious resources
- Injected content
- Phishing indicators
General Fixes
Fix 1: Implement Strict Content Security Policy
Block unauthorized scripts:
Basic CSP (start restrictive):
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://trusted-cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';">Nginx configuration:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted-cdn.com; object-src 'none'; base-uri 'self';" always;Apache configuration:
Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted-cdn.com; object-src 'none'"Progressive CSP with nonces:
<!-- Generate random nonce server-side --> <?php $nonce = base64_encode(random_bytes(16)); ?> <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-<?php echo $nonce; ?>'"> <!-- Inline scripts require nonce --> <script nonce="<?php echo $nonce; ?>"> console.log('Allowed inline script'); </script>
Fix 2: Sanitize User Input
Prevent XSS through input validation:
Server-side input sanitization:
// Node.js with DOMPurify const createDOMPurify = require('dompurify'); const { JSDOM } = require('jsdom'); const window = new JSDOM('').window; const DOMPurify = createDOMPurify(window); app.post('/comment', (req, res) => { const userComment = req.body.comment; // Sanitize HTML const cleanComment = DOMPurify.sanitize(userComment, { ALLOWED_TAGS: ['p', 'br', 'strong', 'em'], ALLOWED_ATTR: [] }); // Save to database await db.comments.insert({ comment: cleanComment }); res.json({ success: true }); });Client-side output encoding:
// Escape HTML entities function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // Use when displaying user content const userInput = '<script>alert("XSS")</script>'; element.textContent = userInput; // Safe // or element.innerHTML = escapeHtml(userInput); // SafeUse textContent instead of innerHTML:
// Unsafe element.innerHTML = userInput; ❌ // Safe element.textContent = userInput; ✓React automatic escaping:
// React escapes by default function Comment({ text }) { return <div>{text}</div>; // Automatically escaped } // Dangerous (avoid) function UnsafeComment({ html }) { return <div dangerouslySetInnerHTML={{__html: html}} />; ❌ }
Fix 3: Use Subresource Integrity (SRI)
Verify third-party script integrity:
Generate SRI hashes:
# Generate SHA-384 hash curl -s https://cdn.example.com/library.js | \ openssl dgst -sha384 -binary | \ openssl base64 -AOr use online tool: https://www.srihash.org/
Add integrity attribute:
<script src="https://cdn.example.com/library.js" integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" crossorigin="anonymous"></script> <link rel="stylesheet" href="https://cdn.example.com/styles.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">Automated SRI in build tools:
// webpack.config.js const SriPlugin = require('webpack-subresource-integrity'); module.exports = { output: { crossOriginLoading: 'anonymous' }, plugins: [ new SriPlugin({ hashFuncNames: ['sha256', 'sha384'], enabled: process.env.NODE_ENV === 'production' }) ] };
Fix 4: Implement Script Monitoring
Detect unauthorized scripts at runtime:
Monitor new scripts:
// Detect dynamically added scripts const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.tagName === 'SCRIPT') { const src = node.src || '(inline)'; // Check against whitelist const allowedDomains = [ 'https://www.googletagmanager.com', 'https://www.google-analytics.com', 'https://cdn.yoursite.com' ]; const isAllowed = allowedDomains.some(domain => src.startsWith(domain) ) || src === '(inline)'; if (!isAllowed) { console.error('Unauthorized script detected:', src); // Remove script node.remove(); // Alert security team reportSecurityIncident('unauthorized_script', { src }); } } }); }); }); observer.observe(document.documentElement, { childList: true, subtree: true });Monitor script execution:
// Override eval and Function constructor (function() { const originalEval = window.eval; window.eval = function(...args) { console.warn('eval() called:', args[0].substring(0, 100)); reportSecurityIncident('eval_usage', { code: args[0] }); // Optionally block // throw new Error('eval() is not allowed'); return originalEval.apply(this, args); }; const OriginalFunction = window.Function; window.Function = function(...args) { console.warn('Function constructor called'); reportSecurityIncident('function_constructor_usage'); return OriginalFunction.apply(this, args); }; })();
Fix 5: Validate and Sanitize URLs
Prevent malicious redirects:
URL validation:
function isSafeUrl(url) { try { const parsed = new URL(url, window.location.origin); // Only allow https and relative URLs if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') { return false; } // Block javascript: and data: protocols if (parsed.protocol === 'javascript:' || parsed.protocol === 'data:') { return false; } // Whitelist allowed domains const allowedDomains = ['yoursite.com', 'trusted-partner.com']; const isAllowedDomain = allowedDomains.some(domain => parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`) ); return isAllowedDomain; } catch (e) { return false; } } // Usage const redirectUrl = getParameterByName('redirect'); if (isSafeUrl(redirectUrl)) { window.location.href = redirectUrl; } else { console.error('Unsafe redirect prevented:', redirectUrl); }Sanitize href attributes:
// Check all links document.querySelectorAll('a').forEach(link => { if (link.href.startsWith('javascript:')) { console.error('Malicious link detected:', link.href); link.removeAttribute('href'); } });
Fix 6: Regular Security Audits
Proactive detection:
Automated daily scans:
#!/bin/bash # security-scan.sh # Check for suspicious patterns find ./public -name "*.js" -exec grep -l "eval(" {} \; > /tmp/eval_usage.txt find ./public -name "*.js" -exec grep -l "atob(" {} \; > /tmp/atob_usage.txt # Compare against baseline diff /tmp/eval_usage.txt /var/security/baseline_eval.txt > /tmp/new_eval.txt if [ -s /tmp/new_eval.txt ]; then echo "New eval() usage detected!" | mail -s "Security Alert" security@yoursite.com fi # Scan for known malware signatures clamscan -r ./public --infected --log=/var/log/clamav/scan.logHash verification:
// Generate checksums of critical files const crypto = require('crypto'); const fs = require('fs'); async function verifyFileIntegrity() { const criticalFiles = [ '/public/js/main.js', '/public/js/checkout.js', '/public/js/analytics.js' ]; const knownHashes = { '/public/js/main.js': 'abc123...', '/public/js/checkout.js': 'def456...', '/public/js/analytics.js': 'ghi789...' }; for (const file of criticalFiles) { const content = fs.readFileSync(file); const hash = crypto.createHash('sha256').update(content).digest('hex'); if (hash !== knownHashes[file]) { console.error(`File modified: ${file}`); sendSecurityAlert(`Unauthorized modification: ${file}`); } } } // Run daily setInterval(verifyFileIntegrity, 24 * 60 * 60 * 1000);
Fix 7: Implement Security Headers
Additional protection layers:
# Nginx configuration
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Strict Transport Security
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Or in Apache:
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "DENY"
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Platform-Specific Guides
Detailed implementation instructions for your specific platform:
Verification
After implementing script injection protection:
Test CSP:
- Try adding inline script (should be blocked)
- Check console for violations
- Verify authorized scripts work
Test XSS protection:
// Try common XSS payloads (in test environment) const xssPayloads = [ '<script>alert("XSS")</script>', '<img src=x onerror=alert("XSS")>', 'javascript:alert("XSS")', '<svg onload=alert("XSS")>' ]; xssPayloads.forEach(payload => { // Verify they don't execute });Verify SRI:
- Modify third-party script
- Browser should block loading
- Console shows SRI error
Run security scan:
- No malware detected
- No unauthorized scripts
- All scripts have SRI hashes
Common Mistakes
- Too permissive CSP - Allowing 'unsafe-inline' and 'unsafe-eval'
- Not sanitizing user input - Trusting client-side validation only
- Using innerHTML - Instead of textContent for user content
- Missing SRI - Third-party scripts without integrity checks
- Not monitoring violations - CSP reports ignored
- Trusting all same-origin scripts - Internal compromise possible
- Not updating dependencies - Vulnerable libraries
- Weak authentication - Admin panels easily compromised
- No security audits - Scripts go unmonitored
- Ignoring security headers - Missing defense layers