Side Channel Attack in Axum with Cockroachdb
Side Channel Attack in Axum with Cockroachdb — how this specific combination creates or exposes the vulnerability
A side channel attack in an Axum service that uses CockroachDB can occur when timing differences, error messages, or request patterns leak information about authentication or data access. Axum, a Rust web framework, does not introduce database-specific leaks by itself, but the way routes are structured and how errors from CockroachDB are handled can unintentionally expose behavior that an attacker can measure and interpret.
Consider an authentication route that queries CockroachDB for a user record by username. If the SQL query uses a simple equality lookup and the database returns a row, the operation may complete slightly faster than when no row exists, because CockroachDB resolves the index lookup and returns no results. An attacker can measure response times across many requests and infer whether a given username exists, even when the HTTP response is generic. This is a classic timing side channel, and it violates the principle of uniform error handling and constant-time behavior.
In addition to timing, the combination can expose information through error payloads. Axum can be configured to return detailed database error messages in development mode, which might include SQL state codes or constraint violations. CockroachDB errors often contain details about constraint failures or index usage. If these details are surfaced to the client, an attacker can learn about schema structure or about the success of injection-like probes. Even in production, where error details are suppressed, differences in status codes (for example, 401 vs 404) or variations in response size can still act as side channels when combined with other observations.
The use of prepared statements and parameterized queries with CockroachDB helps reduce some variability, but does not automatically remove all side channels. Network jitter and CockroachDB’s distributed consensus behavior can introduce noise, but repeated measurements and statistical analysis can still make a timing difference detectable. An attacker might also probe rate limiting or request throttling behavior by observing whether certain endpoints introduce additional latency under high load, especially if Axum routes are not uniformly protected.
To mitigate these risks, Axum handlers should ensure that all code paths involving CockroachDB take approximately the same amount of time and return responses of similar size and structure. This includes avoiding early returns on validation errors that skip database calls, and ensuring that both existing and non-existing user lookups perform the same query shape. Logging and monitoring in Axum should also be configured to avoid leaking request-specific details into observable logs that could be correlated with side channel observations.
middleBrick can support this posture by scanning the unauthenticated attack surface of an Axum API that fronts CockroachDB, identifying inconsistent error handling, missing rate limiting, and potential timing anomalies across endpoints. Its checks for Input Validation, Rate Limiting, and Data Exposure are particularly relevant for reducing information leakage that could enable side channel behavior.
Cockroachdb-Specific Remediation in Axum — concrete code fixes
Remediation focuses on making Axum routes that interact with CockroachDB behave uniformly, regardless of outcome. The following examples show a secure pattern for user lookup and authentication, using parameterized queries, consistent error handling, and constant-time comparison techniques.
Always use parameterized queries to avoid SQL injection and to ensure the execution plan remains stable. In Axum with a Rust SQL client such as sqlx, you can structure your route handler so that the database operation does not reveal existence through timing or errors.
use axum::{routing::post, Router, extract::State};
use sqlx::postgres::PgPoolOptions;
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
#[derive(Deserialize)]
struct LoginRequest {
username: String,
password: String,
}
#[derive(Serialize)]
struct LoginResponse {
token: String,
}
struct AppState {
pool: sqlx::PgPool,
}
async fn login_handler(
State(state): State<AppState>,
body: axum::Json<LoginRequest>,
) -> Result<axum::Json<LoginResponse>, (axum::http::StatusCode, String)> {
let username = &body.username;
// Always perform the query, even if username is obviously invalid,
// to keep timing behavior consistent.
let user = sqlx::query!(
"SELECT id, password_hash FROM users WHERE username = $1",
username
)
.fetch_optional(&state.pool)
.await
.map_err(|e| {
// Log detailed error internally, return generic status to client.
tracing::error!(error = %e, "database error");
(axum::http::StatusCode::INTERNAL_SERVER_ERROR, "Login failed".to_string())
})?;
let expected_hash = "$2b$12$EXAMPLEHASHFORUSER"; // In practice, fetch this from user row.
let is_match = subtle::ConstantTimeEq::ct_eq(
&sha256::Hash::digest(user.password_hash.as_bytes()),
&sha256::Hash::digest(expected_hash.as_bytes()),
);
if user.is_some() && is_match.into() {
Ok(axum::Json(LoginResponse {
token: "example_jwt_token".to_string(),
}))
} else {
// Return same status and generic message regardless of whether user exists.
Err((axum::http::StatusCode::UNAUTHORIZED, "Login failed".to_string()))
}
}
#[tokio::main]
async fn main() {-
let pool = PgPoolOptions::new()
.connect(&std::env::var("DATABASE_URL").unwrap_or_else(|_| "postgres://user:pass@localhost/db".into()))
.await
.expect("Failed to create pool");
let app = Router::new()
.route("/login", post(login_handler))
.with_state(AppState { pool });
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
tracing::debug!(listening = %addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
Key points in the example:
- Use
fetch_optionalso that the query path is followed regardless of whether the user exists, avoiding a branch that could be timed. - Use a constant-time comparison for password hashes (e.g., the
subtlecrate) to prevent timing leaks on hash mismatch. - Return the same HTTP status code and generic message for both missing user and invalid password, preventing user enumeration.
- Log detailed database errors server-side only, ensuring that clients do not receive stack traces or constraint details that could inform side channel probes.
For broader protection, enable request rate limiting at the Axum layer (for example via tower::limit::RateLimitLayer) and ensure that all endpoints that perform CockroachDB queries follow the same pattern of uniform execution paths and opaque error handling. middleBrick’s Pro plan includes continuous monitoring and can alert you if scans detect endpoints with inconsistent error codes or missing rate limiting that could facilitate side channel attacks.