fix(media): use csp instead of modifying content-type

merge-requests/675/merge
Matthias Ahouansou 3 months ago
parent 7b19618136
commit 1dbb3433e0
No known key found for this signature in database

@ -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` /// # `POST /_matrix/media/r0/upload`
/// ///
/// Permanently save media in the server. /// Permanently save media in the server.
@ -108,13 +100,13 @@ pub async fn get_content_route(
if let Some(FileMeta { if let Some(FileMeta {
content_disposition, content_disposition,
content_type,
file, file,
..
}) = services().media.get(mxc.clone()).await? }) = services().media.get(mxc.clone()).await?
{ {
Ok(get_content::v3::Response { Ok(get_content::v3::Response {
file, file,
content_type: Some("application/octet-stream".to_owned()), content_type,
content_disposition, content_disposition,
cross_origin_resource_policy: Some("cross-origin".to_owned()), cross_origin_resource_policy: Some("cross-origin".to_owned()),
}) })
@ -124,7 +116,7 @@ pub async fn get_content_route(
Ok(get_content::v3::Response { Ok(get_content::v3::Response {
content_disposition: remote_content_response.content_disposition, 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, file: remote_content_response.file,
cross_origin_resource_policy: Some("cross-origin".to_owned()), cross_origin_resource_policy: Some("cross-origin".to_owned()),
}) })
@ -143,10 +135,13 @@ pub async fn get_content_as_filename_route(
) -> Result<get_content_as_filename::v3::Response> { ) -> Result<get_content_as_filename::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id); 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 { Ok(get_content_as_filename::v3::Response {
file, file,
content_type: Some("application/octet-stream".to_owned()), content_type,
content_disposition: Some(format!("inline; filename={}", body.filename)), content_disposition: Some(format!("inline; filename={}", body.filename)),
cross_origin_resource_policy: Some("cross-origin".to_owned()), 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 { Ok(get_content_as_filename::v3::Response {
content_disposition: Some(format!("inline: filename={}", body.filename)), 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, file: remote_content_response.file,
cross_origin_resource_policy: Some("cross-origin".to_owned()), 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 { Ok(get_content_thumbnail::v3::Response {
file, file,
content_type: content_type.map(sanitize_content_type), content_type,
cross_origin_resource_policy: Some("cross-origin".to_owned()), cross_origin_resource_policy: Some("cross-origin".to_owned()),
}) })
} else if &*body.server_name != services().globals.server_name() && body.allow_remote { } else if &*body.server_name != services().globals.server_name() && body.allow_remote {
let mut get_thumbnail_response = services() let get_thumbnail_response = services()
.sending .sending
.send_federation_request( .send_federation_request(
&body.server_name, &body.server_name,
@ -225,10 +220,6 @@ pub async fn get_content_thumbnail_route(
) )
.await?; .await?;
get_thumbnail_response.content_type = get_thumbnail_response
.content_type
.map(sanitize_content_type);
Ok(get_thumbnail_response) Ok(get_thumbnail_response)
} else { } else {
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found.")) Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))

@ -2,7 +2,8 @@ use std::{future::Future, io, net::SocketAddr, sync::atomic, time::Duration};
use axum::{ use axum::{
extract::{DefaultBodyLimit, FromRequestParts, MatchedPath}, extract::{DefaultBodyLimit, FromRequestParts, MatchedPath},
response::IntoResponse, middleware::map_response,
response::{IntoResponse, Response},
routing::{any, get, on, MethodFilter}, routing::{any, get, on, MethodFilter},
Router, Router,
}; };
@ -13,7 +14,7 @@ use figment::{
Figment, Figment,
}; };
use http::{ use http::{
header::{self, HeaderName}, header::{self, HeaderName, CONTENT_SECURITY_POLICY},
Method, StatusCode, Uri, Method, StatusCode, Uri,
}; };
use ruma::api::{ 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<()> { async fn run_server() -> io::Result<()> {
let config = &services().globals.config; let config = &services().globals.config;
let addr = SocketAddr::from((config.address, config.port)); let addr = SocketAddr::from((config.address, config.port));
@ -181,6 +189,7 @@ async fn run_server() -> io::Result<()> {
]) ])
.max_age(Duration::from_secs(86400)), .max_age(Duration::from_secs(86400)),
) )
.layer(map_response(set_csp_header))
.layer(DefaultBodyLimit::max( .layer(DefaultBodyLimit::max(
config config
.max_request_size .max_request_size
@ -219,7 +228,7 @@ async fn run_server() -> io::Result<()> {
async fn spawn_task<B: Send + 'static>( async fn spawn_task<B: Send + 'static>(
req: http::Request<B>, req: http::Request<B>,
next: axum::middleware::Next<B>, next: axum::middleware::Next<B>,
) -> std::result::Result<axum::response::Response, StatusCode> { ) -> std::result::Result<Response, StatusCode> {
if services().globals.shutdown.load(atomic::Ordering::Relaxed) { if services().globals.shutdown.load(atomic::Ordering::Relaxed) {
return Err(StatusCode::SERVICE_UNAVAILABLE); return Err(StatusCode::SERVICE_UNAVAILABLE);
} }
@ -231,7 +240,7 @@ async fn spawn_task<B: Send + 'static>(
async fn unrecognized_method<B: Send>( async fn unrecognized_method<B: Send>(
req: http::Request<B>, req: http::Request<B>,
next: axum::middleware::Next<B>, next: axum::middleware::Next<B>,
) -> std::result::Result<axum::response::Response, StatusCode> { ) -> std::result::Result<Response, StatusCode> {
let method = req.method().clone(); let method = req.method().clone();
let uri = req.uri().clone(); let uri = req.uri().clone();
let inner = next.run(req).await; let inner = next.run(req).await;

Loading…
Cancel
Save