Domain Spoofing & Referrer Validation | Blue Frog Docs

Domain Spoofing & Referrer Validation

Detecting and preventing domain spoofing attacks and implementing proper referrer validation for analytics security

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:

  • Fake click attribution
  • Conversion stealing
  • Affiliate fraud
  • Ad fraud (fake impressions/clicks)

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

  1. 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)
  1. Check for ghost referrals:

    • Look for referrers with:
    • 100% bounce rate
    • 0:00 session duration
    • Single page per session
    • No geographic data
  2. 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:

  1. 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>
    
  2. 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:

  1. 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
    });
    
  2. 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:

  1. 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');
    });
    
  2. 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>
    
  3. 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:

  1. Create data filter:

    • Navigate to GA4 Admin → Data Settings → Data Filters
    • Create filter to exclude spam
  2. 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);
    }
    
  3. Bot filtering:

    • GA4 Admin → Data Settings → Data Collection
    • Enable "Exclude all hits from known bots and spiders"
  4. 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 (requires Secure)

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:

Platform Troubleshooting Guide
Shopify Shopify Domain Spoofing Prevention
WordPress WordPress Domain Spoofing Prevention
Wix Wix Domain Spoofing Prevention
Squarespace Squarespace Domain Spoofing Prevention
Webflow Webflow Domain Spoofing Prevention

Verification

After implementing domain spoofing protection:

  1. Test CSRF protection:

    • Submit form without token (should fail)
    • Submit from external domain (should fail)
    • Submit with valid token (should succeed)
  2. 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 403
    
  3. Check analytics:

    • Spam referrals blocked
    • Only valid hostnames
    • Clean traffic sources
    • Bot traffic excluded
  4. Monitor server logs:

    • Blocked requests logged
    • No successful spoofing attempts
    • Rate limiting working

Common Mistakes

  1. Trusting referrer header alone - Easily spoofed
  2. Not implementing CSRF tokens - Forms vulnerable
  3. Weak CORS policy - Allowing all origins
  4. No rate limiting - Spam overwhelms server
  5. Not filtering analytics spam - Polluted data
  6. Checking only referrer, not origin - Missing protection
  7. Not validating hostname in analytics - Ghost referrals
  8. Using SameSite=None unnecessarily - Reduces protection
  9. No monitoring of suspicious patterns - Attacks go unnoticed
  10. Blocking legitimate traffic - Too strict validation

Additional Resources

// SYS.FOOTER