Domain Spoofing & Referrer Validation
What This Means
Domain spoofing is when attackers send fake traffic to your analytics or APIs by impersonating your domain or manipulating referrer headers. This can pollute your analytics data with spam, steal attribution credit, manipulate conversion tracking, or exploit server-side processes that trust referrer information. Proper validation ensures you only accept legitimate traffic from your actual domains.
Types of Domain Spoofing
Analytics Referrer Spam:
- Fake referrers in Google Analytics
- Ghost referrals (no actual visit)
- Spam referrals driving fake traffic
- Manipulated UTM parameters
- Fake conversion events
Cross-Site Request Forgery (CSRF):
- Forged requests from malicious sites
- Unauthorized actions on behalf of users
- Form submissions from external domains
- API calls with spoofed origin
Attribution Fraud:
Hotlinking:
- Bandwidth theft
- Content theft
- Resource abuse
- Brand dilution
Impact on Your Business
Analytics Pollution:
- 20-40% of traffic can be spam/bots
- Inaccurate conversion data
- Wasted marketing budget on fake traffic
- Poor business decisions from bad data
- Inflated bounce rates
Security Risks:
- CSRF attacks leading to unauthorized actions
- Data theft through API exploitation
- Account takeovers
- Financial fraud
Performance Impact:
- Server resources wasted on fake requests
- Bandwidth costs from hotlinking
- CDN overages
- Database pollution
Business Costs:
- Wasted ad spend on fake attribution
- Lost revenue from fraud
- Time spent cleaning data
- Compliance issues (GDPR, PCI)
How to Diagnose
Method 1: Google Analytics Referral Analysis
- Check referral sources:
- Navigate to Google Analytics
- Reports → Acquisition → Traffic Acquisition
- Look for suspicious referrers
Red flags:
semalt.com
darodar.com
buttons-for-website.com
get-free-traffic-now.com
.tk domains
.ga domains
Random strings (abc123xyz.com)
Check for ghost referrals:
- Look for referrers with:
- 100% bounce rate
- 0:00 session duration
- Single page per session
- No geographic data
Review hostname report:
- Admin → Data Settings → Data Filters
- Check hostname dimension
- Look for unexpected hostnames sending data
What to Look For:
- Your analytics tracking ID on other sites
- Unknown domains
- localhost or test domains
- Misspellings of your domain
Method 2: Server Log Analysis
Check raw server logs:
# Find suspicious referrers
grep "Referer:" /var/log/nginx/access.log | \
awk '{print $11}' | \
sort | uniq -c | sort -rn | head -20
# Check for unusual user agents
grep "User-Agent:" /var/log/nginx/access.log | \
awk -F'"' '{print $6}' | \
sort | uniq -c | sort -rn
What to Look For:
- High volume from unknown referrers
- Missing or suspicious user agents
- Patterns of automated requests
- Requests from datacenter IPs
Method 3: Referrer Header Inspection
Check incoming requests:
// Server-side (Node.js/Express)
app.use((req, res, next) => {
const referrer = req.get('Referer') || req.get('Referrer');
const origin = req.get('Origin');
const host = req.get('Host');
console.log({
referrer,
origin,
host,
ip: req.ip,
userAgent: req.get('User-Agent')
});
next();
});
What to Look For:
- Mismatched origin and referrer
- Missing referrer on form submissions
- Referrer from unexpected domains
- Direct access to protected endpoints
Method 4: Analytics Data Quality Check
Identify data anomalies:
// Check for suspicious patterns
const suspiciousPatterns = {
// 100% bounce with no duration
ghostReferral: session.bounceRate === 100 && session.avgDuration === 0,
// Single page per session
singlePage: session.pageviews === 1,
// No location data
noGeo: !session.country && !session.city,
// Suspicious referrer
spamReferrer: /semalt|darodar|buttons-for|free-traffic/.test(session.referrer),
// Invalid hostname
wrongHostname: !['yoursite.com', 'www.yoursite.com'].includes(session.hostname)
};
if (Object.values(suspiciousPatterns).some(v => v)) {
console.warn('Suspicious session detected:', session);
}
Method 5: CSRF Token Validation Check
Test your forms:
Submit form without token:
<!-- Remove or modify CSRF token --> <form method="POST" action="/submit"> <input type="text" name="data" value="test"> <!-- Missing or wrong CSRF token --> <button type="submit">Submit</button> </form>Submit from external domain:
- Create test page on different domain
- Attempt form submission
- Should be rejected
What to Look For:
- Forms accepting requests without CSRF token
- No origin/referrer validation
- Cross-origin requests succeeding
General Fixes
Fix 1: Validate Referrer and Origin Headers
Server-side validation:
Express.js middleware:
function validateReferrer(req, res, next) { const allowedDomains = [ 'https://yoursite.com', 'https://www.yoursite.com', 'https://staging.yoursite.com' ]; const referrer = req.get('Referer') || req.get('Referrer') || ''; const origin = req.get('Origin') || ''; // Check if request is from allowed domain const isValidReferrer = allowedDomains.some(domain => referrer.startsWith(domain) ); const isValidOrigin = allowedDomains.some(domain => origin === domain ); // Allow if either referrer or origin is valid // (referrer can be missing on direct navigation) if (!referrer && !origin) { // Direct navigation - may be legitimate next(); return; } if (isValidReferrer || isValidOrigin) { next(); } else { console.warn('Invalid referrer/origin:', { referrer, origin }); res.status(403).json({ error: 'Invalid referrer' }); } } // Apply to sensitive endpoints app.post('/api/checkout', validateReferrer, (req, res) => { // Process checkout });PHP validation:
<?php function validateReferrer() { $allowedDomains = [ 'https://yoursite.com', 'https://www.yoursite.com' ]; $referrer = $_SERVER['HTTP_REFERER'] ?? ''; $origin = $_SERVER['HTTP_ORIGIN'] ?? ''; foreach ($allowedDomains as $domain) { if (strpos($referrer, $domain) === 0 || $origin === $domain) { return true; } } return false; } if (!validateReferrer()) { http_response_code(403); die('Invalid referrer'); } ?>
Fix 2: Implement CSRF Protection
Add CSRF tokens to forms:
Generate CSRF tokens:
// Node.js with csurf middleware const csrf = require('csurf'); const csrfProtection = csrf({ cookie: true }); app.get('/form', csrfProtection, (req, res) => { res.render('form', { csrfToken: req.csrfToken() }); }); app.post('/form', csrfProtection, (req, res) => { // CSRF token automatically validated res.send('Form submitted successfully'); });Add token to forms:
<form method="POST" action="/submit"> <!-- Hidden CSRF token field --> <input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="text" name="data"> <button type="submit">Submit</button> </form>Include in AJAX requests:
// Get CSRF token from meta tag const csrfToken = document.querySelector('meta[name="csrf-token"]').content; // Include in fetch requests fetch('/api/data', { method: 'POST', headers: { 'Content-Type': 'application/json', 'CSRF-Token': csrfToken }, body: JSON.stringify({ data: 'value' }) });
Fix 3: Filter Analytics Spam
Google Analytics 4 filters:
Create data filter:
- Navigate to GA4 Admin → Data Settings → Data Filters
- Create filter to exclude spam
Filter by hostname:
// In GTM or on-page analytics // Only send to GA if hostname is valid const validHostnames = ['yoursite.com', 'www.yoursite.com']; if (validHostnames.includes(window.location.hostname)) { gtag('config', 'G-XXXXXXXXXX', { // Normal configuration }); } else { console.warn('Analytics blocked on invalid hostname:', window.location.hostname); }Bot filtering:
- GA4 Admin → Data Settings → Data Collection
- Enable "Exclude all hits from known bots and spiders"
Server-side validation before sending:
async function sendToAnalytics(data) { // Validate referrer before sending const validReferrers = [ 'yoursite.com', 'google.com', 'bing.com', 'facebook.com' ]; const referrer = document.referrer; const hostname = window.location.hostname; // Check for spam patterns const isSpam = /semalt|darodar|buttons-for|free-traffic/.test(referrer); if (isSpam || !validHostnames.includes(hostname)) { console.warn('Spam blocked:', referrer); return; } // Send to GA gtag('event', data.event, data.params); }
Fix 4: Implement SameSite Cookies
Prevent CSRF via cookies:
// Node.js/Express
app.use(session({
secret: 'your-secret-key',
cookie: {
httpOnly: true,
secure: true, // HTTPS only
sameSite: 'lax' // or 'strict'
}
}));
// Set individual cookies
res.cookie('session', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 86400000
});
SameSite values:
Strict- Never sent on cross-site requests (most secure)Lax- Sent on top-level navigation (good balance)None- Always sent (requiresSecure)
Fix 5: Validate Content-Type and Accept Headers
Ensure requests are legitimate:
function validateRequestHeaders(req, res, next) {
// Check Content-Type for POST/PUT
if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
const contentType = req.get('Content-Type') || '';
// Require JSON content type
if (!contentType.includes('application/json')) {
return res.status(415).json({
error: 'Invalid Content-Type. Expected application/json'
});
}
}
// Check Accept header
const accept = req.get('Accept') || '';
if (!accept.includes('application/json') && !accept.includes('*/*')) {
return res.status(406).json({
error: 'Invalid Accept header'
});
}
next();
}
app.use('/api/*', validateRequestHeaders);
Fix 6: Use CORS Properly
Control cross-origin access:
// Node.js/Express with CORS middleware
const cors = require('cors');
// Whitelist specific origins
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://yoursite.com',
'https://www.yoursite.com',
'https://app.yoursite.com'
];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'CSRF-Token']
};
app.use('/api', cors(corsOptions));
Or manual CORS headers:
app.use((req, res, next) => {
const allowedOrigins = ['https://yoursite.com', 'https://www.yoursite.com'];
const origin = req.get('Origin');
if (allowedOrigins.includes(origin)) {
res.set('Access-Control-Allow-Origin', origin);
res.set('Access-Control-Allow-Credentials', 'true');
res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
}
next();
});
Fix 7: Implement Rate Limiting
Prevent spam and abuse:
// Node.js/Express with rate-limit
const rateLimit = require('express-rate-limit');
// API rate limiting
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP',
standardHeaders: true,
legacyHeaders: false,
// Skip trusted IPs
skip: (req) => {
const trustedIPs = ['1.2.3.4', '5.6.7.8'];
return trustedIPs.includes(req.ip);
}
});
app.use('/api/', apiLimiter);
// Stricter limit for sensitive endpoints
const strictLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5
});
app.post('/api/checkout', strictLimiter, (req, res) => {
// Process checkout
});
Nginx rate limiting:
# Define rate limit zone
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# Apply to location
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://backend;
}
Fix 8: Monitor and Alert on Suspicious Activity
Real-time monitoring:
// Track suspicious patterns
const suspiciousActivity = new Map();
function monitorRequest(req) {
const ip = req.ip;
const referrer = req.get('Referer') || '';
// Check for spam patterns
const isSpamReferrer = /semalt|darodar|buttons-for/.test(referrer);
const isInvalidHost = req.get('Host') !== 'yoursite.com';
if (isSpamReferrer || isInvalidHost) {
// Track occurrences
const count = (suspiciousActivity.get(ip) || 0) + 1;
suspiciousActivity.set(ip, count);
// Alert if threshold exceeded
if (count > 10) {
sendSecurityAlert({
type: 'domain_spoofing',
ip,
referrer,
host: req.get('Host'),
count
});
// Block IP
blockIP(ip);
}
}
}
app.use(monitorRequest);
Platform-Specific Guides
Detailed implementation instructions for your specific platform:
Verification
After implementing domain spoofing protection:
Test CSRF protection:
- Submit form without token (should fail)
- Submit from external domain (should fail)
- Submit with valid token (should succeed)
Test referrer validation:
# Test with fake referrer curl -X POST https://yoursite.com/api/submit \ -H "Referer: https://malicious-site.com" \ -H "Content-Type: application/json" \ -d '{"data":"test"}' # Should return 403Check analytics:
- Spam referrals blocked
- Only valid hostnames
- Clean traffic sources
- Bot traffic excluded
Monitor server logs:
- Blocked requests logged
- No successful spoofing attempts
- Rate limiting working
Common Mistakes
- Trusting referrer header alone - Easily spoofed
- Not implementing CSRF tokens - Forms vulnerable
- Weak CORS policy - Allowing all origins
- No rate limiting - Spam overwhelms server
- Not filtering analytics spam - Polluted data
- Checking only referrer, not origin - Missing protection
- Not validating hostname in analytics - Ghost referrals
- Using SameSite=None unnecessarily - Reduces protection
- No monitoring of suspicious patterns - Attacks go unnoticed
- Blocking legitimate traffic - Too strict validation