Bleichenbacher Attack in Phoenix with Mutual Tls
Bleichenbacher Attack in Phoenix with Mutual Tls
A Bleichenbacher attack targets RSA-based padding schemes (for example PKCS#1 v1.5) by sending many carefully crafted ciphertexts and observing server behavior or timing differences to decrypt data or recover the private key. When this attack scenario is considered in a Phoenix application that uses Mutual TLS (mTLS), the combination of an mTLS-authenticated channel and an RSA-based key exchange or token format can expose subtle vulnerabilities if padding validation is not constant-time and strictly enforced.
In a typical setup, the client presents a client certificate during the TLS handshake, and the server may also expect an encrypted or signed token (for example an RSA-encrypted session key or an RSA-signed JWT) carrying claims or a session identifier. If the server decrypts or verifies this RSA-encrypted/signed payload using a non-constant-time padding check, an attacker can perform a Bleichenbacher-style adaptive chosen-ciphertext attack even though the transport is protected by mTLS. The mTLS layer ensures client authentication and integrity at the transport level, but it does not inherently prevent application-layer padding oracles. In fact, mTLS may increase the value of the target (strong client identity plus application data), motivating more precise probing of the application component.
Consider a Phoenix endpoint that accepts an encrypted_session parameter containing an RSA-encrypted blob. The server decrypts it with :public_key.decrypt/2 and uses the result to look up a session. If error responses differ based on padding validity (e.g., returning a 400 versus a 401, or leaking timing differences), an attacker can iteratively decrypt ciphertexts without having the private key. This becomes a practical risk when the RSA operation is used for authentication tokens or API keys rather than simple transport encryption. The attack surface is not theoretical: similar issues have been observed in protocols and libraries where PKCS#1 v1.5 padding is used and error handling or timing leaks are not carefully controlled.
To detect this with middleBrick, you can submit the Phoenix API endpoint for an unauthenticated scan. The tool runs checks including Input Validation, Authentication, and Property Authorization in parallel, and it references real OWASP API Top 10 categories and findings. If your API exposes an RSA-based operation with distinguishable error responses, middleBrick can highlight the risk and provide remediation guidance. Note that middleBrick reports findings and provides guidance; it does not fix, patch, or block the application.
Mutual Tls-Specific Remediation in Phoenix
Remediation centers on ensuring that RSA decryption and verification operations use constant-time padding checks and that error handling does not leak distinguishing information. In Phoenix, this typically means wrapping cryptographic operations so that timing and error paths are uniform, and avoiding branching on padding validity.
Example: RSA decryption with constant-time behavior using :public_key.decrypt/2 and explicit error mapping. Instead of allowing the library to raise or return distinct errors for padding failures, catch all decryption outcomes and return a uniform response.
defmodule MyApp.Session do
@moduledoc """
Secure session decryption with constant-time style handling.
"""
@spec decrypt_session(binary) :: {:ok, map()} | {:error, :invalid}
def decrypt_session(encrypted_blob) when is_binary(encrypted_blob) do
case :public_key.decrypt(encrypted_blob, [:rsa_pkcs1, :deterministic]) do
{:ok, plaintext} ->
# Validate structure, then return session data
case validate_session_payload(plaintext) do
{:ok, session} -> {:ok, session}
{:error, _} -> {:error, :invalid}
end
# Map low-level errors to a uniform invalid response to avoid padding oracles
{:error, _reason} ->
{:error, :invalid}
end
end
defp validate_session_payload(plaintext) do
# Perform strict validation; do not leak which step failed
with {:ok, %{session_id: id, expiry: expiry}} <- parse_payload(plaintext),
true <- session_valid?(id, expiry) do
{:ok, %{session_id: id, expiry: expiry}}
else
_ -> {:error, :invalid}
end
end
end
Example: JWT verification with an RSA public key, using a library that supports constant-time verification or ensuring uniform error handling. Configure the Phoenix endpoint to treat verification failures uniformly.
defmodule MyApi.Auth do
@moduledoc """
Verify RSA-signed tokens with uniform error handling.
"""
@spec verify_token(binary, map()) :: {:ok, map()} | {:error, :invalid}
def verify_token(token, public_key) when is_binary(token) and is_map(public_key) do
case MyJwt.verify(token, {:public_key, public_key}, algorithm: :RS256) do
{:ok, claims} ->
# Additional business checks, always returning the same error shape on failure
if claims["exp"] && claims["exp"] > System.system_time(:second) do
{:ok, claims}
else
{:error, :invalid}
end
# Do not distinguish between signature, padding, or format errors
{:error, _} ->
{:error, :invalid}
end
end
end
In addition to code-level fixes, review your API specification (OpenAPI/Swagger) for endpoints that accept RSA-encrypted or signed payloads. Use middleBrick’s OpenAPI/Swagger analysis (with full $ref resolution) to cross-reference spec definitions with runtime behavior. If you adopt the Pro plan, continuous monitoring can help detect regressions in error uniformity over time. For CI/CD enforcement, the GitHub Action can fail builds if security scores drop below your chosen threshold.