Insecure Direct Object Reference in Actix
How Insecure Direct Object Reference Manifests in Actix
Insecure Direct Object Reference (IDOR) in Actix applications typically occurs when route parameters are used directly to access database records without proper authorization checks. This vulnerability allows attackers to manipulate identifiers in API requests to access resources they shouldn't have permission to view or modify.
A common Actix pattern that creates IDOR vulnerabilities looks like this:
async fn get_user_profile(info: web::Path<(String, String)>, db: web::Data) -> impl Responder {
let (user_id, profile_id) = info.into_inner();
// Direct database access using user-provided ID
let result = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1")
.bind(user_id)
.fetch_one(&db)
.await;
match result {
Ok(user) => HttpResponse::Ok().json(user),
Err(_) => HttpResponse::NotFound().finish(),
}
}
This code is vulnerable because it trusts the user_id parameter from the URL without verifying that the authenticated user has permission to access that specific user's data. An attacker could simply change the user_id in the request to access any user's profile.
Another Actix-specific IDOR pattern occurs with query parameters:
async fn get_document(info: web::Query, db: web::Data) -> impl Responder {
let doc_id = info.doc_id;
// No authorization check before accessing document
let result = sqlx::query_as::<_, Document>("SELECT * FROM documents WHERE id = $1")
.bind(doc_id)
.fetch_one(&db)
.await;
match result {
Ok(doc) => HttpResponse::Ok().json(doc),
Err(_) => HttpResponse::NotFound().finish(),
}
}
Actix's strong typing and async/await patterns can actually make IDOR more dangerous when combined with improper authorization. The clean, straightforward syntax can create a false sense of security, leading developers to overlook authorization checks.
Path-based IDOR is particularly common in Actix applications with hierarchical resource structures:
async fn get_team_project(info: web::Path<(String, String)>, db: web::Data) -> impl Responder {
let (team_id, project_id) = info.into_inner();
// Direct access using both IDs without authorization
let result = sqlx::query_as::<_, Project>("SELECT * FROM projects WHERE team_id = $1 AND id = $2")
.bind(team_id)
.bind(project_id)
.fetch_one(&db)
.await;
match result {
Ok(project) => HttpResponse::Ok().json(project),
Err(_) => HttpResponse::NotFound().finish(),
}
}
The vulnerability here is that while the query filters by both team_id and project_id, there's no check to ensure the authenticated user belongs to that team or has access to that project.
Actix-Specific Detection
Detecting IDOR vulnerabilities in Actix applications requires both static analysis and dynamic testing. For static analysis, look for patterns where route parameters are directly used in database queries without authorization checks.
middleBrick's API security scanner can detect IDOR vulnerabilities in Actix applications by analyzing the unauthenticated attack surface. The scanner tests for IDOR by attempting to access resources using manipulated identifiers and checking if authorization controls are properly enforced.
Here's how you can use middleBrick to scan an Actix API endpoint:
# Install middleBrick CLI
npm install -g middlebrick
# Scan an Actix API endpoint
middlebrick scan https://api.example.com/users/{user_id}/profile
The scanner will test for IDOR by attempting to access different user IDs and checking if the application properly restricts access. It looks for responses that indicate whether a resource exists without proper authorization.
For manual testing of Actix applications, you can use tools like curl or Postman to test IDOR vulnerabilities:
# Test if you can access another user's profile
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://api.example.com/users/999/profile
# Test if you can access documents by ID
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://api.example.com/documents/9999
Look for responses that return 200 OK with data for resources you shouldn't have access to, or 404 Not Found that reveals whether a resource exists (information disclosure).
middleBrick's scanning includes specific checks for Actix patterns, such as:
- Path parameter injection testing
- Query parameter manipulation
- Header-based IDOR attempts
- Cross-user resource access attempts
- Enumeration of valid resource IDs
The scanner provides a security score and detailed findings, helping you identify which endpoints are vulnerable to IDOR attacks.
Actix-Specific Remediation
Remediating IDOR vulnerabilities in Actix applications requires implementing proper authorization checks before accessing resources. The most effective approach is to use Actix's middleware and extractors to centralize authorization logic.
Here's a secure pattern using Actix middleware for authorization:
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, HttpMessage, Error};
use actix_web::middleware::Middleware;
use actix_web::web::Data;
use sqlx::Pool;
struct AuthorizationMiddleware;
impl Middleware for AuthorizationMiddleware {
async fn call(&self, req: ServiceRequest, srv: &S) -> Result
where
S: actix_web::dev::Service,
{
// Extract user ID from token
let user_id = match extract_user_id_from_token(req.headers()) {
Some(id) => id,
None => {
return Ok(req.into_response(
HttpResponse::Unauthorized().finish()
));
}
};
// Store user ID in request extension for later use
req.extensions_mut().insert::(UserId(user_id));
srv.call(req).await
}
}
#[derive(Clone)]
struct UserId(String);
async fn get_user_profile(
info: web::Path<(String, String)>,
db: web::Data,
user_id: web::ReqData
) -> impl Responder {
let (requested_user_id, profile_id) = info.into_inner();
// Check if user is accessing their own profile or has admin rights
if requested_user_id != user_id.0 && !is_admin(&db, &user_id.0).await? {
return HttpResponse::Forbidden().finish();
}
// Now it's safe to access the user's profile
let result = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1")
.bind(requested_user_id)
.fetch_one(&db)
.await;
match result {
Ok(user) => HttpResponse::Ok().json(user),
Err(_) => HttpResponse::NotFound().finish(),
}
}
This pattern ensures that every request goes through authorization middleware before reaching the handler, preventing IDOR vulnerabilities.
For resource-specific authorization, use Actix's extractors to validate access rights:
async fn get_team_project(
info: web::Path<(String, String)>,
db: web::Data,
user_id: web::ReqData
) -> impl Responder {
let (team_id, project_id) = info.into_inner();
// Check if user belongs to team and has project access
if !has_team_access(&db, &user_id.0, &team_id).await? {
return HttpResponse::Forbidden().finish();
}
// Check if user has access to specific project
if !has_project_access(&db, &user_id.0, &project_id).await? {
return HttpResponse::Forbidden().finish();
}
// Safe to access project data
let result = sqlx::query_as::<_, Project>("SELECT * FROM projects WHERE team_id = $1 AND id = $2")
.bind(team_id)
.bind(project_id)
.fetch_one(&db)
.await;
match result {
Ok(project) => HttpResponse::Ok().json(project),
Err(_) => HttpResponse::NotFound().finish(),
}
}
Another effective pattern is using Actix's request extension to store user context:
use actix_web::FromRequest;
struct AuthenticatedUser(String);
impl FromRequest for AuthenticatedUser {
type Error = actix_web::Error;
type Future = futures::future::Ready>;
type Config = ();
fn from_request(req: &HttpRequest, payload: &mut actix_web::dev::Payload) -> Self::Future {
// Extract and validate user from token
let user_id = extract_user_id_from_token(req.headers());
if let Some(id) = user_id {
futures::future::ready(Ok(AuthenticatedUser(id)))
} else {
futures::future::ready(Err(actix_web::error::ErrorUnauthorized("Unauthorized")))
}
}
}
async fn get_user_data(
user_id: web::Path,
auth_user: AuthenticatedUser,
db: web::Data
) -> impl Responder {
// Check if authenticated user can access requested user data
if user_id.0 != auth_user.0 && !is_admin(&db, &auth_user.0).await? {
return HttpResponse::Forbidden().finish();
}
// Safe to proceed with data access
let result = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1")
.bind(user_id.0)
.fetch_one(&db)
.await;
match result {
Ok(user) => HttpResponse::Ok().json(user),
Err(_) => HttpResponse::NotFound().finish(),
}
}
These patterns ensure that IDOR vulnerabilities are prevented by validating user permissions before any database access occurs, leveraging Actix's type system and middleware architecture for secure API design.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |