Brute Force Attack in Rails with Basic Auth
Brute Force Attack in Rails with Basic Auth — how this specific combination creates or exposes the vulnerability
A brute force attack against a Rails API using HTTP Basic Auth leverages the simplicity of the authentication scheme to systematically guess credentials. Because Basic Auth sends an RFC 7617–encoded username:password pair in the Authorization header on every request, there is no built-in nonce, replay protection, or token rotation. If an endpoint does not enforce strict rate limits or account lockout, an attacker can automate login attempts at high speed using tools like curl, Hydra, or custom scripts. Rails controllers that authenticate via request.headers["Authorization"] and decode Base64 credentials without additional safeguards effectively expose a linear attack surface: each request reveals whether the account exists (via 200 vs 401/404 responses), and timing differences may even leak password validity.
Consider a typical implementation that parses credentials on each request:
class Api::V1::AccountsController < ApplicationController
before_action :authenticate_with_basic_auth
private
def authenticate_with_basic_auth
authenticate_or_request_with_http_basic do |username, password|
User.exists?(username: username, password_digest: BCrypt::Engine.hash_secret(password, User.find_by(username: username)&.password_salt))
end
end
end
In this pattern, if the controller does not enforce global request throttling, an unauthenticated attacker can iterate through common passwords or use credential lists against valid usernames. Because the endpoint is unauthenticated from the scanner’s perspective (no session cookies or tokens), middleBrick’s unauthenticated scan surface includes this path. The scan’s Authentication and BFLA/Privilege Escalation checks can detect missing rate limiting and inconsistent timing behavior that facilitate brute forcing. Without additional protections—such as account lockout, exponential backoff, or multi-factor authentication—Basic Auth in Rails becomes a practical vector for credential stuffing and online password guessing, mapped to risks in the OWASP API Top 10 and relevant compliance frameworks.
Basic Auth-Specific Remediation in Rails — concrete code fixes
Remediation focuses on reducing the attack surface for automated guessing: enforce rate limiting at the network or application layer, avoid revealing account existence, and add multi-factor or secondary protections. Below are concrete, safe patterns you can adopt in Rails.
1. Rate limit by IP and username
Use Rack::Attack to throttle requests before they reach your controllers:
# config/initializers/rack_attack.rb
class Rack::Attack
throttle('req/ip/api/v1/basic_auth', limit: 30, period: 60) do |req|
if req.path == '/api/v1/accounts' && req.get? && req.headers['Authorization']&.start_with?('Basic')
req.ip
end
end
throttle('logins/ip', limit: 5, period: 60) do |req|
if req.path == '/api/v1/login' && req.post?
req.ip
end
end
end
2. Constant-time response to avoid user enumeration
Always return the same HTTP status and perform a dummy computation when the username is not found:
class Api::V1::AccountsController < ApplicationController
before_action :authenticate_with_basic_auth
private
def authenticate_with_basic_auth
authenticate_or_request_with_http_basic do |username, password|
user = User.find_by(username: username)
# Dummy hash to keep timing consistent
dummy_digest = BCrypt::Password.create('placeholder')
if user
BCrypt::Password.new(user.password_digest) == password
else
BCrypt::Password.new(dummy_digest) == password
end
end
end
end
3. Combine Basic Auth with a request signature or token for sensitive operations
For high-risk endpoints, require an additional bearer token derived from a per-request signature:
class Api::V1::SecureController < ApplicationController
before_action :verify_hmac
private
def verify_hmac
provided = request.headers['X-Request-Signature']
secret = Rails.application.credentials.hmac_secret
payload = request.request_body.read
computed = OpenSSL::HMAC.hexdigest('SHA256', secret, payload)
head :unauthorized unless Rack::SecurityUtils.secure_compare(computed, provided)
end
end
4. Use account lockout or captcha after repeated failures
Track failures in Redis and lock the account or require captcha after a threshold:
# app/services/auth_throttle.rb
class AuthThrottle
def initialize(username)
@key = "auth_failures:#{username}"
end
def register_failure
Redis.current.incr(@key)
Redis.current.expire(@key, 15.minutes.to_i) if Redis.current.ttl(@key) == -1
end
def over_limit?
Redis.current.get(@key).to_i >= 10
end
end
These controls reduce the feasibility of brute force while preserving the simplicity of Basic Auth for low-risk scenarios. middleBrick’s scans (via the CLI "middlebrick scan <url>" or the GitHub Action to add API security checks to your CI/CD pipeline) can validate whether your endpoints expose authentication timing differences or lack rate limiting, helping you confirm that remediation is effective.