Idor Enumeration in Actix
How Idor Enumeration Manifests in Actix
In Actix web applications, IDOR enumeration typically occurs when endpoint handlers extract resource identifiers from path parameters or query strings without verifying that the authenticated user is authorized to access the specific resource instance. Actix's routing system, which uses macros like #[get("/users/{id}")] or service(web::resource("/users/{id}").route(web::get().to(get_user))), makes it easy to bind path parameters directly to handler functions. If the handler uses this identifier to fetch data from a database or service without an authorization check, attackers can enumerate identifiers to access other users' data.
A common pattern in Actix involves handlers that retrieve a user ID from the path and use it directly in a database query. For example, a profile endpoint might look like this:
#[get("/profile/{user_id}")]
async fn get_profile(path: web::Path<i32>, pool: web::Data<Pool>) -> impl Responder {
let user_id = path.into_inner();
let conn = pool.get().expect("failed to get db conn");
let user = web::block(move || {
sql_query_as!(User, "SELECT * FROM users WHERE id = $1", user_id)
.fetch_one(&conn)
.await
})
.await
.map_err(actix_web::error::ErrorInternalServerError)?;
HttpResponse::Ok().json(user)
}
This code is vulnerable to IDOR because there is no check ensuring that the user_id from the path matches the ID of the currently authenticated user. An attacker can simply change the {user_id} value in the URL to enumerate and retrieve profiles of other users. Actix's type-safe path extraction (web::Path<i32>) ensures the parameter is an integer but does not inherently enforce authorization, making this a frequent oversight in Actix applications where authentication (e.g., via actix-web-httpauth or JWT middleware) is present but not coupled with object-level authorization checks in the handler.
Another Actix-specific vector arises in resource-based routing with multiple methods. Consider a web::Scope for user management:
app.service(
web::scope("/api/users")
.service(web::resource("/{id}")
.route(web::get().to(get_user_by_id))
.route(web::put().to(update_user_by_id))
.route(web::delete().to(delete_user_by_id))
)
);
If get_user_by_id, update_user_by_id, or delete_user_by_id handlers use the {id} path parameter without validating that the authenticated user owns or is permitted to act on that resource, each method becomes an entry point for IDOR enumeration. Attackers can iterate through numeric IDs to scan for accessible resources, exploiting Actix's straightforward path matching to bypass business logic intended to restrict access to one's own data.
Actix-Specific Detection
Detecting IDOR enumeration in Actix applications requires analyzing both the routing structure and handler logic for missing authorization checks. middleBrick identifies this vulnerability during its black-box scan by probing unauthenticated and authenticated endpoints with parameter manipulation. It sends requests to paths like /api/users/1, /api/users/2, etc., and compares responses to determine if sensitive data is exposed without proper authorization. For Actix, middleBrick pays particular attention to endpoints that follow Actix's conventional routing patterns, such as those using path parameters for resource IDs ({id}, {user_id}) under scopes like /users, /profiles, or /orders.
During scanning, middleBrick looks for signs of successful enumeration: HTTP 200 responses containing user-specific data (e.g., email, name, internal IDs) when the identifier is incremented or decremented. It also checks for inconsistent responses — such as 404 for some IDs and 200 for others — which may indicate valid but unauthorized resource access. Since Actix applications often return JSON payloads, middleBrick parses response bodies to detect patterns of personal data leakage across sequential ID probes.
To test your Actix application locally before using middleBrick, you can manually simulate enumeration with tools like curl in a loop. For example, if you suspect a profile endpoint is vulnerable:
#!/bin/bash
BASE_URL="https://your-actix-app.com/api/profiles"
for id in {1..100}; do
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/$id")
if [ "$RESPONSE" -eq 200 ]; then
echo "ID $id: Accessible (HTTP 200)"
curl -s "$BASE_URL/$id" | jq . # Inspect data if needed
fi
done
This script checks HTTP status codes; a series of 200 responses indicates potential IDOR. middleBrick automates this process at scale, testing hundreds of identifiers in parallel within its 5–15 second scan window, and correlates findings with authentication context — flagging endpoints that return sensitive data when accessed with arbitrary IDs, even if the application requires authentication elsewhere (as the check may be missing at the object level).
Actix-Specific Remediation
Fixing IDOR in Actix requires implementing authorization checks that verify the authenticated user's identity matches or is permitted to access the resource identified by the path parameter. Actix does not include built-in object-level authorization, so this must be added in handlers or via middleware. The solution involves extracting the authenticated user's identity (e.g., from JWT, session, or auth middleware) and comparing it to the requested resource ID before proceeding with data access.
Consider the earlier vulnerable profile endpoint. To remediate, first ensure authentication is in place (e.g., using actix-web-httpauth with Bearer tokens). Then, modify the handler to compare the path parameter with the authenticated user's ID:
use actix_web::{web, HttpResponse, Responder, Error};
use actix_web_httpauth::extractors::bearer::BearerAuth;
#[get("/profile/{user_id}")]
async fn get_profile(
path: web::Path<i32>,
req: actix_web::HttpRequest,
pool: web::Data<Pool>
) -> Result<impl Responder, Error>
{
let requested_id = path.into_inner();
// Extract authenticated user ID from request extensions (set by auth middleware)
let auth_id = req
.extensions()
.get::()
.ok_or_else(|| actix_web::error::ErrorUnauthorized("Missing auth context"))?;
if requested_id != *auth_id {
return Err(actix_web::error::ErrorForbidden("Access denied"));
}
let conn = pool.get().expect("failed to get db conn");
let user = web::block(move || {
sql_query_as!(User, "SELECT * FROM users WHERE id = $1", requested_id)
.fetch_one(&conn)
.await
})
.await
.map_err(actix_web::error::ErrorInternalServerError)?;
Ok(HttpResponse::Ok().json(user))
}
In this example, the authentication middleware (not shown) is responsible for validating the token and inserting the authenticated user's ID into req.extensions(). The handler then retrieves this ID and compares it to the {user_id} from the path. If they do not match, it returns a 403 Forbidden response. This pattern ensures that even if an attacker enumerates IDs, they can only access their own profile.
For applications using role-based access (e.g., admins can view any profile), adjust the logic accordingly:
let auth_id = req.extensions().get::().ok_or_else(|| ErrorUnauthorized("Missing auth"))?;
let is_admin = req.extensions().get::().unwrap_or(&false);
if requested_id != *auth_id && !is_admin {
return Err(ErrorForbidden("Insufficient permissions"));
}
To avoid repeating this check in every handler, consider creating a custom Actix extractor or middleware that automates the comparison. For instance, a Guard from actix-web's guard system can be used with service() to protect entire scopes:
use actix_web::guard;
let profile_guard = guard::Fn(|req| {
let path = req.match_info().get("user_id").unwrap_or("0");
let Ok(requested_id) = path.parse::() else { return false; };
let auth_id = req.extensions().get::().copied().unwrap_or(-1);
requested_id == auth_id || req.extensions().get::().unwrap_or(&false) // admin check
});
app.service(
web::scope("/api/profiles")
.guard(profile_guard)
.service(web::resource("/{id}").to(get_profile))
);
This approach centralizes authorization logic, reducing the chance of oversight in individual handlers. Regardless of the method, the key is to ensure that every Actix endpoint that uses path parameters for resource identification includes a verification step that the requesting user is authorized for that specific object, closing the enumeration vector that attackers exploit.