Privilege Escalation in Axum with Mutual Tls
Privilege Escalation in Axum with Mutual Tls
In Axum, privilege escalation can occur when mutual TLS (mTLS) is used for authentication but authorization boundaries are not enforced at the handler or service layer. mTLS provides strong identity verification by requiring both client and server to present valid certificates. However, if the application maps a successful TLS authentication directly to a high-privilege role without additional checks, an attacker who compromises a low-privilege client certificate can perform horizontal or vertical privilege escalation.
For example, consider an API where mTLS authenticates users but the endpoint relies only on the presence of a client certificate to decide whether an operation is allowed. If the authorization logic skips role or scope validation, a certificate provisioned for read-only access might be used to invoke write or admin endpoints. This can map to authorization flaws such as BOLA/IDOR or BFLA/Privilege Escalation, which are part of the 12 parallel checks in middleBrick scans.
Axum does not inherently enforce authorization; it provides the primitives to build authentication and authorization. If developers wire mTLS authentication to a permissive authorization policy, the framework cannot prevent a subject from acting outside their intended permissions. Attack patterns include using a valid certificate with minimal claims to access administrative routes, or exploiting missing scope checks to escalate from read to write or delete operations. Real-world findings from scans have mapped such patterns to OWASP API Top 10 A01:2023 broken object level authorization and A07:2023 identification and authentication failures.
middleBrick tests this attack surface by probing endpoints authenticated via mTLS and checking whether authorization constraints are applied consistently across roles and scopes. It cross-references OpenAPI/Swagger definitions, including full $ref resolution, with runtime behavior to detect mismatches between declared security schemes and actual enforcement. For instance, if an operation is tagged as requiring scope admin but the implementation does not validate scope claims present in the mTLS-associated identity, the scan will flag this as a high-severity finding with remediation guidance.
Concrete examples in Axum often involve extracting identity from TLS extensions or client certificate fields and using them to build a user identity. If this identity is then used to gate resource ownership or administrative actions without verifying explicit permissions, escalation becomes feasible. Consider an endpoint that deletes a resource by ID: if the handler trusts the certificate subject to determine whether the subject can delete any resource, a certificate belonging to a low-privilege user could be used to delete resources belonging to others, creating a BOLA flaw.
To reduce risk, developers should treat mTLS as authentication only and enforce authorization explicitly at each handler. This includes validating roles, scopes, and resource ownership independently of the TLS identity. middleBrick’s reporting includes prioritized findings and remediation guidance mapped to compliance frameworks such as OWASP API Top 10, SOC2, and GDPR, helping teams understand the business impact of these misconfigurations.
Mutual Tls-Specific Remediation in Axum
Remediation focuses on ensuring that mTLS authentication is strictly separated from authorization decisions. In Axum, this means using middleware to extract certificate-based identity and then enforcing fine-grained policies in handlers or via a dedicated authorization layer. Below are concrete, working examples that demonstrate secure patterns.
1. Extract client certificate information in middleware and attach a minimal identity to the request extensions. This keeps authentication distinct from authorization.
use axum::{async_trait, extract::FromRequestParts, http::request::Parts, Extension};
use std::sync::Arc;
use axum::extract::RequestPartsExt;
use rustls::server::ClientCertVerified;
// A simple identity type derived from certificate fields
#[derive(Clone, Debug)]
pub struct MtlsIdentity {
pub subject: String,
pub scopes: Vec,
pub roles: Vec,
}
// Custom extractor that requires a verified client cert
#[async_trait]
impl FromRequestParts for MtlsIdentity
where
S: Send + Sync,
{
type Rejection = axum::http::StatusCode;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result {
// Obtain the verified certificate from the request extensions
let cert_verified = parts.extensions().get::()
.ok_or(axum::http::StatusCode::FORBIDDEN)?;
// In practice, you would inspect the certificate to derive subject and claims.
// This is a simplified placeholder.
let subject = "CN=example-user".to_string();
let scopes = vec!["read:data".to_string()];
let roles = vec!["viewer".to_string()];
Ok(MtlsIdentity { subject, scopes, roles })
}
}
// Mount your routes with Extension> if needed, and use MtlsIdentity in handlers
async fn delete_resource(
identity: MtlsIdentity,
Extension(store): Extension>,
Path(id): Path,
) -> Result {
// Authorization check: ensure identity has permission to delete this resource
if !identity.scopes.contains(&"delete:data".to_string()) {
return Err((StatusCode::FORBIDDEN, "Insufficient scope".into()));
}
// Further checks: e.g., resource ownership or admin role
if !identity.roles.contains(&"admin".to_string()) {
// Implement BOLA check: ensure the subject can only delete their own resources
let resource = store.get(id).map_err(|_| (StatusCode::NOT_FOUND, "Not found"))?;
if resource.owner != identity.subject {
return Err((StatusCode::FORBIDDEN, "Cannot delete others' resources".into()));
}
}
store.delete(id).map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Delete failed"))?;
Ok(StatusCode::NO_CONTENT)
}
#[tokio::main]
async fn main() {
use axum::{routing::delete, Router};
let app = Router::new()
.route("/resources/:id", delete(delete_resource))
// Require mTLS for this route via a TLS acceptor in your server configuration
.layer(Extension(Arc::new(Store::new())));
// Bind with rustls server configuration that requests client certs
let listener = tokio_rustls::TlsAcceptor::from(Arc::new(rustls_server_config()))
.accept(std::net::TcpListener::bind("127.0.0.1:3000").unwrap())
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
fn rustls_server_config() -> rustls::ServerConfig {
// Configure server certificate, private key, and client CA root store
// to request and verify client certificates
unimplemented!("Provide server cert, key, and client CA")
}
2. Explicitly validate authorization in handlers using roles and scopes derived from the certificate. Do not assume mTLS alone grants admin privileges.
async fn admin_endpoint(
identity: MtlsIdentity,
) -> Result {
// Require an admin role for this endpoint
if !identity.roles.contains(&"admin".to_string()) {
return Err((StatusCode::FORBIDDEN, "Admin role required".into()));
}
// Proceed with privileged operation
Ok("Admin action performed")
}
3. Enforce resource-level ownership checks (BOLA prevention). Always validate that the subject derived from the certificate is allowed to operate on the specific resource ID.
async fn update_record(
identity: MtlsIdentity,
Path(id): Path,
Extension(store): Extension>,
) -> Result {
let record = store.get(id).map_err(|_| (StatusCode::NOT_FOUND, "Not found"))?;
// BOLA check: ensure the record belongs to the subject authenticated via mTLS
if record.owner != identity.subject {
return Err((StatusCode::FORBIDDEN, "Not owner".into()));
}
// Perform update
store.update(id, /* data */).map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Update failed"))?;
Ok(StatusCode::OK)
}
These examples show how to combine mTLS authentication with explicit authorization checks in Axum. middleBrick scans can verify that such controls are present and correctly applied by inspecting both the OpenAPI spec and runtime behavior, reducing the likelihood of privilege escalation vulnerabilities.