Brute Force Attack in Hanami with Mutual Tls
Brute Force Attack in Hanami with Mutual Tls — how this specific combination creates or exposes the vulnerability
A brute force attack against a Hanami application that uses mutual TLS (mTLS) can be particularly nuanced because the presence of client certificates changes where and how authentication is evaluated. In Hanami, authentication and session handling are typically implemented in the actions layer, where request access is gated via policies or helper methods. When mTLS is enforced at the reverse proxy or load balancer, the application may assume the client identity is already verified and skip additional checks, or it may map the client certificate to a user record only after routing.
If rate limiting or account lockout controls are applied after the TLS handshake and after the identity mapping, an attacker can make many connections with different client certificates, each attempting a different password for the same user. The server validates the TLS handshake successfully for each unique client cert, but the underlying credential brute force continues unchecked. This creates a bypass of protections that might otherwise rely on IP-based or session-based rate limiting applied before strong authentication.
Moreover, if the identity derived from the client certificate is incomplete or lacks a binding to an authorization model, Hanami endpoints that rely on action-level policies may inadvertently permit elevated operations. For example, a mTLS-authenticated request might map a certificate to a low-privilege user, but if the application reuses identifiers across environments or trusts the CN without additional validation, an attacker could cycle through valid client certificates while targeting a single account.
Another angle is logging and detection: because each TLS session presents a different client certificate, correlating failed authentication attempts across multiple certs becomes difficult for SIEMs and application logs. This reduces visibility and allows brute force campaigns to proceed under the radar during the 5–15 second scan window that middleBrick uses for black-box testing.
Consider a Hanami endpoint defined as:
module Web::Controllers::Account
class Login
include Web::Action
def call(params)
user = UserRepository.find_by_email(params[:email])
if user&.authenticate(params[:password])
session[:user_id] = user.id
redirect_to routes.dashboard_path
else
self.status = 401
self.body = { error: 'unauthorized' }.to_json
end
end
end
end
If mTLS is terminated upstream and the certificate-to-identity mapping is done in a before action that only sets a flag, the brute force logic can iterate over passwords without being constrained by per-identity rate limits. middleBrick’s checks for Authentication and Rate Limiting would flag missing per-identity throttling and inconsistent binding between TLS identity and application-level permissions.
Mutual Tls-Specific Remediation in Hanami — concrete code fixes
To harden a Hanami application using mutual TLS, tie certificate identity directly into the authentication model and enforce per-identity rate limits before any password verification. Avoid relying solely on upstream assertions; validate and map the certificate within the application to ensure consistent policy enforcement.
First, create a service that maps the client certificate fields to a user record and raises an error when mapping is missing or ambiguous:
# lib/middlebrick/mtls_identity.rb
module Middlebrick
class MtlsIdentity
def initialize(cert)
@cert = cert
end
def user
return nil unless @cert
# Extract a stable subject field, e.g., email from SAN or CN
email = extract_email(@cert)
UserRepository.find_by_email(email)
end
private
def extract_email(cert)
# Use OpenSSL::X509::Certificate; in practice handle extensions robustly
subject = cert.subject.to_s
# naive extraction for example; use proper OID parsing in production
subject[/CN=([^,]+)/, 1] || subject[/emailAddress=([^,]+)/, 1]
end
end
end
Then integrate this into your Hanami action with explicit authentication and per-identity rate limiting. Use a before action to reject requests without a valid mapping:
# lib/web/actions/application_action.rb
module Web::Actions::Base
include Hanami::Action
before :set_mtls_identity, :authorize
private
def set_mtls_identity
cert = request.client_cert # provided by your Ruby server (e.g., Puma with ssl_client_cert)
identity = Middlebrick::MtlsIdentity.new(cert)
halt 403, { error: 'invalid client certificate' }.to_json unless identity.user
@current_user = identity.user
end
def authorize
# policy check using @current_user; ensure this runs after identity is bound
end
end
Apply per-identity rate limiting at the action level or via Rack middleware before password checks, ensuring that each certificate identity has its own throttle window:
# lib/middlebrick/rate_limit_by_identity.rb
class Mmiddlebrick::RateLimitByIdentity
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
identity = request.client_cert&.hash || request.ip
# Use a store compatible with your runtime; this is illustrative
if TooManyAttempts.for(identity)
return [429, { 'Content-Type' => 'application/json' }, [{ error: 'rate limit' }.to_json]]
end
@app.call(env)
ensure
TooManyAttempts.record(identity)
end
end
Ensure that the identity used for rate limiting is derived from the certificate rather than the session, so that each client cert is treated as a separate channel. This prevents an attacker from cycling client certificates to bypass simple IP-based limits.
Finally, log certificate identity alongside failures to improve detection, and map findings to relevant frameworks: these issues align with OWASP API Security Top 10 controls for Authentication and Rate Limiting, and can be audited against compliance frameworks such as SOC2 and PCI-DSS.