Integrity Failures in Actix
How Integrity Failures Manifests in Actix
Integrity failures in Actix applications typically occur when the framework's powerful routing and extraction mechanisms are combined with insufficient authorization checks. Actix's path parameter extraction and JSON deserialization features, while convenient, can create security gaps if developers assume that matching a route automatically authorizes access to the resource.
A common pattern involves Actix's Path extractor combined with database operations. Consider an endpoint that retrieves user data by ID:
use actix_web::{get, web, HttpResponse, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct UserDataParams {
user_id: i32,
}
#[get("/users/{user_id}")]
async fn get_user(params: web::Path<UserDataParams>, db: web::Data<PgPool>) -> impl Responder {
let user = sqlx::query_as("SELECT * FROM users WHERE id = $1")
.bind(params.user_id)
.fetch_one(&db)
.await?;
HttpResponse::Ok().json(user)
}
This endpoint is vulnerable to IDOR (Insecure Direct Object Reference) attacks. Any authenticated user can modify the user_id parameter to access other users' data. Actix's path extraction makes it trivial to manipulate these values, and without proper authorization checks, the integrity of user data is compromised.
Another manifestation occurs with Actix's JSON deserialization. The framework's Json extractor automatically parses request bodies into structs:
use actix_web::{post, web, HttpResponse, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct UpdateUserRequest {
email: String,
phone: String,
}
#[post("/users/{user_id}/update")]
async fn update_user(
params: web::Path<UserIdParams>,
body: web::Json<UpdateUserRequest>,
db: web::Data<PgPool>,
) -> impl Responder {
sqlx::query("UPDATE users SET email = $1, phone = $2 WHERE id = $3")
.bind(body.email.clone())
.bind(body.phone.clone())
.bind(params.user_id)
.execute(&db)
.await?;
HttpResponse::Ok().finish()
}
This endpoint suffers from both BOLA (Broken Object Level Authorization) and BFLA (Broken Function Level Authorization) vulnerabilities. An attacker can update any user's profile by simply changing the user_id in the URL, regardless of whether they own that account or have permission to perform the update.
Actix's middleware system can also contribute to integrity failures when authentication checks are incomplete. A common mistake is implementing authentication middleware that only verifies the presence of a token, not whether the token grants access to the specific resource being requested:
use actix_web::{middleware, web, App, HttpServer};
async fn auth_middleware(
req: actix_web::dev::ServiceRequest,
srv: &mut actix_web::dev::Service<actix_web::dev::ServiceRequest, actix_web::dev::ServiceResponse>,
) -> actix_web::Result<actix_web::dev::ServiceResponse> {
// Only checks for token presence, not resource ownership
if let Some(auth) = req.headers().get("Authorization") {
return srv.call(req);
}
Ok(req.into_response(
HttpResponse::Unauthorized().finish().into_body()
))
}
This middleware would allow any authenticated user to access any resource, completely failing to enforce data integrity boundaries.
Actix-Specific Detection
Detecting integrity failures in Actix applications requires examining both the routing patterns and the authorization logic that connects requests to data access. The framework's declarative routing makes it straightforward to identify endpoints that accept path parameters or JSON bodies without corresponding authorization checks.
Static analysis of Actix route definitions can reveal potential vulnerabilities. Look for patterns where web::Path or web::Json extractors are used without subsequent authorization checks:
use actix_web::{get, post, web, Responder};
// Vulnerable pattern - no authorization check
#[get("/accounts/{account_id}")]
async fn get_account(params: web::Path<AccountId>) -> impl Responder {
// Direct database access using path parameter
Account::find_by_id(params.account_id)
}
// Secure pattern - includes authorization
#[get("/accounts/{account_id}")]
async fn get_account(
params: web::Path<AccountId>,
auth: actix_web::middleware::ExtractJwt,
db: web::Data<DbPool>,
) -> impl Responder {
// Verify user owns this account
if !auth.user().owns_account(params.account_id) {
return HttpResponse::Forbidden().finish();
}
Account::find_by_id(params.account_id)
}
Dynamic scanning with middleBrick can identify these vulnerabilities by actively testing the unauthenticated attack surface. The scanner examines Actix endpoints for BOLA/IDOR vulnerabilities by systematically modifying path parameters and JSON payloads to access resources that should be protected. For example, it would test whether changing user_id in /users/{user_id} endpoints returns different users' data.
middleBrick's OpenAPI analysis is particularly effective for Actix applications since the framework's route definitions map cleanly to OpenAPI specifications. The scanner cross-references the spec's parameter definitions with actual runtime behavior, identifying endpoints where parameters are used in database queries without corresponding security constraints.
Property authorization failures in Actix often involve partial object disclosure. The framework's serialization features can inadvertently expose sensitive fields:
#[derive(Serialize)]
struct User {
id: i32,
email: String,
ssn: String, // Should not be exposed to all users
balance: f64, // Financial data requiring authorization
}
#[get("/users/{id}")]
async fn get_user(params: web::Path<i32>) -> impl Responder {
let user = User::find_by_id(params.id);
HttpResponse::Ok().json(user) // Exposes all fields
}
middleBrick detects these property authorization issues by analyzing the data structures returned by Actix endpoints and comparing them against expected access patterns for different user roles.
Actix-Specific Remediation
Remediating integrity failures in Actix applications requires implementing proper authorization checks that verify both user identity and resource ownership. Actix's middleware and extractor systems provide excellent foundations for building secure authorization layers.
The most effective approach is creating a custom authorization extractor that validates resource access before the handler executes:
use actix_web::{get, post, web, Responder, HttpResponse, http::header};
use actix_web::dev::ServiceRequest;
// Custom extractor for authorized access
pub struct AuthorizedResource<T> {
pub resource: T,
pub user_id: i32,
}
impl<T> actix_web::FromRequest for AuthorizedResource<T>
where
T: actix_web::FromRequest,
{
type Config = T::Config;
type Error = actix_web::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
fn from_request(
req: &ServiceRequest,
payload: &mut actix_web::dev::Payload,
) -> Self::Future {
let fut = T::from_request(req, payload);
Box::pin(async move {
let resource = fut.await?;
// Extract user ID from authentication
let user_id = req
.headers()
.get("X-User-Id")
.and_then(|h| h.to_str().ok())
.and_then(|s| s.parse().ok())
.ok_or_else(|| HttpResponse::Unauthorized().finish())?;
// Verify resource ownership
if !verify_ownership(&resource, user_id).await? {
return Err(HttpResponse::Forbidden().finish().into());
}
Ok(AuthorizedResource { resource, user_id })
})
}
}
// Usage in handlers
#[get("/users/{user_id}")]
async fn get_user(
params: web::Path<i32>,
auth: AuthorizedResource<i32>,
) -> impl Responder {
// auth.resource == params.user_id and ownership verified
let user = User::find_by_id(auth.resource).await?;
HttpResponse::Ok().json(user)
}
For property-level authorization, Actix's serialization features can be leveraged to control field exposure based on user roles:
use actix_web::{get, Responder};
use serde::{Serialize, Serializer};
#[derive(Serialize)]
struct User {
id: i32,
email: String,
#[serde(serialize_with = "serialize_sensitive_fields")]
sensitive_data: SensitiveData,
}
#[derive(Serialize)]
struct SensitiveData {
ssn: String,
balance: f64,
}
fn serialize_sensitive_fields<S>(
data: &SensitiveData,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Conditionally serialize based on user role
let req = actix_web::HttpRequest::current();
let user_role = req.headers().get("X-User-Role")?.to_str()?;
if user_role == "admin" {
SensitiveData { ssn: data.ssn.clone(), balance: data.balance }.serialize(serializer)
} else {
// Return empty or redacted data
SensitiveData { ssn: "REDACTED".to_string(), balance: 0.0 }.serialize(serializer)
}
}
Actix's middleware system is ideal for implementing cross-cutting authorization policies:
use actix_web::{middleware, web, App, HttpServer, HttpResponse};
struct AuthorizationMiddleware;
impl middleware::Middleware for AuthorizationMiddleware {
fn start(&self, req: &ServiceRequest) -> actix_web::Result<middleware::Started> {
// Check if user is authenticated
if let Some(auth) = req.headers().get("Authorization") {
// Extract user ID and roles from token
let user_info = decode_token(auth.to_str()?)?;
// Store user info for later use
req.extensions_mut().insert(user_info);
Ok(middleware::Started::Done)
} else {
Ok(middleware::Started::Done) // Continue without auth
}
}
fn finish(&self, req: &ServiceRequest, resp: &ServiceResponse) -> actix_web::Result<()> {
// Log or audit authorization decisions
Ok(())
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(AuthorizationMiddleware)
.service(get_user)
.service(update_user)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Database-level authorization using row-level security (RLS) provides an additional defense layer that complements Actix's application-level checks:
// PostgreSQL RLS policy
CREATE POLICY user_access ON users
FOR SELECT USING (id = current_setting('app.current_user_id')::int);
// Actix integration
async fn get_user_with_rls(
params: web::Path<i32>,
db: web::Data<PgPool>,
) -> impl Responder {
// Set RLS context
sqlx::query("SET app.current_user_id = $1")
.bind(params.user_id)
.execute(&db)
.await?;
// RLS automatically filters results
let user = sqlx::query_as("SELECT * FROM users WHERE id = $1")
.bind(params.user_id)
.fetch_one(&db)
.await?;
HttpResponse::Ok().json(user)
}
Frequently Asked Questions
How does middleBrick detect integrity failures in Actix applications?
What makes Actix particularly vulnerable to integrity failures?
web::Path and web::Json extractors can create a false sense of security, leading developers to assume that matching a route implies authorization. Additionally, Actix's serialization features can inadvertently expose sensitive data fields if not properly controlled with role-based access controls.