Denial Of Service in Actix
How Denial Of Service Manifests in Actix
Denial of Service (DoS) attacks against Actix applications typically exploit the framework's async runtime and request handling patterns. Unlike traditional synchronous servers, Actix's Tokio-based runtime creates opportunities for resource exhaustion through specific attack vectors.
The most common DoS pattern in Actix involves slowloris-style attacks where clients establish connections but send data extremely slowly. Actix's default timeout settings are often too permissive, allowing attackers to tie up worker threads for extended periods. A malicious client can send partial HTTP requests and keep connections open indefinitely, consuming Tokio's thread pool capacity.
use actix_web::{web, App, HttpServer, Responder};
use std::time::Duration;
async fn vulnerable_endpoint() -> impl Responder {
// This endpoint has no timeout protection
tokio::time::sleep(Duration::from_secs(60)).await;
"response"
}
#[actix_web::main]
async fn main() {
HttpServer::new(|| {
App::new()
.service(web::resource("/vulnerable")
.route(web::get().to(vulnerable_endpoint)))
})
.bind("127.0.0.1:8080")?.run()
.await;
}
This code creates a perfect DoS target. An attacker can flood the server with requests to /vulnerable, each holding a worker thread for 60 seconds. With default Actix settings (typically 16 worker threads), just 16 concurrent requests can completely block the server.
Another critical Actix DoS vector involves unbounded request body reading. Actix's default extractor will read entire request bodies into memory without limits, making the application vulnerable to memory exhaustion attacks:
use actix_web::{web, App, HttpServer, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct MaliciousPayload {
data: String,
}
async fn unbounded_body(payload: web::Json<MaliciousPayload>) -> impl Responder {
// No size limit on the JSON body
format!("Received: {}", payload.data.len())
}
#[actix_web::main]
async fn main() {
HttpServer::new(|| {
App::new()
.service(web::resource("/unbounded")
.route(web::post().to(unbounded_body)))
})
.bind("127.0.0.1:8080")?.run()
.await;
}
An attacker can send a 10GB JSON payload, causing Actix to attempt loading it entirely into memory, potentially crashing the application or causing the OS to kill it for memory exhaustion.
Actix's middleware system can also introduce DoS vulnerabilities when middleware performs expensive operations without proper safeguards. Consider a logging middleware that processes request bodies:
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, HttpServer, App, Error};
use actix_web::middleware::Middleware;
use futures_util::future::LocalBoxFuture;
use std::pin::Pin;
struct ExpensiveLogger;
impl Middleware for ExpensiveLogger
where
S: actix_web::dev::Service>,
S::Future: 'static,
S::Error: 'static,
B: actix_http::body::MessageBody + 'static,
{
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Future = LocalBoxFuture<'static, Result>;
type Start = ();
fn start(&self, _req: &ServiceRequest) -> Result<Self::Start, Self::InitError> {
Ok(())
}
fn call(&self, req: ServiceRequest, srv: S) -> Self::Future {
// No timeout or resource limits
Box::pin(async move {
let res = srv.call(req).await?;
// Expensive operation without limits
let body = res.body().clone();
let _ = body.as_bytes().unwrap().len();
Ok(res)
})
}
}
This middleware processes every request body without any rate limiting or timeout, creating a perfect amplification vector for DoS attacks.
Actix-Specific Detection
Detecting DoS vulnerabilities in Actix applications requires understanding both the framework's architecture and common attack patterns. middleBrick's API security scanner includes specialized detection for Actix-specific DoS vectors.
For Actix applications, middleBrick performs several key detection operations:
Timeout Configuration Analysis: The scanner examines HTTP server configurations to identify missing or excessive timeout values. Actix applications should configure read, write, and keep-alive timeouts appropriately. The scanner checks for default or missing timeout configurations that leave applications vulnerable to slowloris attacks.
Request Size Limiting: middleBrick analyzes endpoint configurations to detect missing payload size limits. In Actix, the web::Json and web::Bytes extractors should have explicit size limits configured. The scanner verifies that these limits exist and are set to reasonable values based on the application's requirements.
Resource Usage Monitoring: During scanning, middleBrick simulates high-load conditions to observe how Actix applications handle concurrent requests. The scanner measures response times, connection handling, and resource utilization to identify potential DoS vulnerabilities.
Middleware Security Analysis: The scanner examines middleware chains for expensive operations without proper safeguards. Actix middleware that processes request bodies, performs external calls, or executes complex logic without timeouts represents a DoS risk.
Here's how you can use middleBrick to scan your Actix application:
// Install middleBrick CLI
npm install -g middlebrick
// Scan your Actix API endpoint
middlebrick scan https://your-actix-app.com/api/endpoint
// Example output showing DoS vulnerabilities
{
"risk_score": 45,
"grade": "D",
"findings": [
{
"category": "Rate Limiting",
"severity": "high",
"title": "Missing request size limits",
"remediation": "Configure web::Json<T> with explicit size limits using Config::limit()"
},
{
"category": "Timeout Protection",
"severity": "high",
"title": "Excessive default timeouts",
"remediation": "Set appropriate read/write timeouts using Server::timeout()"
}
]
}
middleBrick's continuous monitoring plan can automatically scan your Actix APIs on a schedule, alerting you when new DoS vulnerabilities are detected or when security scores drop below your configured thresholds.
Actix-Specific Remediation
Securing Actix applications against DoS attacks requires implementing multiple defensive layers. Here are Actix-specific remediation strategies with working code examples.
Configure Request Timeouts: Set appropriate timeouts for all HTTP operations to prevent slowloris attacks:
use actix_web::{web, App, HttpServer, Responder};
use std::time::Duration;
async fn secure_endpoint() -> impl Responder {
// Process request with timeout protection
"secure response"
}
#[actix_web::main]
async fn main() {
HttpServer::new(|| {
App::new()
.service(web::resource("/secure")
.route(web::get().to(secure_endpoint)))
})
.timeout(Duration::from_secs(10)) // Read timeout
.keep_alive(Duration::from_secs(5)) // Keep-alive timeout
.bind("127.0.0.1:8080")?.run()
.await;
}
Implement Request Size Limits: Configure payload size limits for all extractors:
use actix_web::{web, App, HttpServer, Responder};
use actix_web::web::JsonConfig;
#[derive(serde::Deserialize)]
struct SecurePayload {
data: String,
}
async fn size_limited_endpoint(payload: web::Json<SecurePayload>) -> impl Responder {
format!("Processed: {}", payload.data.len())
}
#[actix_web::main]
async fn main() {
let json_config = JsonConfig::default()
.limit(1024 * 1024) // 1MB limit
.error_handler(|err, _req| {
actix_web::error::ErrorBadRequest("Payload too large")
});
HttpServer::new(|| {
App::new()
.app_data(json_config)
.service(web::resource("/size-limited")
.route(web::post().to(size_limited_endpoint)))
})
.bind("127.0.0.1:8080")?.run()
.await;
}
Add Rate Limiting: Implement rate limiting to prevent request flooding:
use actix_web::{web, App, HttpServer, Responder};
use actix_web::middleware::errhandlers::ErrorHandlerResponse;
use actix_web::dev::ServiceResponse;
use actix_web::http::StatusCode;
use actix_ratelimit::RateLimiter;
use std::time::Duration;
async fn rate_limited_endpoint() -> impl Responder {
"processed"
}
#[actix_web::main]
async fn main() {
HttpServer::new(|| {
App::new()
.wrap(
RateLimiter::middleware(
// 100 requests per minute per IP
actix_ratelimit::MemoryStore::new(Duration::from_secs(60)),
actix_ratelimit::RateQuota::new(100, Duration::from_secs(60)),
)
.with_err_handler(|_err, _req| {
ErrorHandlerResponse::Response(ServiceResponse::new(
_req.head().clone(),
actix_http::Response::build(StatusCode::TOO_MANY_REQUESTS)
.body("Rate limit exceeded"),
))
}),
)
.service(web::resource("/rate-limited")
.route(web::get().to(rate_limited_endpoint)))
})
.bind("127.0.0.1:8080")?.run()
.await;
}
Implement Circuit Breakers: Add circuit breaker patterns for external service calls:
use actix_web::{web, App, HttpServer, Responder};
use actix_web::middleware::errhandlers::ErrorHandlerResponse;
use actix_web::dev::ServiceResponse;
use actix_web::http::StatusCode;
use actix_circuitbreaker::{CircuitBreaker, TripWhen, TripWhenAll};
use std::time::Duration;
async fn external_service_call() -> Result<String, ()> {
// Simulate external service call
tokio::time::sleep(Duration::from_millis(500)).await;
Ok("external data".to_string())
}
async fn protected_endpoint() -> impl Responder {
let circuit_breaker = CircuitBreaker::builder(
TripWhen::when_all([
TripWhen::new(|err: &Result<String, ()>| err.is_err(), 5), // Trip after 5 failures
TripWhen::new(|_res: &Result<String, ()>| false, 0), // No success threshold
]),
Duration::from_secs(30), // Reset after 30 seconds
)
.build();
match circuit_breaker.call(|| external_service_call()).await {
Ok(data) => data,
Err(_) => "service unavailable".to_string(),
}
}
#[actix_web::main]
async fn main() {
HttpServer::new(|| {
App::new()
.service(web::resource("/protected")
.route(web::get().to(protected_endpoint)))
})
.bind("127.0.0.1:8080")?.run()
.await;
}
Memory Safety Measures: Implement streaming for large payloads instead of loading into memory:
use actix_web::{web, App, HttpServer, Responder};
use bytes::BytesMut;
use futures_util::stream::StreamExt;
async fn streaming_endpoint(mut payload: web::Payload) -> impl Responder {
let mut body = BytesMut::new();
while let Some(chunk) = payload.next().await {
let chunk = chunk.unwrap();
if body.len() + chunk.len() > 1024 * 1024 { // 1MB limit
return actix_web::HttpResponse::build(StatusCode::PAYLOAD_TOO_LARGE)
.finish();
}
body.extend_from_slice(&chunk);
}
format!("Processed {} bytes", body.len())
}
#[actix_web::main]
async fn main() {
HttpServer::new(|| {
App::new()
.service(web::resource("/streaming")
.route(web::post().to(streaming_endpoint)))
})
.bind("127.0.0.1:8080")?.run()
.await;
}
These remediation strategies significantly reduce the DoS attack surface of Actix applications by implementing proper timeout handling, size limits, rate limiting, and circuit breakers.
Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |