Cross Site Request Forgery in Actix with Dynamodb
Cross Site Request Forgery in Actix with Dynamodb — how this specific combination creates or exposes the vulnerability
Cross Site Request Forgery (CSRF) in an Actix web service that uses DynamoDB as a backend can occur when state-changing requests rely on identity signals that are not verified per request. In a typical Actix application, a handler reads a user identifier from a session cookie or from an Authorization header and then issues DynamoDB requests using that identifier. If the application does not include a per-request anti-CSRF token or a strict SameSite/cookie configuration, an attacker can craft a form or script on another origin that causes the user’s browser to send authenticated Actix requests to the vulnerable endpoint. Because the requests include valid session or token credentials, Actix processes them and issues DynamoDB operations on behalf of the user, even though the user did not intend to perform those actions.
DynamoDB does not enforce origin checks or CSRF protections itself; it simply executes the operations (GetItem, UpdateItem, DeleteItem) that the caller requests. If an Actix handler updates a sensitive resource—such as changing an email, enabling a feature, or updating an item’s attributes—using only a user identifier in the request (for example, a path or query parameter like user_id), an attacker can trick the user into submitting a crafted request. A concrete example is an endpoint POST /account/email that accepts a JSON body with new_email and derives the user_id from the authenticated session. Without a CSRF token, an attacker can host a malicious page that sends a forged JSON payload to this endpoint, and because cookies are included automatically, Actix authorizes the update and executes the corresponding DynamoDB UpdateItem call.
This risk is amplified when the Actix service exposes an OpenAPI spec that tightly couples resource paths with DynamoDB key structures. If the API accepts an item_id from the client and uses it directly in a DynamoDB UpdateItem key, an attacker can enumerate or guess identifiers and combine that with CSRF to perform unauthorized updates on other users’ items. The combination of Actix’s async request handling and DynamoDB’s permissive IAM policies can allow unintended mutations if authorization checks are incomplete. For instance, an UpdateItem operation that lacks a condition to ensure the user_id in the key matches the authenticated principal enables privilege escalation across users. Therefore, the threat in this stack is not inherent to DynamoDB but arises from missing checks in Actix handlers that fail to bind each request to the authenticated identity with a CSRF mitigation.
Dynamodb-Specific Remediation in Actix — concrete code fixes
To remediate CSRF in Actix when interacting with DynamoDB, bind every state-changing operation to the authenticated identity on the server and require a per-request anti-CSRF token for any request that can execute a destructive or sensitive mutation. Do not rely solely on cookie-based authentication; instead, extract the authenticated user identifier from the session or token on each request and use it as a strict filter in DynamoDB operations. Below are concrete Actix handler examples that demonstrate a safe pattern.
Example: Safe Update Email with CSRF token and DynamoDB condition
In this example, the Actix handler expects a CSRF token in a custom header, validates it against a per-session value stored server-side (e.g., in Redis), and then issues a DynamoDB UpdateItem that includes a condition expression ensuring the user_id matches the authenticated identity. This prevents an attacker from leveraging a forged request to update another user’s email.
use actix_web::{web, HttpResponse, HttpRequest};
use aws_sdk_dynamodb::Client;
use serde_json::json;
async fn update_email(
req: HttpRequest,
body: web::Json,
ddb: web::Data,
session_store: web::Data, // server-side session store
) -> HttpResponse {
// 1) Extract authenticated user from session (e.g., via JWT or session cookie)
let user_id = match req.cookie("session_id").and_then(|c| session_store.get_user(&c.value())) {
Some(u) => u,
None => return HttpResponse::Unauthorized().finish(),
};
// 2) Validate CSRF token sent in header (e.g., "X-CSRF-Token")
let provided_token = match req.headers().get("X-CSRF-Token") {
Some(h) => match h.to_str() {
Ok(s) => s,
Err(_) => return HttpResponse::BadRequest().finish(),
},
None => return HttpResponse::Forbidden().body("Missing CSRF token"),
};
if !session_store.validate_csrf(&user_id, provided_token) {
return HttpResponse::Forbidden().body("Invalid CSRF token");
}
// 3) Perform DynamoDB update with a condition that the user_id matches
let payload = body.into_inner();
let table_name = "users";
let result = ddb.update_item()
.table_name(table_name)
.key("user_id".to_string(), aws_sdk_dynamodb::types::AttributeValue::S(user_id.clone()))
.update_expression("set email = :new_email")
.expression_attribute_values(":new_email".to_string(), aws_sdk_dynamodb::types::AttributeValue::S(payload.new_email))
.condition_expression("user_id = :expected_user")
.expression_attribute_values(":expected_user".to_string(), aws_sdk_dynamodb::types::AttributeValue::S(user_id))
.send()
.await;
match result {
Ok(_) => HttpResponse::Ok().json(json!({ "status": "email_updated" })),
Err(e) => HttpResponse::InternalServerError().body(format!("DynamoDB error: {:?}", e)),
}
}
#[derive(serde::Deserialize)]
struct UpdateEmailPayload {
new_email: String,
}
The handler verifies the authenticated user_id, ensures the request carries a valid CSRF token, and uses a DynamoDB condition expression to guarantee the update applies only to the intended user. This combination eliminates the CSRF vector while maintaining correct authorization semantics.
Example: Delete item with owner verification
When deleting an item, derive the key from both the client-supplied identifier and the authenticated identity, and include a condition to ensure ownership. This prevents an attacker who can inject a forged request from deleting another user’s item even if they guess or learn an item ID.
async fn delete_item(
req: HttpRequest,
path: web::Path, // item_id from URL
ddb: web::Data,
session_store: web::Data,
) -> HttpResponse {
let item_id = path.into_inner();
// Authenticated user
let user_id = match req.cookie("session_id").and_then(|c| session_store.get_user(&c.value())) {
Some(u) => u,
None => return HttpResponse::Unauthorized().finish(),
};
// CSRF protection
let csrf_token = match req.headers().get("X-CSRF-Token") {
Some(h) => h.to_str().unwrap_or(""),
None => return HttpResponse::Forbidden().body("Missing CSRF token"),
};
if !session_store.validate_csrf(&user_id, csrf_token) {
return HttpResponse::Forbidden().body("Invalid CSRF token");
}
// Ensure the item belongs to the authenticated user
let result = ddb.delete_item()
.table_name("user_items")
.key("user_id", aws_sdk_dynamodb::types::AttributeValue::S(user_id.clone()))
.key("item_id", aws_sdk_dynamodb::types::AttributeValue::S(item_id.clone()))
.condition_expression("user_id = :uid")
.expression_attribute_values(":uid".to_string(), aws_sdk_dynamodb::types::AttributeValue::S(user_id))
.send()
.await;
match result {
Ok(_) => HttpResponse::NoContent().finish(),
Err(e) => HttpResponse::InternalServerError().body(format!("DynamoDB error: {:?}", e)),
}
}
By coupling the authenticated identity with a server-side CSRF token and using DynamoDB condition expressions, you ensure that each mutation is explicitly bound to the requesting user, mitigating CSRF in Actix applications that rely on DynamoDB.