Fix video part directory traversal
This commit is contained in:
53
src/main.rs
53
src/main.rs
@@ -20,6 +20,7 @@ use std::{
|
|||||||
use walkdir::{DirEntry, WalkDir};
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
use actix_files::NamedFile;
|
use actix_files::NamedFile;
|
||||||
|
use actix_cors::Cors;
|
||||||
use actix_multipart as mp;
|
use actix_multipart as mp;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
App, HttpRequest, HttpResponse, HttpServer, Responder, delete, get, middleware, post, put,
|
App, HttpRequest, HttpResponse, HttpServer, Responder, delete, get, middleware, post, put,
|
||||||
@@ -430,8 +431,34 @@ async fn get_video_part(
|
|||||||
let mut file_part = PathBuf::new();
|
let mut file_part = PathBuf::new();
|
||||||
file_part.push(app_state.video_path.clone());
|
file_part.push(app_state.video_path.clone());
|
||||||
file_part.push(part);
|
file_part.push(part);
|
||||||
// TODO: Do we need to guard against directory attacks here?
|
|
||||||
match NamedFile::open(&file_part) {
|
// Guard against directory traversal attacks
|
||||||
|
let canonical_base = match std::fs::canonicalize(&app_state.video_path) {
|
||||||
|
Ok(path) => path,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to canonicalize video path: {:?}", e);
|
||||||
|
span.set_status(Status::error("Invalid video path configuration"));
|
||||||
|
return HttpResponse::InternalServerError().finish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let canonical_file = match std::fs::canonicalize(&file_part) {
|
||||||
|
Ok(path) => path,
|
||||||
|
Err(_) => {
|
||||||
|
warn!("Video part not found or invalid: {:?}", file_part);
|
||||||
|
span.set_status(Status::error(format!("Video part not found '{}'", part)));
|
||||||
|
return HttpResponse::NotFound().finish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure the resolved path is still within the video directory
|
||||||
|
if !canonical_file.starts_with(&canonical_base) {
|
||||||
|
warn!("Directory traversal attempt detected: {:?}", part);
|
||||||
|
span.set_status(Status::error("Invalid video path"));
|
||||||
|
return HttpResponse::Forbidden().finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
match NamedFile::open(&canonical_file) {
|
||||||
Ok(file) => {
|
Ok(file) => {
|
||||||
span.set_status(Status::Ok);
|
span.set_status(Status::Ok);
|
||||||
file.into_response(&request)
|
file.into_response(&request)
|
||||||
@@ -714,8 +741,30 @@ fn main() -> std::io::Result<()> {
|
|||||||
let favorites_dao = SqliteFavoriteDao::new();
|
let favorites_dao = SqliteFavoriteDao::new();
|
||||||
let tag_dao = SqliteTagDao::default();
|
let tag_dao = SqliteTagDao::default();
|
||||||
let exif_dao = SqliteExifDao::new();
|
let exif_dao = SqliteExifDao::new();
|
||||||
|
let cors = Cors::default()
|
||||||
|
.allowed_origin_fn(|origin, _req_head| {
|
||||||
|
// Allow all origins in development, or check against CORS_ALLOWED_ORIGINS env var
|
||||||
|
if let Ok(allowed_origins) = env::var("CORS_ALLOWED_ORIGINS") {
|
||||||
|
allowed_origins.split(',').any(|allowed| {
|
||||||
|
origin.as_bytes() == allowed.trim().as_bytes()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Default: allow all origins if not configured
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.allowed_methods(vec!["GET", "POST", "PUT", "DELETE", "OPTIONS"])
|
||||||
|
.allowed_headers(vec![
|
||||||
|
actix_web::http::header::AUTHORIZATION,
|
||||||
|
actix_web::http::header::ACCEPT,
|
||||||
|
actix_web::http::header::CONTENT_TYPE,
|
||||||
|
])
|
||||||
|
.supports_credentials()
|
||||||
|
.max_age(3600);
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
|
.wrap(cors)
|
||||||
.service(web::resource("/login").route(web::post().to(login::<SqliteUserDao>)))
|
.service(web::resource("/login").route(web::post().to(login::<SqliteUserDao>)))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/photos")
|
web::resource("/photos")
|
||||||
|
|||||||
Reference in New Issue
Block a user