Security & Governance · recorded 2026-08-29 · applies to 1 tool(s) ✓ Verified

Problem: SMS API hammered by scripts/abuse — costs spike, endpoint collapses (no rate limiting) —— Trove problem library

Verified 2026-08-29SMS API being hammered — rate-limit by IP at nginx: limit_req_zone (rate=5r/s) + limit_req burst=10 nodelay inside the location, 503 above the limit.

Quick fix

# One-liner: IP rate limiting at the nginx layer (zone in http block, enable inside location; over-limit returns 503)
# http block: limit_req_zone $binary_remote_addr zone=sms:10m rate=5r/s;
# inside location: limit_req zone=sms burst=10 nodelay;

burst = burst bucket capacity; nodelay = reject immediately over limit; default rejection is 503, limit_req_status 429 switches to 429.

What you will run into

The SMS endpoint gets hammered by site docs traffic: huge request volumes in a short window, SMS costs spiking, the endpoint overwhelmed.

Generalizing to any business endpoint: same IP/same phone number high-frequency requests, proxy IP pool rotation, verification-code endpoints being credential-stuffed.

One abuse wave is real money lost.

Why this happens

The endpoint has no rate limiting and no blacklist: an SMS/verification-code endpoint can naturally be called infinitely (no frequency, no origin restriction).

Emergency stop-loss measures (circuit breaker / degradation / blacklist / rate limit) are missing or ineffective config — measured finding: limit_req and return in the same location short-circuit, config "looks present" but isn't.

Protection layerMeansMeasured status
Emergency stop-lossTest mode / circuit breaker, Nginx deny, global rate limit✅ nginx deny measured 403
Nginx rate limitlimit_req_zone + limit_req✅ Works measured in static/proxy forms (503)
Business layerRedis frequency, Bloom filter, device fingerprint⏳ No redis/docker on this machine; not measured
Monitoring reviewRequest-volume baseline + anomaly alertsLong-term after go-live

SMS/verification APIs get abused because they are "stateless endpoints without rate constraints": the endpoint does not track call sources or counts, so attackers can call it indefinitely (credential stuffing, harassment, bill-shaving) while you pay the cost.

The "config ineffective" trap is separate: when nginx limit_req and return live in the same location, return short-circuits subsequent directives — the config "looks present" but is not, a trap reproduced in testing.

Deterministic takeaway: verify protections actually take effect first (tested: nginx deny returns 403, limit_req returns 503) — an unverified config equals no config;

protections come in three layers — network (nginx deny/rate-limit, deployable immediately), business (Redis counters/device fingerprinting, needs dependencies), monitoring (baseline + alerts, long-term);

for emergency stop, test-mode/circuit-breaking stops the bleed first, then harden; fixed diagnostic: when abused, first confirm whether the protection really returns the expected status code, then discuss strategy.

Step by step

1. Emergency stop-loss (golden 30 minutes) — Required

In the config center, switch the SMS endpoint to "test mode" (returns success but doesn't actually send) or circuit-break it; block abnormal IPs at the nginx layer (deny, measured 403); if ineffective, temporarily disable SMS to protect core business.

2. Nginx rate limiting (measured working form) — Required

Define the zone in the http block:

limit_req_zone $binary_remote_addr zone=sms:10m rate=5r/s;

Enable inside the location (works in both proxy and static-file forms, measured):

limit_req zone=sms burst=10 nodelay;

3. Business-layer protection — Required

Phone-number frequency limits (Redis INCR+EXPIRE, auto-reset on expiry); Bloom filter to fast-filter invalid/risky numbers (12MB for tens of millions); device fingerprint / behavior risk control; API signing.

(No redis-server or docker daemon on this machine; not measured, stated honestly.)

4. Monitoring review — Required

Build request-volume baselines by IP/phone/device with automatic anomaly alerts; gray-release new risk rules.

How to confirm it works

Load-test assertions (measured output shape: 15 rapid-fire requests under rate limit):

curl 15 rapid requests
→ 1×200 + 14×503 (rate=2r/s, static-file form)
→ error log: limiting requests, excess: 0.974 by zone "min"

After deny 127.0.0.1, requests → 403 (log: access forbidden by rule).

Other environments

EnvironmentNotes
Static-file formlimit_req measured working: 1×200 + 14×503
proxy_pass proxy formProduction form measured working: 1×200 + 14×503
deny + return same locationMeasured ineffective (return short-circuits the preaccess/access stages)
limit_req + return same locationMeasured ineffective (all 200, no log entries) — a pitfall the article didn't mention
Business layerRedis token bucket / Bloom filter not measured (no redis-server / docker daemon)

Symptom

An SMS endpoint gets hammered by site docs traffic: a huge number of requests in a short window, SMS costs spike, the API is overwhelmed; generalized to any business endpoint — same IP/same phone number high-frequency requests, rotating proxy IP pools, verification-code endpoints brute-forced.

Cause

The endpoint has no frequency limits or blacklists: SMS/verification endpoints are naturally callable without limit (no frequency, no source restrictions); once hammered it is real money lost; emergency stop mechanisms (circuit breaker / degrade / blacklist / rate limit) are missing or misconfigured (see evidence: limit_req + return in the same location gets short-circuited — config "looks on" but is actually off).

Solution

  1. Emergency stop (golden 30 minutes): switch the SMS endpoint to "test mode" (returns success but does not really send) or circuit-break it in the config center; block offending IPs at nginx (deny, measured 403); enable global rate limiting at the gateway; if ineffective, temporarily disable the SMS feature to protect core business.
  2. nginx rate limiting (measured working form):
   limit_req_zone $binary_remote_addr zone=sms:10m rate=5r/s;   # http block
   # inside location (both reverse-proxy and static-file forms measured working, excess → 503):
   limit_req zone=sms burst=10 nodelay;
  • burst = burst bucket capacity, nodelay = reject excess immediately; default excess status is 503 (change with limit_req_status 429).
  • Measured pitfall (not in the article): limit_req/deny together with return in the same location do NOT work — return terminates the request in the rewrite phase, skipping the preaccess/access phases; location = /api/sms/send { limit_req ...; return 200 "ok"; } is effectively unlimited. The endpoint must use a static-file/proxy_pass form, or put the limit at the server level.
  1. Application-layer protection: per-phone-number rate limits (Redis INCR+EXPIRE, auto-reset on expiry); a Bloom filter to quickly drop invalid/risky numbers (12MB for tens of millions); device fingerprinting / behavior risk control; API signatures. No redis-server / docker daemon locally, so not measured (stated honestly).
  2. Monitor & review: per-IP/number/device request baselines, auto-alert on anomalies; gray-release new risk rules.
Verification Evidence
  • Local test (2026-08-29, nginx/1.28.3 nginx-light + python3, fully local zero network):
  1. Control: 15 consecutive requests to an unlimited endpoint — all 200.
  2. limit_req zone=min rate=2r/s + static file: 15 consecutive = 1×200 + 14×503, error log limiting requests, excess: 0.974 by zone "min".
  3. limit_req rate=2r/s + proxy_pass reverse proxy: same as 2 (production form works).
  4. deny 127.0.0.1: 403, log access forbidden by rule.
  5. Pitfall: limit_req + return 200 in the same location — 15 consecutive all 200, no log entries; deny + return also ineffective — return short-circuits the preaccess/access phases (found by testing the first broken config; results 2/3/4 came after fixing it).
  • Redis/Lua token bucket, Bloom filter: not measured (no redis-server locally; docker daemon not running, image pulls restricted).

Evidence details kept locally (2026-08-29)

Applicable tools (1)

This problem is linked from the tool pages below — reachable from either side.

nginx

Source & evidence

Recorded Verified ✓ VerifiedApplies to 1 tool(s)
Entries contain locally measured or reviewable-source solutions; unmeasured items are explicitly labeled.

Source: https://www.cnblogs.com/zrui-xyu/p/22612063

← Security & Governance domain | Problem Catalog Experience | Facing another issue? Go to the Helpdesk