HIGH auth bypassactix

Auth Bypass in Actix

How Auth Bypass Manifests in Actix

Auth bypass vulnerabilities in Actix applications often stem from misconfigured middleware, incorrect request routing, or improper state management. The most common pattern occurs when developers assume middleware will always execute, but Actix's routing system can bypass middleware under certain conditions.

Consider this dangerous pattern:

async fn admin_dashboard() -> impl Responder {
    // No auth check here!
    HttpResponse::Ok().body("Admin dashboard")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(middleware::NormalizePath::default())
            .wrap(middleware::Logger::default())
            .service(admin_dashboard)
    }).bind("127.0.0.1:8080")?.run().await
}

The admin endpoint is exposed without any authentication middleware. Actix's route registration order matters—if you register a route before wrapping it with auth middleware, that route remains unprotected.

Another Actix-specific bypass vector involves extractors and async state:

async fn profile(
    user_id: web::Path<String>,
    pool: web::Data<PgPool>,
    auth: Option<AuthUser> // Optional extractor!
) -> impl Responder {
    if let Some(auth) = auth {
        // User is authenticated
        if auth.user_id == user_id {
            return HttpResponse::Ok().json(get_user_profile(&pool, &user_id));
        }
    }
    
    // Fallback for unauthenticated users
    HttpResponse::Unauthorized().finish()
}

Making the auth extractor optional creates a bypass path. An attacker can craft requests that hit the fallback logic without proper authentication checks.

Path normalization attacks are particularly relevant to Actix:

async fn sensitive_data(
    info: web::Path<(String, String)>,
) -> impl Responder {
    // No auth check!
    let (user_id, data_id) = info.into_inner();
    HttpResponse::Ok().json(get_sensitive_data(user_id, data_id))
}

Without proper path normalization middleware, Actix may treat /api/v1/data/./admin and /api/v1/data/admin differently, allowing bypass through crafted path traversal.

Actix-Specific Detection

Detecting auth bypass in Actix requires examining both the routing structure and middleware configuration. The key is identifying unprotected endpoints and understanding Actix's execution flow.

Static analysis with middleBrick's OpenAPI scanner reveals structural issues:

middlebrick scan https://api.example.com/openapi.json

The scanner identifies endpoints missing authentication requirements in their OpenAPI definitions, then attempts unauthenticated requests to verify if auth is actually enforced at runtime.

Runtime detection focuses on middleware chain analysis. In Actix, middleware wraps services in a specific order:

App::new()
    .wrap(middleware::NormalizePath::default())
    .wrap(middleware::Logger::default())
    .wrap(middleware::NormalizePath::default())
    .service(web::scope("/api")
        .wrap(middleware::Authentication::default())
        .service(web::resource("/admin").route(web::get().to(admin_dashboard))))

middleBrick's black-box scanner tests authentication bypass by sending requests with missing/invalid tokens, examining HTTP status codes, and checking response content for sensitive data that shouldn't be exposed.

Specific Actix patterns to scan for:

  • Optional extractors that default to public behavior
  • Routes registered before auth middleware
  • Missing CORS configuration that might expose internal APIs
  • Path parameters that allow traversal or IDOR attacks

The scanner also tests for common Actix-specific bypass techniques like using HEAD requests when GET requires auth, or exploiting Actix's default error handling that might leak information.

Actix-Specific Remediation

Fixing auth bypass in Actix requires understanding its middleware execution model and route registration order. The most robust approach uses scope-based authentication:

async fn admin_dashboard(
    auth_user: AuthUser, // Required extractor
    pool: web::Data<PgPool>,
) -> impl Responder {
    if auth_user.role != "admin" {
        return HttpResponse::Forbidden().finish();
    }
    
    HttpResponse::Ok().json(get_admin_data(&pool))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(middleware::NormalizePath::default())
            .wrap(middleware::Logger::default())
            .service(web::scope("/api")
                .wrap(middleware::Authentication::default())
                .service(web::resource("/admin").route(web::get().to(admin_dashboard))))
    }).bind("127.0.0.1:8080")?.run().await
}

Key improvements: auth middleware wraps the entire scope, extractors are required (not optional), and role-based access control is implemented.

For comprehensive protection, use Actix's built-in guard system:

use actix_web::guard;

async fn admin_only(
    auth_user: AuthUser,
) -> impl Responder {
    if auth_user.role != "admin" {
        return HttpResponse::Forbidden().body("Admin access only");
    }
    HttpResponse::Ok().body("Welcome admin")
}

App::new()
    .service(web::resource("/admin")
        .guard(guard::Header("Authorization"))
        .guard(guard::Header("Content-Type", "application/json"))
        .route(web::get().to(admin_only)))

This ensures both authentication headers and content type are validated before route matching.

Implement proper error handling to prevent information leakage:

async fn protected_resource(
    auth_user: AuthUser,
    path: web::Path<String>,
) -> Result<impl Responder, ApiError> {
    if auth_user.role != "admin" {
        return Err(ApiError::Forbidden);
    }
    
    let data = get_resource(&path.into_inner())
        .await
        .map_err(|e| ApiError::Internal(e.to_string()))?;
    
    Ok(HttpResponse::Ok().json(data))
}

#[derive(Debug)]
enum ApiError {
    Forbidden,
    NotFound,
    Internal(String),
}

impl ResponseError for ApiError {
    fn error_response(&self) -> HttpResponse<Body> {
        match self {
            ApiError::Forbidden => HttpResponse::Forbidden().finish(),
            ApiError::NotFound => HttpResponse::NotFound().finish(),
            ApiError::Internal(e) => {
                log::error!("Internal error: {}", e);
                HttpResponse::InternalServerError().finish()
            }
        }
    }
}

This pattern ensures consistent error responses that don't leak implementation details while maintaining proper authentication enforcement throughout the request lifecycle.

Related CWEs: authentication

CWE IDNameSeverity
CWE-287Improper Authentication CRITICAL
CWE-306Missing Authentication for Critical Function CRITICAL
CWE-307Brute Force HIGH
CWE-308Single-Factor Authentication MEDIUM
CWE-309Use of Password System for Primary Authentication MEDIUM
CWE-347Improper Verification of Cryptographic Signature HIGH
CWE-384Session Fixation HIGH
CWE-521Weak Password Requirements MEDIUM
CWE-613Insufficient Session Expiration MEDIUM
CWE-640Weak Password Recovery HIGH

Frequently Asked Questions

Why does Actix sometimes bypass authentication middleware?
Actix's routing system matches routes before middleware execution in certain cases. If you register a route directly on the App rather than within a wrapped scope, that route won't inherit the middleware. Always wrap entire scopes with authentication middleware rather than individual routes.
Can middleBrick detect auth bypass in Actix applications?
Yes. middleBrick's black-box scanner tests Actix APIs by sending unauthenticated requests to protected endpoints, checking for missing auth headers, and examining response patterns. It also analyzes OpenAPI specs to identify endpoints marked as requiring authentication but potentially vulnerable to bypass through Actix's routing behavior.