HIGH timing attackaxumcockroachdb

Timing Attack in Axum with Cockroachdb

Timing Attack in Axum with Cockroachdb — how this specific combination creates or exposes the vulnerability

A timing attack in the combination of Axum and CockroachDB arises when response times vary measurably based on secret-dependent branching or data access patterns. In Axum, handlers often query CockroachDB using the sqlx crate. If a handler performs different SQL paths or different in-memory operations depending on whether a username exists, or whether a row matches a secret token, an attacker can measure precise response differences over many requests and infer the secret.

Consider an endpoint that retrieves a user by username and compares a provided token with a stored value. If the comparison short-circuits on the first mismatching byte, or if the SQL query filters by username but returns no row for a non-existent user, the time taken for a valid user will differ from an invalid one. CockroachDB, while distributed, still exhibits variable latency for row lookups depending on whether the row is present in the local range lease or requires a distributed round-trip. These small timing differences, combined with network jitter, can be amplified when the attacker is close to the cluster network path.

In Axum, middleware or extractors that conditionally load data can introduce variance. For example, an extractor that first checks a cache layer and then falls back to CockroachDB will have different timing characteristics on a cache hit versus a miss. If the cache check depends on a secret-derived key, an attacker can learn whether a key is present in the cache before the database is consulted. The SQL query itself may also leak timing through index usage: a query that uses an index on a non-selective column may complete faster than a full table scan when the predicate matches few rows, exposing relative match counts.

Real-world attack patterns relevant here include measuring response times to infer existence of usernames (user enumeration) or to deduce validity of password reset tokens stored in CockroachDB. These map to OWASP API Top 10 A01:2023 — Broken Access Control and A02:2023 — Cryptographic Failures, because timing can unintentionally disclose information that should remain private. PCI-DSS and SOC2 also expect protection against inference attacks where feasible.

Concrete example in Axum with CockroachDB using sqlx::postgres::PgPoolOptions:

use axum::{routing::get, Router};
use sqlx::{postgres::PgPoolOptions, PgPool};
use std::net::SocketAddr;

async fn user_handler(
    axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>,
    pool: &PgPool,
) -> String {
    let username = params.get("username").unwrap_or("");
    // This branch leaks timing: exists() returns true/false with measurable differences
    let exists: bool = sqlx::query_scalar(
        "SELECT EXISTS(SELECT 1 FROM users WHERE username = $1)"
    )
    .bind(username)
    .fetch_one(pool)
    .await
    .unwrap_or(false);
    if exists {
        // Additional work for existing user
        let _email: String = sqlx::query_scalar("SELECT email FROM users WHERE username = $1")
            .bind(username)
            .fetch_one(pool)
            .await
            .unwrap_or_default();
        format!("User {} exists", username)
    } else {
        format!("User not found")
    }
}

#[tokio::main]
async fn main() -> sqlx::Result<()> {
    let pool = PgPoolOptions::new()
        .connect("postgres://user:pass@localhost/cockroachdb?sslmode=require")
        .await?;
    let app = Router::new().route("/user", get(user_handler));
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    axum::Server::bind(&addr).serve(app.into_make_service()).await.unwrap();
    Ok(())
}

In this snippet, the exists query timing can differ based on whether the row is present. An attacker can send many requests with guessed usernames and measure response times to infer valid usernames. Even when using CockroachDB’s strong consistency, local vs remote range lookups can introduce measurable variance.

Cockroachdb-Specific Remediation in Axum — concrete code fixes

To mitigate timing attacks in Axum with CockroachDB, ensure that all code paths involving secret-dependent decisions take constant time and produce uniform load patterns. Use constant-time comparison for secrets and avoid branching on secret-dependent values.

1. Replace boolean existence checks with a constant-time fetch that always performs the same query work. Fetch the row if it exists, and use a constant-time comparison for tokens.

use axum::routing::get;
use sqlx::{postgres::PgPoolOptions, PgPool, postgres::types::Json;
use subtle::ConstantTimeEq;
use std::net::SocketAddr;

async fn safe_user_handler(
    axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>,
    pool: &PgPool,
) -> String {
    let username = params.get("username").unwrap_or("");
    // Always fetch the row; avoid branching on existence
    let row: Option<(String, String)> = sqlx::query_as(
        "SELECT username, email FROM users WHERE username = $1"
    )
    .bind(username)
    .fetch_optional(pool)
    .await
    .unwrap_or_default();

    // Simulate work for non-existent users to mask timing differences
    let dummy_token = "0123456789abcdef0123456789abcdef";
    let provided_token = "provided_by_client"; // In practice from request
    let token_ct = subtle::ConstantTimeEq::ct_eq(
        &provided_token.as_bytes(),
        &dummy_token.as_bytes(),
    );
    // token_ct is not used to gate control flow in a secret-dependent way;
    // it only ensures timing does not reveal match/mismatch.

    if let Some((u, email)) = row {
        // Perform same class of work regardless of user existence
        format!("User {} email: {}", u, email)
    } else {
        // Do equivalent work to mask timing
        format!("User not found, token check performed")
    }
}

2. Use parameterized queries that always access an index to avoid full scans that vary by data size. Ensure the username column is indexed in CockroachDB.

-- CockroachDB SQL: ensure index exists
CREATE INDEX IF NOT EXISTS idx_users_username ON users (username);

3. For token validation, avoid early exit comparisons. Use a fixed-time comparison library such as subtle and ensure the comparison always processes the full expected length regardless of input length.

use subtle::ConstantTimeEq;

fn verify_token(stored: &str, provided: &str) -> bool {
    // Use a fixed-length secret or pad to constant length to avoid length leaks
    let stored_padded = format!("{stored:-<64}"); // pad to 64 chars
    let provided_padded = format!("{provided:-<64}");
    stored_padded
        .as_bytes()
        .ct_eq(provided_padded.as_bytes())
        .into()
}

4. Consider using middleware that normalizes response times for sensitive endpoints, though this is a supplementary measure and not a replacement for constant-time code.

Products such as the middleBrick CLI can be used to scan your Axum endpoints against timing-related security checks; the GitHub Action can fail builds if risk scores exceed thresholds; and the MCP Server allows scanning APIs directly from your AI coding assistant while you develop in Axum.

Frequently Asked Questions

Can timing variations in CockroachDB be reliably measured over the network?
Yes, if an attacker can send many requests and measure round-trip times with sufficient precision, they can detect differences caused by cache hits, index usage, or distributed vs local reads in CockroachDB. This is why constant-time code paths are essential.
Does using an ORM or sqlx in Axum prevent timing attacks by default?
No. ORMs and sqlx do not inherently prevent timing attacks. Developers must design queries and comparison logic to avoid secret-dependent branches and ensure uniform execution paths regardless of data presence.