0
0
Fork 0
mirror of https://github.com/dani-garcia/vaultwarden synced 2024-12-15 18:13:43 +01:00

Secure send file uploads (#4810)

Currently there are no checks done during the actual upload of the file of a send item.
This PR adds several checks to make sure it only accepts the correct uploads.
This commit is contained in:
Mathijs van Veluw 2024-07-31 15:24:15 +02:00 committed by GitHub
parent d7df0ad79e
commit c28246cf34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -349,7 +349,15 @@ async fn post_send_file_v2(data: Json<SendData>, headers: Headers, mut conn: DbC
}))) })))
} }
// https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L243 #[derive(Deserialize)]
#[allow(non_snake_case)]
pub struct SendFileData {
id: String,
size: u64,
fileName: String,
}
// https://github.com/bitwarden/server/blob/66f95d1c443490b653e5a15d32977e2f5a3f9e32/src/Api/Tools/Controllers/SendsController.cs#L250
#[post("/sends/<send_uuid>/file/<file_id>", format = "multipart/form-data", data = "<data>")] #[post("/sends/<send_uuid>/file/<file_id>", format = "multipart/form-data", data = "<data>")]
async fn post_send_file_v2_data( async fn post_send_file_v2_data(
send_uuid: &str, send_uuid: &str,
@ -367,15 +375,55 @@ async fn post_send_file_v2_data(
err!("Send not found. Unable to save the file.") err!("Send not found. Unable to save the file.")
}; };
if send.atype != SendType::File as i32 {
err!("Send is not a file type send.");
}
let Some(send_user_id) = &send.user_uuid else { let Some(send_user_id) = &send.user_uuid else {
err!("Sends are only supported for users at the moment") err!("Sends are only supported for users at the moment.")
}; };
if send_user_id != &headers.user.uuid { if send_user_id != &headers.user.uuid {
err!("Send doesn't belong to user"); err!("Send doesn't belong to user.");
}
let Ok(send_data) = serde_json::from_str::<SendFileData>(&send.data) else {
err!("Unable to decode send data as json.")
};
match data.data.raw_name() {
Some(raw_file_name) if raw_file_name.dangerous_unsafe_unsanitized_raw() == send_data.fileName => (),
Some(raw_file_name) => err!(
"Send file name does not match.",
format!(
"Expected file name '{}' got '{}'",
send_data.fileName,
raw_file_name.dangerous_unsafe_unsanitized_raw()
)
),
_ => err!("Send file name does not match or is not provided."),
}
if file_id != send_data.id {
err!("Send file does not match send data.", format!("Expected id {} got {file_id}", send_data.id));
}
let Some(size) = data.data.len().to_u64() else {
err!("Send file size overflow.");
};
if size != send_data.size {
err!("Send file size does not match.", format!("Expected a file size of {} got {size}", send_data.size));
} }
let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(send_uuid); let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(send_uuid);
let file_path = folder_path.join(file_id); let file_path = folder_path.join(file_id);
// Check if the file already exists, if that is the case do not overwrite it
if tokio::fs::metadata(&file_path).await.is_ok() {
err!("Send file has already been uploaded.", format!("File {file_path:?} already exists"))
}
tokio::fs::create_dir_all(&folder_path).await?; tokio::fs::create_dir_all(&folder_path).await?;
if let Err(_err) = data.data.persist_to(&file_path).await { if let Err(_err) = data.data.persist_to(&file_path).await {