비트베이크

Complete SMS Pumping Fraud Prevention Guide 2026 - Security Strategies Every Developer Must Know

2026-03-16T06:43:56.698Z

SMS-PUMP-GUARD

Complete SMS Pumping Fraud Prevention Guide 2026 - Security Strategies Every Developer Must Know

Your SMS bill suddenly spikes by thousands of dollars. OTP requests are flooding in, but actual sign-up conversions are near zero. You're likely already a victim of SMS pumping fraud — and you're not alone.

In 2023, businesses worldwide spent approximately $1.16 billion on fraudulent SMS messages. Elon Musk revealed that Twitter (now X) was losing $60 million per year to SMS pumping attacks. By 2025–2026, experts project the global financial impact will reach several billion dollars. The threat is so significant that MITRE ATT&CK now formally classifies it as T1496.003 (Resource Hijacking: SMS Pumping).

This guide provides a complete, code-driven defense strategy that every developer building SMS-based authentication must implement.


What is SMS Pumping?

SMS pumping (also known as SMS toll fraud or Artificially Inflated Traffic) is a fraud scheme where attackers partner with complicit telecom providers to generate massive volumes of SMS traffic and share the resulting revenue.

How the Attack Works

  1. The attacker acquires a set of premium-rate phone numbers from a telecom provider
  2. They identify publicly available endpoints that trigger automated SMS — sign-up forms, OTP verification, password resets
  3. Bots flood these endpoints with thousands of verification requests to those premium numbers
  4. Each SMS sent generates revenue for the telecom provider, who splits the profit with the attacker
  5. The attacker never intends to use the OTP codes — the goal is purely to generate SMS traffic

Key Indicators of an Attack

  • Unverified OTPs: Extremely low verification completion rates
  • Country clustering: Sudden spike in requests to specific country codes
  • Sequential numbers: Requests to +1-555-0001, +1-555-0002, etc.
  • Inhuman speed: Hundreds of requests per second from single IPs or sessions
  • Off-hours surges: Unusual traffic during non-business hours for the target region

Defense Strategy 1: Multi-Layer Rate Limiting

Rate limiting is your first and most essential line of defense. However, a single rate limit is easily circumvented — you need multiple layers.

Node.js/Express Implementation

const express = require('express');
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');

const redis = new Redis(process.env.REDIS_URL);
const app = express();

// Layer 1: Global IP-based rate limiting
const globalSmsLimiter = rateLimit({
  store: new RedisStore({ sendCommand: (...args) => redis.call(...args) }),
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 10, // Max 10 SMS requests per IP per hour
  keyGenerator: (req) => req.ip,
  message: { error: 'Too many SMS requests. Please try again later.' },
  standardHeaders: true,
});

// Layer 2: Phone number-based rate limiting
const phoneNumberLimiter = rateLimit({
  store: new RedisStore({ sendCommand: (...args) => redis.call(...args) }),
  windowMs: 10 * 60 * 1000, // 10 minutes
  max: 3, // Max 3 codes per number per 10 minutes
  keyGenerator: (req) => req.body.phoneNumber,
  message: { error: 'Verification limit reached for this number.' },
});

// Layer 3: Country code-based rate limiting
const countryCodeLimiter = async (req, res, next) => {
  const phoneNumber = req.body.phoneNumber;
  const countryCode = extractCountryCode(phoneNumber);
  
  const key = `sms:country:${countryCode}`;
  const count = await redis.incr(key);
  if (count === 1) await redis.expire(key, 3600);
  
  // Tiered limits per country based on expected traffic
  const limits = {
    '+82': 1000,  // South Korea (primary market)
    '+1': 500,    // United States
    '+81': 300,   // Japan
    'default': 50 // All other countries: conservative limit
  };
  
  const maxAllowed = limits[countryCode] || limits['default'];
  if (count > maxAllowed) {
    return res.status(429).json({ 
      error: 'Regional SMS limit reached.' 
    });
  }
  next();
};

// Apply middleware chain
app.post('/api/send-otp', 
  globalSmsLimiter, 
  phoneNumberLimiter, 
  countryCodeLimiter, 
  sendOtpHandler
);

Exponential Backoff

Increase wait times exponentially for repeated requests to the same number:

async function enforceExponentialBackoff(phoneNumber) {
  const key = `sms:backoff:${phoneNumber}`;
  const attempts = await redis.get(key);
  
  if (attempts) {
    const waitSeconds = Math.min(
      Math.pow(2, parseInt(attempts)) * 30, 
      3600
    );
    const ttl = await redis.ttl(key);
    
    if (ttl > 0) {
      throw new Error(
        `Please wait ${Math.ceil(ttl / 60)} minutes before requesting another code.`
      );
    }
  }
  
  await redis.multi()
    .incr(key)
    .expire(key, Math.min(
      Math.pow(2, parseInt(attempts || 0)) * 30, 
      3600
    ))
    .exec();
}

Defense Strategy 2: Phone Number Validation & Risk Scoring

Rate limiting slows attackers down but doesn't stop them. You must validate the phone number before sending any SMS.

const { parsePhoneNumber, isValidPhoneNumber } = require('libphonenumber-js');

async function validatePhoneNumber(phoneNumber) {
  const result = {
    isValid: false,
    riskScore: 0,
    riskFactors: [],
  };

  // 1. Basic format validation
  if (!isValidPhoneNumber(phoneNumber)) {
    result.riskFactors.push('INVALID_FORMAT');
    return result;
  }

  const parsed = parsePhoneNumber(phoneNumber);
  const countryCode = parsed.country;

  // 2. Country allowlist check
  const allowedCountries = ['KR', 'US', 'JP'];
  if (!allowedCountries.includes(countryCode)) {
    result.riskFactors.push('BLOCKED_COUNTRY');
    result.riskScore += 100;
    return result;
  }

  // 3. Korean number-specific validation (010, 011 prefixes)
  if (countryCode === 'KR') {
    const localNumber = parsed.nationalNumber;
    const validPrefixes = ['010', '011', '016', '017', '018', '019'];
    const prefix = localNumber.substring(0, 3);
    
    if (!validPrefixes.includes(prefix)) {
      result.riskFactors.push('INVALID_KR_PREFIX');
      result.riskScore += 80;
    }
  }

  // 4. Carrier lookup — block VoIP numbers
  const carrierInfo = await lookupCarrier(phoneNumber);
  if (carrierInfo.type === 'voip') {
    result.riskFactors.push('VOIP_NUMBER');
    result.riskScore += 60;
  }

  // 5. Sequential number pattern detection
  const isSequential = await checkSequentialPattern(phoneNumber);
  if (isSequential) {
    result.riskFactors.push('SEQUENTIAL_PATTERN');
    result.riskScore += 70;
  }

  result.isValid = result.riskScore < 50;
  return result;
}

// Detect sequential number patterns
async function checkSequentialPattern(phoneNumber) {
  const baseNumber = phoneNumber.slice(0, -2);
  const key = `sms:recent:${baseNumber}`;
  
  await redis.sadd(key, phoneNumber);
  await redis.expire(key, 3600);
  
  const recentRequests = await redis.scard(key);
  // 5+ requests to the same number range = suspicious
  return recentRequests >= 5;
}

Defense Strategy 3: Bot Detection & Behavioral Analysis

CAPTCHA was once the go-to defense, but in 2025–2026, automated CAPTCHA-solving services are cheap and widely available. Sophisticated attackers bypass them at scale. You need behavioral analysis alongside — or instead of — CAPTCHA.

Client-Side Behavior Collection

// Frontend: collect behavioral signals
class BehaviorCollector {
  constructor() {
    this.startTime = Date.now();
    this.mouseMovements = 0;
    this.keystrokes = 0;
    this.touchEvents = 0;
  }

  init() {
    document.addEventListener('mousemove', () => this.mouseMovements++);
    document.addEventListener('keydown', () => this.keystrokes++);
    document.addEventListener('touchstart', () => this.touchEvents++);
  }

  getSignals() {
    const timeOnPage = Date.now() - this.startTime;
    return {
      timeOnPageMs: timeOnPage,
      mouseMovements: this.mouseMovements,
      keystrokes: this.keystrokes,
      touchEvents: this.touchEvents,
      isSuspiciouslyFast: timeOnPage < 2000,
      hasNaturalInteraction: this.mouseMovements > 5 || this.touchEvents > 0,
    };
  }
}

Server-Side Behavior Analysis

function analyzeBehavior(signals, req) {
  let riskScore = 0;
  const flags = [];

  // Bots fill forms instantly
  if (signals.timeOnPageMs < 2000) {
    riskScore += 40;
    flags.push('SUSPICIOUSLY_FAST');
  }

  // No human interaction signals
  if (!signals.hasNaturalInteraction && signals.keystrokes === 0) {
    riskScore += 35;
    flags.push('NO_INTERACTION');
  }

  // Suspicious User-Agent
  const ua = req.headers['user-agent'];
  if (!ua || /bot|curl|python-requests|scrapy|headless/i.test(ua)) {
    riskScore += 50;
    flags.push('SUSPICIOUS_UA');
  }

  // Headless browser detection
  if (signals.webdriverDetected) {
    riskScore += 60;
    flags.push('WEBDRIVER_DETECTED');
  }

  return {
    riskScore,
    flags,
    action: riskScore >= 60 ? 'BLOCK' : riskScore >= 30 ? 'CHALLENGE' : 'ALLOW',
  };
}

Defense Strategy 4: OTP Conversion Rate Monitoring

One of the most reliable indicators of SMS pumping is that sent OTPs are never actually verified. The attacker's goal is to generate SMS traffic, not authenticate. Monitoring this conversion rate gives you an early warning system.

class OtpConversionMonitor {
  constructor(redis) {
    this.redis = redis;
  }

  async recordSmsSent(countryCode) {
    const key = `otp:sent:${countryCode}:${this.getHourKey()}`;
    await this.redis.incr(key);
    await this.redis.expire(key, 86400);
  }

  async recordOtpVerified(countryCode) {
    const key = `otp:verified:${countryCode}:${this.getHourKey()}`;
    await this.redis.incr(key);
    await this.redis.expire(key, 86400);
  }

  async getConversionRate(countryCode) {
    const hourKey = this.getHourKey();
    const sent = parseInt(
      await this.redis.get(`otp:sent:${countryCode}:${hourKey}`)
    ) || 0;
    const verified = parseInt(
      await this.redis.get(`otp:verified:${countryCode}:${hourKey}`)
    ) || 0;

    if (sent === 0) return { rate: 1, sent, verified, alert: false };

    const rate = verified / sent;

    return {
      rate,
      sent,
      verified,
      alert: rate < 0.20,
      critical: rate < 0.05,
      action: rate < 0.05 ? 'BLOCK_COUNTRY' 
            : rate < 0.20 ? 'ALERT' 
            : 'NORMAL',
    };
  }

  getHourKey() {
    return new Date().toISOString().slice(0, 13);
  }
}

// Automated defense trigger
async function autoDefenseCheck(countryCode, monitor) {
  const stats = await monitor.getConversionRate(countryCode);
  
  if (stats.critical) {
    console.error(
      `[CRITICAL] SMS pumping detected for ${countryCode}! ` +
      `Conversion: ${(stats.rate * 100).toFixed(1)}% ` +
      `(${stats.verified}/${stats.sent})`
    );
    await blockCountryCode(countryCode);
    await sendAlertToSlack(countryCode, stats);
    return false;
  }
  
  if (stats.alert) {
    console.warn(
      `[WARNING] Low OTP conversion for ${countryCode}: ` +
      `${(stats.rate * 100).toFixed(1)}%`
    );
    await sendAlertToSlack(countryCode, stats);
  }
  
  return true;
}

Defense Strategy 5: Geographic Controls & Country Blocking

Only allowing SMS to countries where you actually operate is one of the highest-ROI defenses you can implement.

const ALLOWED_COUNTRIES = new Map([
  ['KR', { dailyLimit: 10000, description: 'South Korea' }],
  ['US', { dailyLimit: 5000, description: 'United States' }],
  ['JP', { dailyLimit: 3000, description: 'Japan' }],
]);

// Known high-risk prefixes commonly exploited in pumping attacks
const HIGH_RISK_PREFIXES = [
  '+248', // Seychelles
  '+592', // Guyana  
  '+675', // Papua New Guinea
  '+960', // Maldives
];

async function geoFilterMiddleware(req, res, next) {
  const phoneNumber = req.body.phoneNumber;
  const parsed = parsePhoneNumber(phoneNumber);
  
  if (!ALLOWED_COUNTRIES.has(parsed.country)) {
    console.warn(`Blocked SMS to non-allowed country: ${parsed.country}`);
    return res.status(403).json({ 
      error: 'SMS verification is not available in your region.' 
    });
  }

  if (HIGH_RISK_PREFIXES.some(p => phoneNumber.startsWith(p))) {
    return res.status(403).json({ 
      error: 'This number cannot be verified.' 
    });
  }

  const countryConfig = ALLOWED_COUNTRIES.get(parsed.country);
  const dailyCount = await getDailyCountForCountry(parsed.country);
  
  if (dailyCount >= countryConfig.dailyLimit) {
    await sendAlertToOps(
      `Daily SMS limit reached for ${countryConfig.description}`
    );
    return res.status(429).json({ 
      error: 'Service temporarily unavailable.' 
    });
  }

  next();
}

Defense Strategy 6: IP Intelligence & Device Fingerprinting

async function ipAndDeviceCheck(req) {
  const ip = req.ip;
  const riskFactors = [];
  let riskScore = 0;

  // 1. IP geolocation vs phone number country mismatch
  const ipGeo = await getGeoFromIP(ip);
  const phoneCountry = parsePhoneNumber(req.body.phoneNumber).country;
  
  if (ipGeo.country !== phoneCountry) {
    riskScore += 25;
    riskFactors.push('GEO_MISMATCH');
  }

  // 2. VPN / Proxy / TOR / Datacenter detection
  const ipType = await checkIPType(ip);
  if (['vpn', 'proxy', 'tor', 'datacenter'].includes(ipType)) {
    riskScore += 40;
    riskFactors.push(`IP_TYPE_${ipType.toUpperCase()}`);
  }

  // 3. Multiple phone numbers from same IP
  await redis.sadd(`ip:numbers:${ip}`, req.body.phoneNumber);
  await redis.expire(`ip:numbers:${ip}`, 3600);
  const uniqueNumbers = await redis.scard(`ip:numbers:${ip}`);
  
  if (uniqueNumbers > 3) {
    riskScore += 30;
    riskFactors.push('MULTIPLE_NUMBERS_SAME_IP');
  }

  // 4. Device fingerprint abuse detection
  const deviceFp = req.headers['x-device-fingerprint'];
  if (deviceFp) {
    const fpCount = await redis.incr(`device:sms:${deviceFp}`);
    await redis.expire(`device:sms:${deviceFp}`, 86400);
    if (fpCount > 5) {
      riskScore += 35;
      riskFactors.push('DEVICE_ABUSE');
    }
  }

  return { riskScore, riskFactors };
}

Unified Defense Pipeline

Combine all strategies into a single middleware pipeline with cumulative risk scoring:

async function smsSecurityPipeline(req, res, next) {
  const phoneNumber = req.body.phoneNumber;
  let totalRiskScore = 0;
  const allFlags = [];

  try {
    // Layer 1: Geographic filtering
    const geoResult = await geoFilter(phoneNumber);
    if (geoResult.blocked) {
      return res.status(403).json({ error: geoResult.message });
    }

    // Layer 2: Phone number validation
    const phoneResult = await validatePhoneNumber(phoneNumber);
    totalRiskScore += phoneResult.riskScore;
    allFlags.push(...phoneResult.riskFactors);

    // Layer 3: IP and device analysis
    const ipResult = await ipAndDeviceCheck(req);
    totalRiskScore += ipResult.riskScore;
    allFlags.push(...ipResult.riskFactors);

    // Layer 4: Behavioral analysis
    const behaviorResult = analyzeBehavior(req.body.signals, req);
    totalRiskScore += behaviorResult.riskScore;
    allFlags.push(...behaviorResult.flags);

    // Layer 5: OTP conversion rate monitoring
    const conversionOk = await autoDefenseCheck(
      extractCountryCode(phoneNumber), otpMonitor
    );
    if (!conversionOk) {
      return res.status(429).json({ 
        error: 'Service temporarily restricted.' 
      });
    }

    // Final decision
    if (totalRiskScore >= 80) {
      logSecurityEvent('SMS_BLOCKED', {
        phoneNumber: maskPhoneNumber(phoneNumber),
        totalRiskScore,
        allFlags,
      });
      return res.status(403).json({ 
        error: 'Request could not be processed.' 
      });
    }

    if (totalRiskScore >= 40) {
      req.requireChallenge = true; // Trigger CAPTCHA
    }

    await enforceExponentialBackoff(phoneNumber);
    next();
  } catch (error) {
    console.error('SMS security pipeline error:', error);
    // FAIL CLOSED: block SMS on error
    return res.status(500).json({ 
      error: 'Service temporarily unavailable.' 
    });
  }
}

The 2026 Alternative: Silent Network Authentication (SNA)

While hardening your SMS pipeline is essential, it's worth noting a newer technology gaining traction: Silent Network Authentication (SNA). Instead of sending an OTP, SNA verifies the user's SIM and device directly through carrier-level APIs.

Key advantages:

  • Zero friction: No OTP input required from the user
  • Immune to pumping: No SMS is sent, so there's nothing to pump
  • Phishing-resistant: Verification happens at the network level
  • Potentially cheaper: Some markets offer SNA below SMS rates

SNA isn't universally available across all carriers and countries yet, so the recommended approach is a hybrid model: use SNA where supported, with hardened SMS OTP as a fallback.


Monitoring Dashboard: Key Metrics

Track these metrics to detect attacks early:

| Metric | Normal Range | Warning Threshold | Critical Threshold | |--------|-------------|-------------------|--------------------| | OTP Conversion Rate | > 50% | < 20% | < 5% | | Hourly SMS Volume | Service-dependent | 3x average | 10x average | | Unique Numbers per IP | 1–2 | 5+ | 10+ | | New Country Traffic | Within baseline | Sudden spike | Previously unseen country | | Sequential Number Requests | 0% | > 5% | > 15% |


Implementation Checklist

Immediate (Day 1):

  • [ ] Block SMS to non-service countries
  • [ ] IP-based rate limiting (10/hour)
  • [ ] Phone number rate limiting (3/10 min)

Short-term (Week 1):

  • [ ] Phone number format validation & VoIP blocking
  • [ ] Exponential backoff implementation
  • [ ] OTP conversion rate monitoring with alerts

Medium-term (Month 1):

  • [ ] Behavioral bot detection
  • [ ] IP intelligence & device fingerprinting
  • [ ] Automated country blocking system
  • [ ] Unified risk scoring pipeline

Long-term:

  • [ ] Evaluate Silent Network Authentication (SNA)
  • [ ] Integrate carrier-level fraud detection APIs

When building SMS verification systems, leveraging authentication APIs like Easy Auth that include built-in fraud prevention can significantly reduce the burden of implementing these security layers from scratch.


Conclusion

SMS pumping is not a nuisance — it's a sophisticated fraud operation that can cost your organization hundreds of thousands of dollars. No single defense is sufficient against modern attackers. The key principles are:

  1. Fail closed: When in doubt, block the SMS
  2. Defense in depth: Layer rate limiting + number validation + behavioral analysis + conversion monitoring
  3. Geographic restriction: Only allow countries you actually serve
  4. Real-time monitoring: Detect anomalies and auto-respond within minutes
  5. Set spending caps: Configure daily and monthly SMS limits with your provider

The code examples in this guide are production-ready starting points. Implement them incrementally — start with geographic controls and rate limiting today, then build toward the full risk-scoring pipeline. Your SMS budget will thank you.


Sources: MITRE ATT&CK T1496.003, Twilio Fraud Prevention, Group-IB SMS Pumping Research, Sardine SMS Pumping Protection, Akamai SMS Pumping Overview

비트베이크에서 광고를 시작해보세요

광고 문의하기

다른 글 보기

2026-04-08T11:02:47.515Z

2026 Professionals Solo Party & Wine Mixer Complete Guide: Real Reviews and Success Tips for Korean Singles

2026-04-08T11:02:47.487Z

2026년 직장인 솔로파티 & 와인모임 소개팅 완벽 가이드 - 실제 후기와 성공 팁

2026-04-08T10:03:28.247Z

Complete Google NotebookLM Guide 2026: Master the New Studio Features, Video Overviews, and Gemini Canvas Integration

2026-04-08T10:03:28.231Z

2026년 구글 NotebookLM 완벽 가이드: 새로운 스튜디오 기능, 비디오 개요 및 제미나이 캔버스 통합 실전 활용법

서비스

피드자주 묻는 질문고객센터

문의

비트베이크

레임스튜디오 | 사업자 등록번호 : 542-40-01042

경기도 남양주시 와부읍 수례로 116번길 16, 4층 402-제이270호

트위터인스타그램네이버 블로그