HIGH cross site request forgeryactixmutual tls

Cross Site Request Forgery in Actix with Mutual Tls

Cross Site Request Forgery in Actix with Mutual Tls

Cross-site request forgery (CSRF) in Actix with mutual TLS (mTLS) involves a combination of authentication and authorization considerations. In an Actix-web service, mTLS is typically enforced at the transport layer by configuring the server to request and validate client certificates. When mTLS is in use, the server authenticates the client using a certificate, which can reduce reliance on session-based cookies for authentication. However, CSRF concerns shift rather than disappear because state-changing requests can still be forged by an authenticated client within the same origin context or via malicious pages that an authenticated user visits.

In Actix, mTLS is commonly implemented by integrating Rust TLS acceptor configurations that require client certificates. If the application relies solely on the presence of a valid client certificate to authorize sensitive operations without additional anti-CSRF controls (such as same-site cookie attributes, CSRF tokens, or custom request validation), an authenticated client may inadvertently execute unintended actions. For example, an authenticated user who visits a malicious site could trigger requests from their browser that include the client certificate-based authentication context, leading to unauthorized state changes.

Consider an endpoint in Actix that updates a user’s email using a POST request. If the endpoint only verifies the client certificate and does not validate a CSRF token or check the Origin header, an attacker could craft a form on another site that submits the request on behalf of the authenticated client. Because mTLS handles authentication at the TLS layer, the application might incorrectly assume that requests with valid certificates are safe from CSRF. This assumption can expose the API to CSRF when the same-origin policy is bypassed by malicious sites or when requests are made via embedded resources or cross-origin requests that browsers allow under certain conditions.

The combination of mTLS and CSRF in Actix requires careful design. mTLS ensures that only clients with trusted certificates can authenticate, but it does not inherently protect against CSRF because the browser may still send requests automatically within the same origin context. Developers must implement additional protections such as synchronizer token patterns or same-site cookie policies to mitigate CSRF risks. Relying solely on mTLS for CSRF prevention can lead to vulnerabilities where authenticated requests are forged by malicious sites.

Mutual Tls-Specific Remediation in Actix

To remediate CSRF risks in Actix while using mutual TLS, implement defense-in-depth strategies that complement mTLS. This includes enforcing same-site cookie attributes, validating Origin and Referer headers for state-changing requests, and using anti-CSRF tokens for critical operations. Below are concrete code examples for configuring mTLS in Actix and applying CSRF protections.

Mutual TLS configuration in Actix

To set up mTLS in Actix, configure the Rust TLS acceptor with client certificate verification. The following example uses the rustls crate to require client certificates and validate them against a trusted CA.

use actix_web::{web, App, HttpServer};  
use actix_web_httpauth::extractors::AuthenticationError;  
use std::sync::Arc;  
use rustls::{Certificate, PrivateKey, ServerConfig};  
use rustls_pemfile::{certs, pkcs8_private_keys};  
use std::io::BufReader;  
use std::fs::File;  

async fn validate_client_cert(cert: &[u8]) -> bool {  
    // Implement certificate validation logic, e.g., check against a trusted CA  
    true  
}  

fn load_rustls_config() -> Arc<ServerConfig> {  
    let mut cert_reader = BufReader::new(File::open("ca.pem").expect("cannot open CA cert"));  
    let ca_certs = certs(&mut cert_reader).expect("invalid CA cert").into_iter().map(Certificate).collect();  

    let mut key_reader = BufReader::new(File::open("server.key").expect("cannot open key"));  
    let mut keys: Vec<PrivateKey> = pkcs8_private_keys(&mut key_reader).expect("invalid key");  

    let mut server_config = ServerConfig::builder()  
        .with_safe_defaults()  
        .with_client_auth_cert(ca_certs, |certs| {  
            // Validate client certificates  
            certs.iter().all(|cert| validate_client_cert(&cert.0))  
        })  
        .expect("invalid client auth");  

    let server_cert = Certificate(std::fs::read("server.crt").expect("cannot read server cert"));  
    let server_key = PrivateKey(std::fs::read("server.key").expect("cannot read server key"));  
    server_config.set_single_cert(vec![server_cert], server_key).expect("invalid cert/key");  
    Arc::new(server_config)  
}  

#[actix_web::main]  
async fn main() -> std::io::Result<()> {  
    let rustls_config = load_rustls_config();  
    HttpServer::new(move || {  
        App::new()  
            .app_data(web::Data::new(rustls_config.clone()))  
            .route("/update-email", web::post().to(update_email))  
    })  
    .bind_rustls("127.0.0.1:8443", rustls_config)?  
    .run()  
    .await  
}

CSRF protection with same-site cookies and origin validation

For endpoints that perform state-changing operations, use same-site cookies and validate the Origin header to mitigate CSRF. The following example demonstrates setting a same-site cookie and checking the Origin header in an Actix handler.

use actix_web::{web, HttpRequest, HttpResponse, Responder};  

#[actix_web::post("/update-email")]  
async fn update_email(req: HttpRequest, form: web::Form<EmailForm>) -> impl Responder {  
    // Validate Origin header to prevent CSRF  
    if let Some(origin) = req.headers().get("Origin") {  
        if origin != "https://trusted.example.com" {  
            return HttpResponse::Forbidden().body("Invalid Origin");  
        }  
    } else {  
        return HttpResponse::Forbidden().body("Missing Origin");  
    }  

    // Set a same-site cookie for session management  
    HttpResponse::Ok()  
        .cookie(  
            actix_web::cookie::Cookie::build("session", "value")  
                .same_site(actix_web::cookie::SameSite::Strict)  
                .secure(true)  
                .finish(),  
        )  
        .body("Email updated")  
}

CSRF token validation for critical operations

For high-risk operations, implement synchronizer token patterns. The server issues a CSRF token that must be included in the request payload or headers. The example below shows a simple token validation middleware in Actix.

use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error, HttpMessage};  
use actix_web::body::BoxBody;  
use actix_web::http::header::HeaderValue;  
use actix_web::middleware::Next;  
use actix_web::web::Bytes;  

pub struct CsrfMiddleware;  

impl actix_web::dev::Transform for CsrfMiddleware  
where  
    S: actix_web::dev::Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,  
    S::Future: 'static,  
    B: 'static,  
{  
    type Response = ServiceResponse<BoxBody>;  
    type Error = Error;  
    type InitError = ();  
    type Transform = CsrfMiddlewareImpl<S>;  
    type Future = std::future::Ready<Result<Self::Transform, Self::InitError>>;  

    fn new_transform(&self, service: S) -> Self::Future {  
        std::future::ready(Ok(CsrfMiddlewareImpl { service }))  
    }  
}  

pub struct CsrfMiddlewareImpl<S> {  
    service: S,  
}  

impl<S, B> actix_web::dev::Service<ServiceRequest> for CsrfMiddlewareImpl<S>  
where  
    S: actix_web::dev::Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,  
    S::Future: 'static,  
    B: 'static,  
{  
    type Response = ServiceResponse<BoxBody>;  
    type Error = Error;  
    type Future = std::pin::Pin<Box<dyn std::future::Future<Output = Result<Self::Response, Self::Error>>>;  

    fn poll_ready(&self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {  
        self.service.poll_ready(cx)  
    }  

    fn call(&self, req: ServiceRequest) -> Self::Future {  
        let csrf_token = req.headers().get("X-CSRF-Token");  
        if csrf_token != Some(&HeaderValue::from_static("expected-token")) {  
            let response = ServiceResponse::new(  
                req.into_parts().0,  
                BoxBody::new("Invalid CSRF token".into()),  
            );  
            return Box::pin(async { Ok(response) });  
        }  
        let fut = self.service.call(req);  
        Box::pin(async move {  
            let res = fut.await?;  
            Ok(res)  
        })  
    }  
}

Frequently Asked Questions

Does mTLS alone prevent CSRF in Actix APIs?
No. While mTLS authenticates clients at the TLS layer, it does not prevent CSRF. Browsers may still send authenticated requests automatically when a user visits a malicious site. Implement additional CSRF protections such as same-site cookies, Origin header validation, or synchronizer tokens.
How does CSRF risk change when using mTLS in Actix?
mTLS shifts the threat model: authentication is tied to certificates rather than cookies, but CSRF remains possible if the application does not validate request origins or include anti-CSRF tokens. Developers must still apply CSRF mitigations for state-changing endpoints.