From 1dbb3433e06d5f721408b9bf5cdfa1b0828a5054 Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Mon, 3 Jun 2024 21:35:20 +0100 Subject: [PATCH] fix(media): use csp instead of modifying content-type --- src/api/client_server/media.rs | 31 +++++++++++-------------------- src/main.rs | 17 +++++++++++++---- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/api/client_server/media.rs b/src/api/client_server/media.rs index a1bfab40..5cd2b2f9 100644 --- a/src/api/client_server/media.rs +++ b/src/api/client_server/media.rs @@ -22,14 +22,6 @@ pub async fn get_media_config_route( }) } -fn sanitize_content_type(content_type: String) -> String { - if content_type == "image/jpeg" || content_type == "image/png" { - content_type - } else { - "application/octet-stream".to_owned() - } -} - /// # `POST /_matrix/media/r0/upload` /// /// Permanently save media in the server. @@ -108,13 +100,13 @@ pub async fn get_content_route( if let Some(FileMeta { content_disposition, + content_type, file, - .. }) = services().media.get(mxc.clone()).await? { Ok(get_content::v3::Response { file, - content_type: Some("application/octet-stream".to_owned()), + content_type, content_disposition, cross_origin_resource_policy: Some("cross-origin".to_owned()), }) @@ -124,7 +116,7 @@ pub async fn get_content_route( Ok(get_content::v3::Response { content_disposition: remote_content_response.content_disposition, - content_type: Some("application/octet-stream".to_owned()), + content_type: remote_content_response.content_type, file: remote_content_response.file, cross_origin_resource_policy: Some("cross-origin".to_owned()), }) @@ -143,10 +135,13 @@ pub async fn get_content_as_filename_route( ) -> Result { let mxc = format!("mxc://{}/{}", body.server_name, body.media_id); - if let Some(FileMeta { file, .. }) = services().media.get(mxc.clone()).await? { + if let Some(FileMeta { + file, content_type, .. + }) = services().media.get(mxc.clone()).await? + { Ok(get_content_as_filename::v3::Response { file, - content_type: Some("application/octet-stream".to_owned()), + content_type, content_disposition: Some(format!("inline; filename={}", body.filename)), cross_origin_resource_policy: Some("cross-origin".to_owned()), }) @@ -156,7 +151,7 @@ pub async fn get_content_as_filename_route( Ok(get_content_as_filename::v3::Response { content_disposition: Some(format!("inline: filename={}", body.filename)), - content_type: Some("application/octet-stream".to_owned()), + content_type: remote_content_response.content_type, file: remote_content_response.file, cross_origin_resource_policy: Some("cross-origin".to_owned()), }) @@ -192,11 +187,11 @@ pub async fn get_content_thumbnail_route( { Ok(get_content_thumbnail::v3::Response { file, - content_type: content_type.map(sanitize_content_type), + content_type, cross_origin_resource_policy: Some("cross-origin".to_owned()), }) } else if &*body.server_name != services().globals.server_name() && body.allow_remote { - let mut get_thumbnail_response = services() + let get_thumbnail_response = services() .sending .send_federation_request( &body.server_name, @@ -225,10 +220,6 @@ pub async fn get_content_thumbnail_route( ) .await?; - get_thumbnail_response.content_type = get_thumbnail_response - .content_type - .map(sanitize_content_type); - Ok(get_thumbnail_response) } else { Err(Error::BadRequest(ErrorKind::NotFound, "Media not found.")) diff --git a/src/main.rs b/src/main.rs index 6eeff9a0..5fd248a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,8 @@ use std::{future::Future, io, net::SocketAddr, sync::atomic, time::Duration}; use axum::{ extract::{DefaultBodyLimit, FromRequestParts, MatchedPath}, - response::IntoResponse, + middleware::map_response, + response::{IntoResponse, Response}, routing::{any, get, on, MethodFilter}, Router, }; @@ -13,7 +14,7 @@ use figment::{ Figment, }; use http::{ - header::{self, HeaderName}, + header::{self, HeaderName, CONTENT_SECURITY_POLICY}, Method, StatusCode, Uri, }; use ruma::api::{ @@ -141,6 +142,13 @@ async fn main() { } } +/// Adds additional headers to prevent any potential XSS attacks via the media repo +async fn set_csp_header(response: Response) -> impl IntoResponse { + ( + [(CONTENT_SECURITY_POLICY, "sandbox; default-src 'none'; script-src 'none'; plugin-types application/pdf; style-src 'unsafe-inline'; object-src 'self';")], response + ) +} + async fn run_server() -> io::Result<()> { let config = &services().globals.config; let addr = SocketAddr::from((config.address, config.port)); @@ -181,6 +189,7 @@ async fn run_server() -> io::Result<()> { ]) .max_age(Duration::from_secs(86400)), ) + .layer(map_response(set_csp_header)) .layer(DefaultBodyLimit::max( config .max_request_size @@ -219,7 +228,7 @@ async fn run_server() -> io::Result<()> { async fn spawn_task( req: http::Request, next: axum::middleware::Next, -) -> std::result::Result { +) -> std::result::Result { if services().globals.shutdown.load(atomic::Ordering::Relaxed) { return Err(StatusCode::SERVICE_UNAVAILABLE); } @@ -231,7 +240,7 @@ async fn spawn_task( async fn unrecognized_method( req: http::Request, next: axum::middleware::Next, -) -> std::result::Result { +) -> std::result::Result { let method = req.method().clone(); let uri = req.uri().clone(); let inner = next.run(req).await;