Symlink Attack in Axum
How Symlink Attack Manifests in Axum
Symlink attacks in Axum applications typically occur when file operations are performed without validating that a path is not a symbolic link pointing to sensitive locations. In Axum, this vulnerability often appears in endpoint handlers that process file uploads, downloads, or configuration management.
The most common attack pattern involves creating a symbolic link that points to a protected system file or another user's data. When the Axum application processes this link without verification, it may inadvertently read, write, or expose sensitive information. For example, an attacker might create a symlink to /etc/passwd or a database file, then upload it through an endpoint expecting only regular files.
Consider this vulnerable Axum handler:
async fn upload_file(
MultipartParts<File> parts,
path: web::Path<String>,
) -> impl Responder {
let file = parts.files.get("file").unwrap();
let dest_path = format!("/uploads/{}/{}.{}", path, uuid::Uuid::new_v4(), file.content_type);
// Vulnerable: no symlink check
tokio::fs::write(dest_path, file.data).await.unwrap();
HttpResponse::Ok().finish()
}This code is vulnerable because it writes data to a path without verifying that the destination isn't a symbolic link. An attacker could create a symlink to a critical system file and the application would overwrite it.
Another manifestation occurs in configuration endpoints where Axum applications might read files based on user input:
async fn read_config(
config_path: web::Path<String>,
) -> impl Responder {
let path = format!("/configs/{}.json", config_path);
// Vulnerable: could be a symlink to sensitive data
let content = tokio::fs::read_to_string(path).await.unwrap();
HttpResponse::Ok().json(json!({ "content": content }))
}In this case, an attacker could create a symlink to /etc/shadow or other protected files and read their contents through the API.
Axum-Specific Detection
Detecting symlink attacks in Axum requires both static code analysis and runtime scanning. For static analysis, look for file operations that don't validate paths before use. The middleBrick API security scanner can automatically detect these patterns when scanning Axum applications.
When scanning with middleBrick, the tool specifically checks for:
- File write operations without path validation
- File read operations that could follow symlinks
- Path construction from user input without sanitization
- Lack of
followparameter checks on file operations
Here's how to scan your Axum API with middleBrick CLI:
npm install -g middlebrick
middlebrick scan https://yourapi.com/api/upload
The scanner tests for symlink vulnerabilities by attempting to create symlinks during its black-box assessment and checking if the application follows them to sensitive locations. It also analyzes your OpenAPI spec if available to identify endpoints that handle file operations.
For runtime detection, you can add middleware to your Axum application that validates paths before file operations:
use std::path::Path;
async fn validate_path_is_not_symlink(path: &Path) -> Result<(), &'static str> {
if path.symlink_metadata().await?.file_type().is_symlink() {
return Err("Path is a symbolic link");
}
Ok(())
}
// Wrap file operations with validation
async fn safe_write(path: &Path, data: &[u8]) -> Result<(), &'static str> {
validate_path_is_not_symlink(path).await?;
tokio::fs::write(path, data).await.map_err(|_| "Write failed")?;
Ok(())
}middleBrick's continuous monitoring feature (Pro plan) can automatically re-scan your API on a schedule, alerting you if new symlink vulnerabilities are introduced in deployments.
Axum-Specific Remediation
Remediating symlink attacks in Axum requires a multi-layered approach. The most effective strategy combines path validation, secure file handling, and proper error handling.
First, always validate that paths are not symbolic links before performing file operations:
use axum::extract::{Path, MultipartParts};
use axum::http::StatusCode;
use std::path::Path;
use tokio::fs;
async fn safe_upload(
MultipartParts<File> parts: MultipartParts<File>,
path: Path<String>,
) -> impl IntoResponse {
let file = parts.files.get("file").unwrap();
let dest_path = format!("/uploads/{}/{}.{}", path, uuid::Uuid::new_v4(), file.content_type);
let dest_path = Path::new(&dest_path);
// Check if path is a symlink
if let Ok(metadata) = dest_path.symlink_metadata().await {
if metadata.file_type().is_symlink() {
return (StatusCode::BAD_REQUEST, "Invalid file path");
}
}
// Write file safely
if let Err(e) = fs::write(dest_path, file.data).await {
return (StatusCode::INTERNAL_SERVER_ERROR, "File write failed");
}
(StatusCode::OK, "File uploaded successfully")
}
For reading files, use open with the follow parameter set to false to prevent following symlinks:
use std::fs::OpenOptions;
use std::io::ErrorKind;
async fn safe_read_config(
config_name: String,
) -> impl IntoResponse {
let base_path = Path::new("/configs");
let target_path = base_path.join(format!("{}.json", config_name));
// Verify path is within base directory
if !target_path.starts_with(base_path) {
return (StatusCode::BAD_REQUEST, "Invalid path");
}
// Check if it's a symlink
if let Ok(metadata) = target_path.symlink_metadata().await {
if metadata.file_type().is_symlink() {
return (StatusCode::FORBIDDEN, "Symlink not allowed");
}
}
// Read file safely
match fs::read_to_string(&target_path).await {
Ok(content) => (StatusCode::OK, json!({ "content": content })),
Err(e) if e.kind() == ErrorKind::NotFound => {
(StatusCode::NOT_FOUND, "Config not found")
}
Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Read failed"),
}
}
For enterprise deployments, middleBrick Pro's continuous monitoring can scan your Axum APIs on a configurable schedule (every 5, 15, or 60 minutes) and alert your team via Slack or Teams if symlink vulnerabilities are detected in production.