Improper Control of Interaction Frequency
Description
Improper Control of Interaction Frequency is a vulnerability where software does not properly limit how often an actor—whether human or automated—can perform actions or send requests. Without frequency controls, users or automated systems can interact with the application more rapidly or more often than intended, potentially exhausting resources, bypassing protection mechanisms, or manipulating program logic. This includes scenarios like unlimited authentication attempts, unrestricted API calls, voting without cooldowns, or coupon redemption without rate limits.
Risk
This vulnerability enables multiple attack vectors. Denial of service attacks become possible when attackers flood the system with requests faster than it can process them, exhausting CPU, memory, network bandwidth, or database connections. Brute-force attacks against authentication become feasible without attempt limits—attackers can try thousands of passwords per second. Business logic abuse occurs when users can vote, redeem coupons, or perform other restricted actions unlimited times. Automated bots can scrape content, create spam accounts, or manipulate systems at superhuman speeds. The lack of rate limiting is particularly dangerous in public-facing APIs and authentication endpoints.
Solution
Implement rate limiting at multiple levels: per-user, per-IP, per-session, and globally. Use techniques like token buckets, sliding windows, or fixed window counters to track request rates. Add exponential backoff for repeated failures, particularly in authentication. Implement CAPTCHA after threshold violations to distinguish humans from bots. Use account lockout policies with escalating lockout durations. Set appropriate limits on all API endpoints based on expected legitimate usage patterns. Log rate limit violations for security monitoring. Consider using specialized rate limiting services or middleware for complex applications.
Common Consequences
| Impact | Details |
|---|---|
| Availability | Scope: Availability Denial of Service (Resource Consumption) - Unlimited requests can exhaust server resources, making the system unavailable to legitimate users. |
| Access Control | Scope: Access Control Bypass Protection Mechanism - Unlimited authentication attempts enable brute-force attacks against passwords and other credentials. |
| Integrity | Scope: Integrity Unauthorized Actions - Unrestricted action frequency allows manipulation of voting, ratings, transactions, or other business logic. |
Example Code
Vulnerable Code
// Vulnerable: No limit on authentication attempts
#define MAX_PASSWORD_LENGTH 64
int isValidUser = 0;
char password[MAX_PASSWORD_LENGTH];
// Vulnerable: Infinite loop with no attempt counter
while (isValidUser == 0) {
printf("Enter password: ");
fgets(password, MAX_PASSWORD_LENGTH, stdin);
isValidUser = validatePassword(password);
// No limit - attacker can try unlimited passwords
}
# Vulnerable: No rate limiting on login endpoint
from flask import Flask, request
app = Flask(__name__)
@app.route('/login', methods=['POST'])
def login():
# Vulnerable: No rate limiting
username = request.form['username']
password = request.form['password']
if check_credentials(username, password):
return create_session(username)
else:
return 'Invalid credentials', 401
# Attacker can send thousands of requests per second
// Vulnerable: No rate limiting on API
const express = require('express');
const app = express();
// Vulnerable: Unlimited requests allowed
app.post('/api/vote', (req, res) => {
const { postId, userId } = req.body;
// No frequency check - user can vote unlimited times
incrementVote(postId);
res.json({ success: true });
});
// Attacker can manipulate vote counts with rapid requests
// Vulnerable: No limit on coupon redemption attempts
<?php
function vulnerable_redeem_coupon($coupon_code, $user_id) {
// Vulnerable: No frequency limit
$coupon = get_coupon($coupon_code);
if ($coupon && is_valid($coupon)) {
apply_discount($user_id, $coupon->discount);
return true;
}
return false;
}
// Attacker can brute-force coupon codes rapidly
// Vulnerable: No throttling on password reset
public class VulnerableAuthController {
// Vulnerable: No rate limiting
public Response requestPasswordReset(String email) {
// No check on how often this is called
User user = userRepository.findByEmail(email);
if (user != null) {
String token = generateResetToken();
sendResetEmail(user, token);
}
// Attacker can flood email systems
return Response.ok().build();
}
}
// Vulnerable: Unbounded API requests
func vulnerableHandler(w http.ResponseWriter, r *http.Request) {
// Vulnerable: No rate limiting
// Each request consumes database resources
results := expensiveDatabaseQuery(r.URL.Query().Get("search"))
json.NewEncoder(w).Encode(results)
}
// Attacker can exhaust database connections
Fixed Code
// Fixed: Limit authentication attempts
#define MAX_PASSWORD_LENGTH 64
#define MAX_ATTEMPTS 3
#define LOCKOUT_SECONDS 300
int isValidUser = 0;
int attemptCount = 0;
char password[MAX_PASSWORD_LENGTH];
// Fixed: Limited attempts with lockout
while (isValidUser == 0 && attemptCount < MAX_ATTEMPTS) {
printf("Enter password (attempt %d of %d): ",
attemptCount + 1, MAX_ATTEMPTS);
fgets(password, MAX_PASSWORD_LENGTH, stdin);
isValidUser = validatePassword(password);
attemptCount++;
if (!isValidUser && attemptCount >= MAX_ATTEMPTS) {
printf("Too many attempts. Locked for %d seconds.\n",
LOCKOUT_SECONDS);
sleep(LOCKOUT_SECONDS);
// Could also require additional verification
}
}
# Fixed: Rate limiting with Flask-Limiter
from flask import Flask, request
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
app = Flask(__name__)
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute") # Fixed: Strict rate limit for login
def login():
username = request.form['username']
password = request.form['password']
if check_credentials(username, password):
return create_session(username)
else:
# Exponential backoff could be added here
return 'Invalid credentials', 401
# Additional protection: account lockout after failures
def check_credentials_with_lockout(username, password):
lockout = get_lockout_status(username)
if lockout and lockout.is_active:
raise TooManyAttemptsError(lockout.remaining_time)
success = verify_password(username, password)
if not success:
record_failed_attempt(username)
else:
clear_failed_attempts(username)
return success
// Fixed: Rate limiting middleware
const express = require('express');
const rateLimit = require('express-rate-limit');
const app = express();
// Fixed: Rate limiter middleware
const voteLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // 10 votes per window per IP
message: 'Too many votes, please try again later',
standardHeaders: true,
legacyHeaders: false,
});
app.post('/api/vote', voteLimiter, (req, res) => {
const { postId, userId } = req.body;
// Additional check: one vote per user per post
if (hasUserVoted(userId, postId)) {
return res.status(409).json({ error: 'Already voted' });
}
incrementVote(postId, userId);
res.json({ success: true });
});
// API-wide rate limiting
const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100,
message: 'Too many requests'
});
app.use('/api/', apiLimiter);
// Fixed: Rate limiting for coupon redemption
<?php
class RateLimiter {
private $redis;
public function isAllowed($key, $maxAttempts, $windowSeconds) {
$current = $this->redis->incr($key);
if ($current === 1) {
$this->redis->expire($key, $windowSeconds);
}
return $current <= $maxAttempts;
}
}
function fixed_redeem_coupon($coupon_code, $user_id) {
$limiter = new RateLimiter();
$key = "coupon_attempt:{$user_id}";
// Fixed: Limit to 10 attempts per hour per user
if (!$limiter->isAllowed($key, 10, 3600)) {
throw new RateLimitException("Too many attempts");
}
$coupon = get_coupon($coupon_code);
if ($coupon && is_valid($coupon)) {
// Check if already redeemed
if (has_redeemed($user_id, $coupon->id)) {
throw new Exception("Coupon already redeemed");
}
apply_discount($user_id, $coupon->discount);
mark_redeemed($user_id, $coupon->id);
return true;
}
return false;
}
// Fixed: Rate limiting with bucket4j
import io.github.bucket4j.*;
import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;
public class FixedAuthController {
private final ConcurrentHashMap<String, Bucket> buckets =
new ConcurrentHashMap<>();
private Bucket createBucket() {
return Bucket4j.builder()
.addLimit(Bandwidth.classic(3, Refill.intervally(3, Duration.ofMinutes(15))))
.build();
}
public Response requestPasswordReset(String email) {
// Fixed: Per-email rate limiting
Bucket bucket = buckets.computeIfAbsent(email, k -> createBucket());
if (!bucket.tryConsume(1)) {
return Response.status(429)
.entity("Too many requests. Try again later.")
.build();
}
User user = userRepository.findByEmail(email);
if (user != null) {
String token = generateResetToken();
sendResetEmail(user, token);
}
// Same response regardless of user existence (prevents enumeration)
return Response.ok("If that email exists, a reset link was sent").build();
}
}
// Fixed: Rate limiting middleware
package main
import (
"net/http"
"golang.org/x/time/rate"
"sync"
)
type IPRateLimiter struct {
ips map[string]*rate.Limiter
mu *sync.RWMutex
r rate.Limit
b int
}
func NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter {
return &IPRateLimiter{
ips: make(map[string]*rate.Limiter),
mu: &sync.RWMutex{},
r: r,
b: b,
}
}
func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
i.mu.Lock()
defer i.mu.Unlock()
limiter, exists := i.ips[ip]
if !exists {
limiter = rate.NewLimiter(i.r, i.b)
i.ips[ip] = limiter
}
return limiter
}
// Fixed: 10 requests per second per IP
var limiter = NewIPRateLimiter(10, 30)
func rateLimitMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
if !limiter.GetLimiter(ip).Allow() {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
CVE Examples
- CVE-2024-50653: E-commerce platform's coupon endpoint lacked server-side request frequency restrictions, allowing unlimited redemption attempts.
- CVE-2002-1876: Mail server vulnerable to rapid connection requests causing denial of service through resource exhaustion.
Related CWEs
- CWE-691: Insufficient Control Flow Management (parent)
- CWE-307: Improper Restriction of Excessive Authentication Attempts (child)
- CWE-837: Improper Enforcement of a Single, Unique Action (child)
- CWE-770: Allocation of Resources Without Limits or Throttling (related)
References
- MITRE Corporation. "CWE-799: Improper Control of Interaction Frequency." https://cwe.mitre.org/data/definitions/799.html
- OWASP. "Blocking Brute Force Attacks." https://owasp.org/www-community/controls/Blocking_Brute_Force_Attacks
- OWASP. "Rate Limiting." https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html