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 ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |