HIGH out of bounds writeaxumapi keys

Out Of Bounds Write in Axum with Api Keys

Out Of Bounds Write in Axum with Api Keys — how this specific combination creates or exposes the vulnerability

An Out Of Bounds Write occurs when an API writes data beyond the boundaries of a buffer or data structure. In Axum, this often arises when handling API keys in request processing pipelines where byte-level or slice manipulation is involved. If an API key is used to index into a fixed-size buffer, a missing bounds check can allow writes outside the allocated memory region. This can corrupt adjacent memory, overwrite control data, or lead to undefined behavior that may be exploitable.

Consider an Axum handler that performs key validation by copying an API key into a fixed-length array. If the key length is not validated before copying, an attacker can supply a key longer than the destination buffer, triggering an out of bounds write. For example:

use axum::extract::State;
use std::sync::Arc;

struct AppState {
    allowed_keys: [u8; 32], // fixed-size key store
}

async fn validate_key(
    State(state): State<Arc<AppState>>,
    header: axum::extract::Header<&str>,
) -> Result<(), (axum::http::StatusCode, String)> {
    let key = header.0.as_bytes();
    let mut dest = [0u8; 32];
    // Unsafe: no length check before copy
    unsafe {
        std::ptr::copy_nonoverlapping(key.as_ptr(), dest.as_mut_ptr(), key.len());
    }
    if dest == state.allowed_keys {
        Ok(())
    } else {
        Err((axum::http::StatusCode::UNAUTHORIZED, "invalid key".into()))
    }
}

In this contrived example, key.len() is not bounded by 32, so a key longer than 32 bytes writes beyond dest. This is an out of bounds write because the copy operates on more bytes than the destination buffer can hold. While Rust’s safety guarantees usually prevent such issues, using unsafe blocks bypasses these checks. An attacker could exploit this to corrupt stack variables or attempt code injection, depending on the surrounding memory layout.

Another scenario involves using API keys to control pagination or slicing of response data. If an API key is parsed into an integer that determines slice indices, and those indices are not validated, an attacker might craft a key that results in negative or excessively large offsets. When used to slice a collection or buffer, this can produce an out of bounds write on the underlying storage, especially if the API internally uses mutable buffers or constructs temporary vectors. This ties into broader input validation failures where API keys are treated as trusted data without sanity checks.

Because middleBrick tests unauthenticated attack surfaces, an API that accepts API keys in headers or query parameters without strict length and type validation may be flagged for unsafe memory operations during scanning. The scanner does not infer internal implementation details but observes behaviors consistent with out of bounds write patterns, such as inconsistent responses or crashes when oversized keys are supplied. Remediation focuses on eliminating unchecked copies and enforcing strict bounds on all key-derived indices.

Api Keys-Specific Remediation in Axum — concrete code fixes

Secure handling of API keys in Axum requires explicit bounds checking and avoiding unsafe operations unless strictly necessary. Prefer safe abstractions such as Vec or String with length validation, or use fixed-size arrays with verified sizes. Below are concrete, safe patterns that prevent out of bounds writes when processing API keys.

1. Validate key length before copying into fixed-size buffers

When a fixed-size buffer is required, ensure the source key length does not exceed the buffer size. Use safe methods like copy_from_slice which performs bounds checks at runtime:

use axum::extract::State;
use std::sync::Arc;

struct AppState {
    allowed_keys: [u8; 32],
}

async fn validate_key_safe(
    State(state): State<Arc<AppState>>,
    header: axum::extract::Header<&str>,
) -> Result<(), (axum::http::StatusCode, String)> {
    let key = header.0.as_bytes();
    if key.len() != 32 {
        return Err((axum::http::StatusCode::BAD_REQUEST, "invalid key length".into()));
    }
    let mut dest = [0u8; 32];
    dest.copy_from_slice(key); // Safe: lengths are equal
    if dest == state.allowed_keys {
        Ok(())
    } else {
        Err((axum::http::StatusCode::UNAUTHORIZED, "invalid key".into()))
    }
}

This approach removes unsafe and guarantees no out of bounds writes by enforcing length equality before the copy.

2. Use owned types and avoid fixed buffers when possible

In many cases, storing API keys as String or Vec<u8> is simpler and safer. If you need to compare keys, store them as owned types and compare directly:

use axum::extract::State;
use std::sync::Arc;

struct AppState {
    allowed_keys: Vec<Vec<u8>>, // store keys as owned vectors
}

async fn validate_key_own(
    State(state): State<Arc<AppState>>,
    header: axum::extract::Header<&str>,
) -> Result<(), (axum::http::StatusCode, String)> {
    let key = header.0.as_bytes().to_vec();
    if state.allowed_keys.contains(&key) {
        Ok(())
    } else {
        Err((axum::http::StatusCode::UNAUTHORIZED, "invalid key".into()))
    }
}

This eliminates fixed-size buffers entirely, leveraging Rust’s collections to manage memory safely. The contains method performs safe comparisons without manual index arithmetic.

3. Validate slice indices derived from API keys

If an API key is used to compute indices for slicing collections, validate the resulting range before use:

use axum::extract::State;
use std::sync::Arc;

struct AppState {
    data: Vec<u8>,
}

async fn get_chunk_safe(
    State(state): State<Arc<AppState>>,
    header: axum::extract::Header<&str>,
) -> Result<Vec<u8>, (axum::http::StatusCode, String)> {
    let key = header.0.as_bytes();
    let len = key.len();
    if len > state.data.len() {
        return Err((axum::http::StatusCode::BAD_REQUEST, "key too large".into()));
    }
    // Safe: len is verified to be within data bounds
    let chunk = state.data[..len].to_vec();
    Ok(chunk)
}

Here, the length derived from the API key controls slice bounds. By checking len <= state.data.len() before slicing, we prevent out of bounds reads or writes.

middleBrick’s scans can help identify endpoints that accept API keys and test for unsafe memory handling patterns. While the scanner does not fix code, its findings highlight areas where bounds checks are missing, guiding developers toward safer implementations using these patterns.

Frequently Asked Questions

Can an out of bounds write via API keys lead to remote code execution?
Yes, if an out of bounds write corrupts critical memory structures such as return addresses or function pointers, it may be possible to redirect execution flow. This depends on the runtime environment, compiler protections, and surrounding memory layout, making strict bounds checking essential.
Does middleBrick test for out of bounds writes in API key handling?
middleBrick evaluates unauthenticated attack surfaces and can detect behaviors consistent with out of bounds write patterns, such as inconsistent responses or crashes when oversized keys are supplied. It does not inspect source code but flags endpoints where unsafe memory operations are observable.